/* =====================================================================
   Accra Find — Design System
   Modern aesthetic: immersive photography, clean sans-serif display
   type, softly rounded cards, generous white space. No build step —
   plain CSS, safe for any GoDaddy shared hosting account.
   ===================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap');

/* ---------------------------------------------------------------------
   Design tokens
   --------------------------------------------------------------------- */
:root {
    /* Brand palette, sampled from the Accra Find reference design */
    --forest-900: #101f18;
    --forest-800: #16281f;
    --forest-700: #1c3a2b;
    --forest-600: #244937;
    --forest-500: #2f5c46;

    --cream-100: #fdfbf6;
    --cream-200: #f8f3e8;
    --cream-300: #efe8d8;

    --coral-600: #c9573c;
    --coral-500: #dd6b4c;
    --coral-400: #e58467;

    --mustard-500: #f0c744;
    --mustard-400: #f3d876;

    /* Section-accent colors for the four Discover/Find/Events/Assistance
       pillar cards and the Popular Categories tiles on the homepage —
       kept separate from the core brand palette above (forest/coral/
       mustard), used only as small icon-chip backgrounds, never as large
       surfaces. */
    --accent-green: #2f9e5c;
    --accent-blue: #2f6fed;
    --accent-purple: #7c5cf5;
    --accent-orange: #e8823a;
    --accent-red: #e0473e;
    --accent-teal: #1fa2a6;
    --accent-pink: #e0459c;

    /* Dark navy footer, matching the homepage concept design — distinct
       from --forest-900 (which is used for header/CTA dark-green sections). */
    --navy-900: #0b1b2e;
    --navy-800: #12233a;

    --ink-900: #16211b;
    --ink-700: #3c463f;
    --ink-500: #6b756e;
    --ink-300: #a9b0aa;
    --ink-100: #e3e6e2;

    --white: #ffffff;

    --font-display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

    --radius-sm: 8px;
    --radius-md: 10px;
    --radius-lg: 14px;
    --radius-full: 999px;
    --radius-btn: 10px;

    --shadow-sm: 0 2px 8px rgba(16, 31, 24, 0.06);
    --shadow-md: 0 8px 24px rgba(16, 31, 24, 0.10);
    --shadow-lg: 0 20px 48px rgba(16, 31, 24, 0.16);

    --container-width: 1240px;
    --transition: 220ms cubic-bezier(0.4, 0, 0.2, 1);

    /* Admin console redesign v2 (client update #20) — the client's actual
       approved design package (uploaded 2026-08-19) turned out to use a
       different, LIGHTER palette than the first screenshot mockup this was
       originally built from: a "eucalyptus, porcelain and champagne" theme
       (light sage sidebar, deep-emerald actions, champagne-gold accents)
       rather than the dark-green/lime look client update #19 shipped.
       These tokens are scoped for use only inside
       .dash-role-admin/.dash-role-super_admin (and, per the matching
       business-dashboard package, eventually .dash-role-business_owner
       too) — the public site keeps the forest/cream/coral tokens above
       completely untouched. */
    --admin-ink: #182a2e;
    --admin-muted: #64736f;
    --admin-forest: #176b55;
    --admin-forest-2: #214f42;
    --admin-forest-deep: #174d40;
    --admin-gold: #c49a52;
    --admin-gold-deep: #d6b36c;
    --admin-cream: #f2efe8;
    --admin-line: #dedbd1;
    --admin-white: #fffdf9;
    --admin-sky: #90aeb0;
    --admin-coral: #b9894c;
    --admin-navy: #1b3038;
    --admin-active: #1d5d49;
    --admin-active-hover: #164a3b;
    --admin-shadow: 0 16px 40px rgba(27, 48, 56, .075);
    --admin-font: "Helvetica Neue", Helvetica, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
}

/* ---------------------------------------------------------------------
   Reset
   --------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
/* overflow-x: hidden on html/body stops any single element that's a hair
   wider than the viewport (an unconstrained image, an absolutely-positioned
   decorative card, a fixed-width panel inside a mobile media query, etc.)
   from opening up horizontal scroll room on the page. Without this, a phone's
   touch-scroll gesture can drag the whole layout a few pixels sideways as
   well as up/down, which reads as "the screen moves all over the place"
   instead of a clean, locked vertical scroll. Body keeps no explicit height,
   so this does not create a second nested scroll container and does not
   interfere with the sticky header/sidebar elsewhere in this file. */
/* scrollbar-gutter: stable reserves the vertical scrollbar's width at all
   times, even on pages/moments where overflow is toggled to hidden (the
   mobile dashboard nav drawer does this while open — see
   body.dash-nav-locked below). Without it, hiding the scrollbar makes the
   whole page a few pixels wider for as long as the drawer is open, which
   shows up as the entire layout nudging sideways the instant you open or
   close it. Falls back to normal behavior on the handful of older browsers
   that don't support the property yet — no regression, just no benefit. */
html { scroll-behavior: smooth; overflow-x: hidden; width: 100%; scrollbar-gutter: stable; }
body {
    margin: 0;
    font-family: var(--font-body);
    /* Base text size. First trimmed from the 16px browser default to 15px;
       the user then compared the live site directly against Airbnb/Jiji
       side by side and asked for another pass — "space is money," more
       has to be visible per screen without scrolling. 14px matches the
       density of those references and the rest of the site's component
       font-sizes (14-14.5px is already the norm across cards/panels/forms)
       without needing to touch every explicit px value already set
       elsewhere. See also the .section/.filters-panel/.listing-card/
       heading clamp() reductions made in the same pass. */
    font-size: 14px;
    color: var(--ink-900);
    background: var(--cream-100);
    -webkit-font-smoothing: antialiased;
    line-height: 1.55;
    overflow-x: hidden;
    width: 100%;
    position: relative;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
ul { list-style: none; margin: 0; padding: 0; }
input, select, textarea { font-family: inherit; font-size: inherit; }
h1, h2, h3, h4, h5 { margin: 0; font-family: var(--font-display); font-weight: 700; line-height: 1.1; letter-spacing: -0.015em; }
p { margin: 0; }

/* ---------------------------------------------------------------------
   Layout helpers
   --------------------------------------------------------------------- */
.container { max-width: var(--container-width); margin: 0 auto; padding: 0 24px; }
/* Section vertical padding — reduced from 88px/56px per direct user
   feedback comparing the live site to Airbnb/Jiji: "space is money," more
   content needs to be visible per screen without scrolling. */
.section { padding: 52px 0; }
.section--tight { padding: 36px 0; }
/* Revision #6 — the standard 88px top padding reads as cramped specifically
   on a page whose hero sits directly under the solid (non-transparent)
   site header with no photo to visually blend into, like /advertise. Extra
   room above the eyebrow gives the header its own breathing space instead
   of the headline crowding right up against it. */
.section--roomy { padding-top: 80px; }
@media (max-width: 640px) {
    .section--roomy { padding-top: 56px; }
}
.section--cream { background: var(--cream-100); }
.section--beige { background: var(--cream-300); }
.section--forest { background: var(--forest-800); color: var(--cream-100); }
.section--coral { background: var(--coral-500); color: var(--white); }

.eyebrow {
    display: inline-block;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--coral-500);
    margin-bottom: 14px;
}
.section--forest .eyebrow,
.section--coral .eyebrow { color: var(--mustard-500); }

.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 24px; flex-wrap: wrap; }
.section-head h2 { font-size: clamp(22px, 2.6vw, 30px); }
.section-head__aside { color: var(--ink-500); font-size: 14px; max-width: 320px; }
.section--forest .section-head__aside { color: var(--ink-100); }

.grid { display: grid; gap: 18px; }
.grid--2 { grid-template-columns: repeat(2, 1fr); }

/* Homepage's Upcoming Events / Assistance Programs columns — scoped
   override so only this section gets extra breathing room between the two
   columns, without changing the shared .grid--2 gap used by every other
   two-column layout (admin/business/account pages) sitewide. */
.home-events-assistance { column-gap: 64px; }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }
.grid--5 { grid-template-columns: repeat(5, 1fr); }
.grid--6 { grid-template-columns: repeat(6, 1fr); }

@media (max-width: 980px) {
    .grid--3, .grid--4, .grid--5 { grid-template-columns: repeat(2, 1fr); }
    .grid--6 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 640px) {
    .grid--2, .grid--3, .grid--4, .grid--5, .grid--6 { grid-template-columns: 1fr; }
    .section { padding: 56px 0; }
}

.section--gray { background: #f6f7f8; }

/* ---------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 14px 26px;
    border-radius: var(--radius-btn);
    font-weight: 700;
    font-size: 15px;
    border: 1px solid transparent;
    transition: transform var(--transition), background var(--transition), box-shadow var(--transition), color var(--transition);
    white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--coral { background: var(--coral-500); color: var(--white); box-shadow: var(--shadow-sm); }
.btn--coral:hover { background: var(--coral-600); box-shadow: var(--shadow-md); }

.btn--forest { background: var(--forest-700); color: var(--cream-100); }
.btn--forest:hover { background: var(--forest-800); }

/* WhatsApp brand green — used specifically for "WhatsApp" contact buttons
   on listing/event/assistance pages, distinct from the site's own coral
   accent, so it reads instantly as the WhatsApp action. */
.btn--whatsapp { background: #1DA851; color: var(--white); box-shadow: var(--shadow-sm); }
.btn--whatsapp:hover { background: #17863F; box-shadow: var(--shadow-md); }

.btn--outline { background: transparent; border-color: currentColor; color: inherit; }
.btn--outline:hover { background: rgba(255, 255, 255, 0.08); }

.btn--outline-dark { background: transparent; border-color: var(--ink-900); color: var(--ink-900); }
.btn--outline-dark:hover { background: var(--ink-900); color: var(--white); }

.btn--ghost { background: rgba(255,255,255,0.12); color: inherit; }
.btn--ghost:hover { background: rgba(255,255,255,0.2); }

.btn--sm { padding: 9px 18px; font-size: 13px; }
.btn--block { width: 100%; }
.btn[disabled] { opacity: 0.55; cursor: not-allowed; transform: none; }

/* Processing status indicators — sitewide requirement: every action button
   (Front End, Customer/Business, Sales, and Admin portals all share this
   stylesheet) must show unmistakable feedback the instant it's clicked, not
   just a native disabled look. Driven by initProcessingButtons() in main.js,
   which intercepts every form submit site-wide and swaps the clicked
   button's label for a spinner + "Processing…" (or a caller-supplied
   data-processing-text). border-color: currentColor means one spinner works
   on every button variant — coral fill, outline, ghost, icon-btn — without
   needing per-variant colors. */
.icon-btn[disabled] { opacity: 0.55; cursor: not-allowed; pointer-events: none; }

.btn-spinner {
    display: inline-block;
    width: 15px;
    height: 15px;
    flex: none;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    opacity: 0.85;
    vertical-align: -3px;
    animation: btn-spin 0.6s linear infinite;
}
.icon-btn .btn-spinner { width: 13px; height: 13px; margin-right: 5px; }
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* Fallback for the handful of buttonless auto-submit forms (a <select
   onchange="this.form.submit()"> status/role picker) — there's no single
   control to relabel with a spinner, so the whole form dims and locks
   instead. Still an unmistakable "this is working" signal, and it prevents
   a second change firing mid-request. */
.form-is-processing { opacity: 0.6; pointer-events: none; transition: opacity var(--transition); }

/* ---------------------------------------------------------------------
   Admin impersonation banner (client update #15)
   --------------------------------------------------------------------- */
.impersonation-banner {
    /* Client update #16 — was position:relative (scrolls away with the
       page), on the reasoning that the public .site-header was already
       sticky at top:0 so a second sticky element would overlap it. That
       held right up until .dash-topheader (the admin/business dashboard
       top bar) was switched from sticky to position:fixed elsewhere in
       this project — fixed elements are removed from normal flow, so
       this banner (the first element in <body>, right before the header)
       no longer pushed the header down; it just rendered at the exact
       same top:0 spot, and the header painted over it. The banner was
       genuinely present on every dashboard page the whole time, just
       hidden behind the header on all of them except the split second
       right after a fresh page load on a page whose header hadn't
       painted yet — which is what made it look like "only one page" had
       it. Fixed now: the banner itself is fixed at the very top on every
       page, and every top bar that used to start at (or reserve space
       at) y:0 is pushed down to start right below it instead — see the
       body.is-impersonating rules right after this block, and inside the
       .dash-topheader / .dash-sidebar media queries further down. */
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 180;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 10px 16px;
    background: #B45309;
    color: #fff;
    font-size: 13.5px;
    text-align: center;
}
.impersonation-banner__text strong { font-weight: 700; }
.impersonation-banner__form { display: inline-flex; margin: 0; }
.impersonation-banner__btn {
    background: rgba(255,255,255,0.18);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.4);
}
/* Every top bar on the site is position:fixed at the very top of the
   viewport (.dash-topheader, .site-header--public — see that class below,
   now fixed at every width instead of only on mobile/homepage). While
   impersonating, the fixed banner above sits in that spot instead, so each
   of those needs to start exactly one banner-height lower — 55px is this
   banner's real height at a normal single-line width; the 640px query
   below matches the taller two-line height .impersonation-banner already
   switches to at that width (see the max-width:640px rule above
   .impersonation-banner itself). Each selector here intentionally combines
   body.is-impersonating with the target class so it outranks the plain
   single-class rule it's overriding, regardless of which media query
   defines that original. Targeting .site-header--public specifically (not
   the bare .site-header both it and .dash-topheader share) matters:
   .dash-topheader is also a .site-header element, so a bare ".site-header"
   selector here would hit it too and push the dashboard header down by
   110px instead of 55px.
   Client report (2026-08-21, "the front end navigation bar should not
   scroll with the rest of the site") — .site-header--public used to rely
   on position:sticky outside the homepage/mobile special cases, which
   silently stops working in some browsers once an ancestor (html, here)
   has any overflow other than visible (see the .site-header--public
   comment below, and the near-identical note on the mobile-only fix this
   same bug already got a few lines down) — .site-header--public is now
   position:fixed at every width, so a margin-top to reserve its own flow
   space (what the old sticky version needed while impersonating) is no
   longer needed; the top:Npx offset below is all that's required, exactly
   like the homepage case already worked. */
body.is-impersonating .dash-topheader { top: 55px; }
body.is-impersonating .dash-wrap { padding-top: 139px; }
body.is-impersonating .site-header--public { top: 55px; }
/* Same reasoning as .dash-sidebar's own top:84px fix further down this
   file (mobile off-canvas drawer must start below the fixed header, not
   at top:0) — while impersonating, the header itself is pushed down to
   make room for this banner (top:55px above), so the drawer needs to
   start lower still: 139px = banner height + header height, matching
   .dash-wrap's own padding-top for the same case. Unscoped (not inside a
   media query) so it applies to both the mobile drawer (top:84px default,
   ≤980px) and the desktop fixed sidebar (top:84px default, ≥901px) — both
   already want this exact same 139px value while impersonating, so one
   rule covers both without conflicting with the desktop-scoped copy of
   this same override elsewhere in this file. */
body.is-impersonating .dash-sidebar { top: 139px; }
@media (max-width: 640px) {
    body.is-impersonating .dash-topheader { top: 106px; }
    body.is-impersonating .dash-wrap { padding-top: 190px; }
    body.is-impersonating .site-header--public { top: 106px; }
    /* Narrower screens only (this range is always within the ≤980px
       mobile-drawer breakpoint, never the ≥901px desktop one, so this
       can't affect the desktop sidebar) — the impersonation banner grows
       to two lines below 640px (106px header offset instead of 55px), so
       the drawer needs the taller 190px offset to match, same as
       .dash-wrap's padding-top just above. */
    body.is-impersonating .dash-sidebar { top: 190px; }
}
.impersonation-banner__btn:hover { background: rgba(255,255,255,0.3); }
@media (max-width: 640px) {
    .impersonation-banner { flex-direction: column; gap: 8px; padding: 10px 12px; }
}

/* ---------------------------------------------------------------------
   Header / Nav
   --------------------------------------------------------------------- */
.site-header {
    background: var(--forest-800);
    color: var(--cream-100);
    position: sticky;
    top: 0;
    z-index: 50;
}
.site-header .container { display: flex; align-items: center; justify-content: space-between; padding-top: 18px; padding-bottom: 18px; gap: 20px; }

.brand { display: flex; align-items: center; gap: 10px; font-family: var(--font-display); font-weight: 700; font-size: 20px; }
/* Wraps "Accra" + "Find" as a single inline unit so the two-tone wordmark
   always renders as one seamless "AccraFind" word with zero gap between
   them — a bare "Accra<span>Find</span>" relies on there being no
   whitespace in the source between the text node and the span, which is
   easy to reintroduce by accident (e.g. a future edit adding a line break
   between them) and was exactly what caused a visible gap to creep in. */
.brand__word { white-space: nowrap; }
/* Client logo (pin + star mark, transparent PNG) — replaces the old
   mustard-circle "AF" monogram placeholder. object-fit: contain keeps the
   non-square source (a teardrop pin shape) from stretching inside this
   fixed box, on both the dashboard topbar (.brand__mark) and the public
   header (.brand__pin, right below). */
.brand__mark { width: 36px; height: 36px; object-fit: contain; flex: none; }

/* gap tightened from 32px so the 6th item (Organizations) fits comfortably
   on desktop without the nav feeling cramped at laptop widths. */
.main-nav { display: flex; align-items: center; gap: 24px; }
.main-nav a { font-size: 15px; font-weight: 500; opacity: 0.9; transition: opacity var(--transition); }
.main-nav a:hover { opacity: 1; }

.header-actions { display: flex; align-items: center; gap: 14px; }
.header-actions .btn { padding: 11px 20px; }

.nav-toggle { display: none; background: none; border: none; color: var(--cream-100); font-size: 22px; }

@media (max-width: 900px) {
    .main-nav { display: none; }
    .nav-toggle { display: block; }

    /* The hamburger toggles this class on .main-nav via JS (initMobileNav()
       in main.js) — without this rule the button did nothing, since
       .main-nav stayed display:none regardless. Turn it into a full-width
       dropdown panel anchored under the sticky header. */
    .main-nav--open {
        display: flex; flex-direction: column; align-items: flex-start; gap: 0;
        position: absolute; top: 100%; left: 0; right: 0;
        background: var(--forest-800); padding: 8px 24px 18px; box-shadow: var(--shadow-md);
    }
    .main-nav--open a { padding: 12px 0; width: 100%; }
}

/* ---------------------------------------------------------------------
   Public site header (site-header--public) — a separate, scoped white/
   light reskin matching the new homepage concept design. Deliberately
   scoped to this modifier class (not the bare .site-header) so the
   business-owner/sales-rep/admin dashboard top bar in layouts/dashboard.php
   — which reuses the plain .site-header + .brand classes on a dark
   background — is completely unaffected.
   --------------------------------------------------------------------- */
.site-header--public {
    background: var(--white);
    color: var(--ink-900);
    box-shadow: 0 1px 0 rgba(16,31,24,0.07);
    transition: background var(--transition), box-shadow var(--transition);
    /* Client report (2026-08-21, "the front end navigation bar should not
       scroll with the rest of the site") — this used to be position:sticky
       outside the homepage/mobile special cases below, which reserves its
       own space in normal flow and sticks on scroll — correct in theory,
       but position:sticky is well known to silently misbehave in some
       browsers once an ancestor (html, here — see the overflow-x:hidden
       rule near the top of this file, needed to stop wide content dragging
       the page sideways) has any overflow other than visible. Fixed is
       unaffected by that, exactly like the homepage and mobile cases
       already worked below — now every page uses the same, one positioning
       strategy instead of three different ones. Removed from normal flow,
       so body.page-public gets compensating top padding a few lines down;
       initFixedHeaderOffset() in main.js refines that padding to the
       header's exact rendered height (which varies: logged-in vs. guest,
       impersonating or not, and the account row wrapping to two lines on
       narrower screens) instead of a single guessed pixel value. */
    position: fixed; top: 0; left: 0; right: 0; z-index: 100;
}

/* Homepage-only: the header starts fully transparent, floating directly
   over the hero photo (which already fades to white/light at its own
   top edge, so the dark nav text stays legible) — then becomes solid
   white with a shadow once the page is scrolled past the hero, via the
   .is-scrolled class toggled in main.js (initHeaderShadow). Every other
   page keeps the plain solid-white header at all times, since they don't
   have a photo directly behind it. It also floats with zero reserved
   space (see body.page-home:not() exclusion on the padding rule below) —
   letting the hero photo start at the very top of the viewport — since
   position:fixed is now set unconditionally above rather than only here. */
body.page-home .site-header--public { background: transparent; box-shadow: none; }
body.page-home .site-header--public.is-scrolled {
    background: var(--white); box-shadow: 0 4px 16px rgba(16,31,24,0.12);
}
.site-header--public .brand { color: var(--ink-900); }
.site-header--public .brand__pin {
    width: 32px; height: 32px; object-fit: contain; flex: none;
}
.site-header--public .brand__find { color: var(--accent-green); }

.site-header--public .main-nav a {
    font-size: 12px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink-700);
    opacity: 1; padding-bottom: 4px; border-bottom: 2px solid transparent; transition: color var(--transition), border-color var(--transition);
}
.site-header--public .main-nav a:hover { color: var(--ink-900); }
.site-header--public .main-nav a.is-active { color: var(--accent-green); border-bottom-color: var(--accent-green); }

/* The logged-in account row (notification bell, profile/greeting, log out)
   stacks above the Add Listing button + mobile menu toggle instead of
   sharing one long horizontal line with the primary nav — that single row
   was crowding .main-nav and forcing it to compete for width. */
.site-header--public .header-actions { flex-direction: column; align-items: flex-end; gap: 6px; }
.site-header--public .header-actions__account { display: flex; align-items: center; gap: 10px; }
.site-header--public .header-actions__primary { display: flex; align-items: center; gap: 14px; }

.header-icon-btn {
    display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%;
    color: var(--ink-700); font-size: 18px; background: transparent; border: none; transition: background var(--transition);
}
.header-icon-btn:hover { background: var(--cream-300); }
/* Logged-in account link — widens into a pill so "Hi, Name" can sit beside
   the icon, instead of the plain circular icon-only button guests see. */
.header-icon-btn--account {
    width: auto; border-radius: var(--radius-full); padding: 0 14px 0 8px; gap: 6px; font-size: 16px;
}
.header-icon-btn--account__greeting { font-size: 13.5px; font-weight: 600; white-space: nowrap; }
@media (max-width: 640px) {
    .header-icon-btn--account__greeting { display: none; }
}

/* Usability audit (Critical #1) — notification bell dropdown, shared by
   both the public header (.header-icon-btn toggle) and the admin
   dashboard header (.admin-icon-btn toggle, .notif-bell--admin modifier
   for its dark-on-cream admin palette) — see initNotificationBell() in
   main.js for the behaviour this backs. */
.notif-bell { position: relative; }
.notif-bell__toggle { position: relative; }
.notif-bell__badge {
    position: absolute; top: 1px; right: 1px; min-width: 16px; height: 16px; padding: 0 4px;
    border-radius: var(--radius-full); background: var(--coral-500); color: var(--white);
    font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
    box-shadow: 0 0 0 2px var(--white);
}
.notif-bell--admin .notif-bell__badge { box-shadow: 0 0 0 2px var(--admin-white); }
.notif-bell__panel {
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 70; width: 320px; max-width: 88vw;
    background: var(--white); border: 1px solid var(--ink-100); border-radius: var(--radius-md);
    box-shadow: 0 12px 32px rgba(16,31,24,0.16); overflow: hidden;
    /* Both toggle locations sit on dark/light-on-dark headers (the public
       header's dark logged-in row, the admin header's app-chrome strip) —
       without resetting color explicitly here, "Notifications" and every
       item's text silently inherited that ambient near-white color and
       went unreadable against this panel's own white background. */
    color: var(--ink-900);
}
.notif-bell__panel-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 12px 14px; border-bottom: 1px solid var(--ink-100); font-weight: 700; font-size: 13.5px;
}
.notif-bell__mark-all {
    background: none; border: none; color: var(--coral-600); font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 0;
}
.notif-bell__mark-all:hover { text-decoration: underline; }
.notif-bell__list { max-height: 340px; overflow-y: auto; }
.notif-bell__item {
    display: block; width: 100%; text-align: left; background: none; border: none; cursor: pointer;
    padding: 12px 14px; border-bottom: 1px solid var(--ink-100); color: var(--ink-900);
}
.notif-bell__item:last-child { border-bottom: none; }
.notif-bell__item:hover { background: var(--cream-200); }
.notif-bell__item.is-unread { background: rgba(221,107,76,0.06); }
.notif-bell__item.is-unread .notif-bell__item-message { font-weight: 700; }
.notif-bell__item-message { display: block; font-size: 13.5px; line-height: 1.4; }
.notif-bell__item-meta { display: block; margin-top: 4px; font-size: 11.5px; color: var(--ink-500); }
.notif-bell__empty { padding: 20px 14px; text-align: center; color: var(--ink-500); font-size: 13px; }

.site-header--public .nav-toggle { color: var(--ink-900); }

/* .site-header--public is position:fixed at every width now (see that
   class's own base rule above) — removed from normal flow everywhere, so
   body.page-public needs matching top padding at every width to stop page
   content sliding underneath it, not just below this breakpoint. 76px is a
   CSS fallback sized for a guest/logged-out header (no JS, or JS hasn't run
   yet); initFixedHeaderOffset() in main.js measures the header's actual
   rendered height on load/resize and overrides this with the exact value —
   see that function for why a fixed guess alone isn't reliable (logged-in
   vs. guest, impersonating or not, mobile's two-row account stack). The
   homepage is excluded everywhere: its transparent header is meant to
   float directly over the hero photo with zero reserved space, by design. */
body.page-public:not(.page-home) { padding-top: 76px; }
@media (max-width: 900px) {
    .site-header--public .main-nav--open { background: var(--white); max-height: calc(100vh - 76px); overflow-y: auto; }
}

/* On mobile, the home page header floats transparent over the hero photo
   (see body.page-home .site-header--public above), so the plain ☰ glyph
   can disappear against a busy image. Give it a white box there — and only
   there — for visibility; every other page already has a solid white
   header behind it at this width. */
@media (max-width: 900px) {
    body.page-home .site-header--public .nav-toggle {
        display: flex; align-items: center; justify-content: center;
        width: 40px; height: 40px; background: var(--white); color: var(--ink-900);
        border-radius: var(--radius-btn); box-shadow: var(--shadow-sm);
    }
    /* Client report (2026-08-21) — the bell and account/person icons right
       next to the ☰ button above have the exact same problem: no background
       of their own, so on the transparent homepage header they're just bare
       dark line art floating directly on the hero photo, easy to lose
       against a bright patch of sky. Give them the same white-box treatment
       the ☰ button already gets here — each keeps its own shape (a circle
       for the bare bell/login icon, via .header-icon-btn's existing
       border-radius: 50%, or the wider pill the logged-in "Hi, Name"
       version already uses) rather than being forced into the ☰ button's
       rounded-square box. */
    body.page-home .site-header--public .header-icon-btn {
        background: var(--white); color: var(--ink-900); box-shadow: var(--shadow-sm);
    }
}

/* ---------------------------------------------------------------------
   Hero — rebuilt to match the "AccraFind" homepage concept design. The
   supplied hero photo is rendered as a plain <img> at its own native
   aspect ratio (NOT a cropped/stretched CSS background — the client was
   explicit that the photo must show exactly as supplied), so the section's
   height is simply whatever height that image renders at. The headline,
   search bar and trust badge are layered on top via an absolutely
   positioned overlay that doesn't add any extra height of its own.
   Current photo (hero-bg-v5.jpg) is a wide 2.65:1 banner (Independence
   Arch + coastline) that fades from white on the LEFT edge to full colour
   on the right — a different fade direction than the earlier 1.5:1 photo,
   so it's noticeably shorter at every viewport width. --------------------------------------------------------------------- */
.hero { position: relative; color: var(--ink-900); overflow: hidden; background: var(--white); }
.hero__bg { display: block; width: 100%; height: auto; }
/* Scoped to the hero's own container instance only (not the global
   .container helper) so this overlay — and .trust-badge, an absolutely
   positioned child of it — sit on top of the image without affecting
   the section's height, and anchor to the readable content column's
   edges rather than the raw viewport edge on ultra-wide screens. */
/* padding-top is vw-based (not a fixed px) so it scales proportionally
   with the image's own rendered height (which is itself width:100vw-driven
   via .hero__bg) — a fixed px value looked fine on very wide monitors but
   ate a much bigger share of the (shorter) image at laptop widths. Base
   proportion for this photo works out to ~4.5vw (100/2.65 * 0.12); bumped
   up twice per explicit request to sit closer to the vertical middle of
   the (short) photo band instead of hugging its top edge. */
.hero .container { position: absolute; inset: 0; padding-top: 9vw; }
.hero__grid { width: 100%; max-width: 640px; }
/* Restored to its original (pre-density-pass) size per direct user request
   — the sitewide density reduction (smaller headings/spacing everywhere
   else) should NOT apply to this hero specifically. */
.hero__title { font-size: clamp(32px, 4.4vw, 52px); font-weight: 800; line-height: 1.12; margin: 14px 0 16px; color: var(--ink-900); }
.hero__title em { font-style: normal; color: var(--accent-green); font-weight: 800; }
.hero__lede { font-size: 16px; font-weight: 500; color: var(--ink-700); max-width: 460px; margin-bottom: 26px; }

.search-bar {
    display: flex;
    align-items: stretch;
    background: var(--white);
    /* Client request — the pill-shaped (fully-rounded) search bar and its
       Search button read as too rounded; toned down to the same corner
       radius used everywhere else on the site (cards, panels, inputs)
       instead of --radius-full. */
    border-radius: var(--radius-lg);
    padding: 6px;
    box-shadow: var(--shadow-lg);
    gap: 6px;
    max-width: 660px;
}
.search-bar__field { display: flex; align-items: center; gap: 10px; padding: 10px 16px; flex: 1; color: var(--ink-900); min-width: 0; }
/* Fixed narrower column on desktop only — a bare inline flex-basis here would
   silently become a fixed HEIGHT once the mobile media query switches the
   bar to flex-direction:column, leaving a tall blank gap under the field. */
.search-bar__field--area { flex: 0 0 200px; }
.search-bar__field input, .search-bar__field select {
    border: none; outline: none; background: transparent; width: 100%; font-size: 14px; color: var(--ink-900);
}
/* Mobile home-page search bar zoom fix — see the @media (max-width: 640px)
   block near the bottom of this file for why this needs to be 16px there
   specifically (iOS Safari auto-zooms any input under 16px on focus). */
.search-bar__divider { width: 1px; background: var(--ink-100); margin: 8px 0; }
/* Client request (see .search-bar above) — the Search button itself was
   also pill-shaped via this rule; matched to the standard button radius
   every other .btn on the site already uses. */
.search-bar .btn { border-radius: var(--radius-btn); }

/* Usability audit (Minor #8) — the category-page inline search (see
   category/show.php's data-inline-search) used the exact same .search-bar
   look as the homepage hero search, even though it behaves completely
   differently: the homepage version navigates to a results page, this one
   filters right there on the page and never navigates. Gives it a visibly
   different look — a soft tinted background instead of a floating white
   card with a big shadow — so the different behavior has a visual cue. */
.search-bar--inline { background: var(--cream-200); box-shadow: none; border: 1px solid var(--ink-100); }

/* Client request (2026-08-22) — "in these green boxes, the search bar
   should be more round" (category/show.php, category/all.php — both
   sit in a .section--forest hero), while the homepage's own white
   .search-bar (a plain .hero, not .section--forest) stays exactly as
   toned-down as the "too rounded" fix above already made it. Scoped by
   the .section--forest ancestor rather than by .search-bar--inline
   alone, so category/all.php's plain (non---inline) search bar — which
   sits in the same green hero — rounds the same way as show.php's. The
   Search button inside is rounded to match, so it still reads as one
   continuous pill rather than a rounded shell around a square-ish
   button. */
.section--forest .search-bar { border-radius: var(--radius-full); }
.section--forest .search-bar .btn { border-radius: var(--radius-full); }

/* Inline category-hero search results (client request) — dropdown panel
   under the search bar populated by category-search.js's /api/autocomplete
   fetch, so a category page's own search field shows matches right there
   instead of navigating to /discover. See category/show.php and
   category/all.php's [data-inline-search] form. */
.inline-search-results {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    background: var(--white);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    z-index: 20;
}
.inline-search-results__loading,
.inline-search-results__empty {
    padding: 16px;
    font-size: 14px;
    color: var(--ink-500);
}
.inline-search-results__list { display: flex; flex-direction: column; max-height: 320px; overflow-y: auto; }
.inline-search-results__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 12px 16px;
    color: var(--ink-900);
    text-decoration: none;
    border-bottom: 1px solid var(--ink-100);
    transition: background var(--transition);
}
.inline-search-results__item:last-child { border-bottom: none; }
.inline-search-results__item:hover,
.inline-search-results__item:focus { background: var(--cream-100); }
.inline-search-results__name { font-weight: 600; font-size: 14px; }
.inline-search-results__meta { font-size: 12px; color: var(--ink-500); flex-shrink: 0; }

/* Reused by events/index.php's own hero (section--forest) for its date
   preset pill row — kept even though the homepage hero no longer uses it. */
.hero__pill-row { display: flex; align-items: center; gap: 10px; margin-top: 18px; flex-wrap: wrap; font-size: 13px; color: var(--ink-100); }
.pill {
    display: inline-flex; align-items: center; padding: 7px 14px; border-radius: var(--radius-full);
    background: rgba(255,255,255,0.08); font-size: 13px; font-weight: 500; color: var(--cream-100);
    border: 1px solid rgba(255,255,255,0.12); transition: background var(--transition);
}
a.pill:hover, .pill--clickable:hover { background: rgba(255,255,255,0.18); }
.pill--active { background: var(--mustard-500); color: var(--forest-900); border-color: transparent; }

.hero__wave {
    position: absolute; left: 0; right: 0; bottom: -1px; height: 34px; background: var(--mustard-500);
    clip-path: polygon(0 60%, 100% 0, 100% 100%, 0% 100%);
}

@media (max-width: 980px) {
    .search-bar { flex-direction: column; border-radius: var(--radius-md); }
    /* A true pill radius on a vertically-stacked (column-layout) bar reads
       as odd/pinched, not "rounder" — kept at --radius-lg here instead of
       the full pill the desktop rule above uses, same reasoning as the
       general mobile override just above this one. */
    .section--forest .search-bar { border-radius: var(--radius-lg); }
    .search-bar__divider { display: none; }
    .search-bar__field, .search-bar__field--area { flex: 0 0 auto; width: 100%; }
}

/* ---------------------------------------------------------------------
   Trust badge ("Your City. Your Connection.") — floating card positioned
   right next to the search-bar content column rather than pinned to the
   hero's far-right edge. `left` is a fixed px value keyed to the content
   column's own box (.hero__grid's max-width:640px, itself sitting 24px in
   from .hero .container's left edge per the base .container padding) plus
   a small gap — not a percentage — since the column's width is a fixed
   640px cap, not viewport-relative, so this stays snug against it at
   every desktop width instead of drifting. `top` is a PERCENTAGE (not vw/
   px) so it stays proportional to the photo's own rendered height at
   every viewport width — tuned from an actual laptop screenshot so the
   badge's vertical center lines up with the search bar's row.
   --------------------------------------------------------------------- */
.trust-badge {
    position: absolute; left: 700px; top: 63%; z-index: 3; background: rgba(11,27,46,0.88); color: var(--cream-100);
    border-radius: var(--radius-md); padding: 20px 22px; width: 240px; box-shadow: var(--shadow-lg);
    backdrop-filter: blur(6px);
}
.trust-badge__title { font-family: var(--font-display); font-weight: 800; font-size: 16px; margin-bottom: 12px; color: var(--mustard-500); line-height: 1.3; }
.trust-badge__list { display: flex; flex-direction: column; gap: 8px; list-style: none; padding: 0; margin: 0; }
.trust-badge__list li { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--cream-100); font-weight: 600; }
.trust-badge__check {
    display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%;
    background: var(--accent-green); color: var(--white); font-size: 11px; font-weight: 800; flex: none;
}

/* Below ~980px the source photo (native aspect ratio, shown unstretched)
   gets too short to hold the full headline + search bar + badge as an
   overlay without everything colliding — so the overlay drops out of
   absolute positioning and stacks in normal flow below the image instead.
   The image itself is still never cropped or distorted, only where the
   text sits relative to it changes. */
@media (max-width: 980px) {
    .hero .container { position: static; padding: 20px 24px 32px; }
    .trust-badge { position: static; width: 100%; margin-top: 20px; top: auto; left: auto; }
}

/* ---------------------------------------------------------------------
   Hero overlap wrap — the pillar cards + stats row are part of the hero
   cluster (sit directly under the photo), not a separate section further
   down the page. Earlier photos faded to white across their own bottom
   edge, so this used to sit as a normal-flow sibling pulled UP with a
   negative margin to overlap that fade with no visible seam. The current
   photo (hero-bg-v5.jpg) instead fades left-to-right — its bottom-right
   two-thirds is full-colour road/foliage, not white — so overlapping it
   the same way would show a dark strip through this section's own
   (background-less) gaps. Kept as a normal-flow sibling (still safe
   against .hero's overflow:hidden) but only pulled up a small amount, just
   enough to read as "attached" to the photo without cutting into its
   non-white area.
   --------------------------------------------------------------------- */
.hero-overlap {
    position: relative;
    z-index: 2;
    margin-top: clamp(12px, 1.5vw, 28px);
    padding-bottom: 32px;
}

@media (max-width: 980px) {
    .hero-overlap { margin-top: 24px; }
}

/* ---------------------------------------------------------------------
   Pillar cards (Discover / Find / Events / Assistance)
   Layout columns come from the existing .grid.grid--4 helper (which
   already collapses responsively) — these rules only add icon colors
   and card styling on top of it.
   --------------------------------------------------------------------- */
.pillar-card {
    background: var(--white); border-radius: var(--radius-lg); padding: 26px; border: 1px solid var(--ink-100);
    display: flex; flex-direction: column; gap: 14px; position: relative;
    transition: transform var(--transition), box-shadow var(--transition);
}
.pillar-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.pillar-card__icon {
    width: 46px; height: 46px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;
    font-size: 20px; color: var(--white);
}
.pillar-card--discover .pillar-card__icon { background: var(--accent-green); }
.pillar-card--find .pillar-card__icon { background: var(--accent-blue); }
.pillar-card--events .pillar-card__icon { background: var(--accent-purple); }
.pillar-card--assistance .pillar-card__icon { background: var(--accent-orange); }
.pillar-card--organization .pillar-card__icon { background: var(--accent-teal); }
.pillar-card__title { font-family: var(--font-display); font-weight: 700; font-size: 18px; }
.pillar-card__desc { font-size: 14px; color: var(--ink-500); line-height: 1.5; }
.pillar-card__arrow { position: absolute; top: 24px; right: 24px; font-size: 15px; font-weight: 700; transition: transform var(--transition); }
.pillar-card--discover .pillar-card__arrow { color: var(--accent-green); }
.pillar-card--find .pillar-card__arrow { color: var(--accent-blue); }
.pillar-card--events .pillar-card__arrow { color: var(--accent-purple); }
.pillar-card--assistance .pillar-card__arrow { color: var(--accent-orange); }
.pillar-card--organization .pillar-card__arrow { color: var(--accent-teal); }
.pillar-card:hover .pillar-card__arrow { transform: translate(3px, -3px); }

/* Mobile — the generic .grid--N collapse above (640px) drops every
   multi-column grid to 1-per-row, which is right for form fields but
   made these icon/title tiles (homepage Discover/Find/Events/Assistance,
   get-started's role chooser, /advertise's feature + step rows) each take
   a full screen — several screens of scrolling before anything else was
   visible, called out directly by the user. :has() targets only grids
   built from .pillar-card (not the shared .grid--2/3 form-field layouts
   elsewhere) so this doesn't touch anything else using those same
   classes. Description/arrow are hidden at this width — a full sentence
   doesn't fit 3-to-a-row on a phone, so the tile becomes icon + title
   only, same trade-off already made for .popular-cat-grid. */
@media (max-width: 640px) {
    .grid:has(> .pillar-card) { grid-template-columns: repeat(3, 1fr); gap: 8px; }
    .pillar-card { padding: 12px 8px; gap: 6px; align-items: center; text-align: center; }
    .pillar-card__icon { width: 34px; height: 34px; font-size: 15px; }
    .pillar-card__title { font-size: 11.5px; line-height: 1.25; }
    .pillar-card__desc, .pillar-card__arrow { display: none; }
    .step-card__num { width: 26px; height: 26px; font-size: 12px; }
}
@media (max-width: 380px) {
    .grid:has(> .pillar-card) { grid-template-columns: repeat(2, 1fr); }
}

/* Home page only — the 4-card Discover/Find/Events/Assistance row and the
   Popular Categories tile row. Explicit user request: 2 per line on mobile
   here specifically, not the sitewide 3-per-line pillar-card default (which
   still applies elsewhere, e.g. the 6-card get-started chooser). Scoped via
   .home-pillar-grid/.home-category-grid classes added in home/index.php;
   tied specificity with the rules above resolved by source order (this
   block comes later, so it wins). */
@media (max-width: 640px) {
    .grid.home-pillar-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
    .grid.home-category-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
    .home-category-grid .category-tile { padding: 14px 12px; gap: 10px; }
    .home-category-grid .category-tile__icon { font-size: 19px; }
    .home-category-grid .category-tile__label { font-size: 12.5px; }
    /* Businesses & Artisans / Events / Organizations / Programs stats row —
       same "2 on a line" request, extended to this grid too. */
    .grid.home-stats-grid { grid-template-columns: repeat(2, 1fr); row-gap: 20px; column-gap: 14px; }
}

/* ---------------------------------------------------------------------
   Revision #5/#6 — /advertise page: a callout distinguishing "buying an
   ad" from "creating a business listing" (the two were easy to confuse —
   see /advertise), and a numbered step-card row for "How it works",
   reusing .pillar-card's white-card-with-icon-chip shape instead of the
   plain <ol> the page used before.
   --------------------------------------------------------------------- */
.ad-vs-listing {
    display: flex; gap: 20px; align-items: flex-start; background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.14); border-radius: var(--radius-lg); padding: 22px 26px;
}
.ad-vs-listing__icon {
    flex: none; width: 44px; height: 44px; border-radius: 50%; background: var(--mustard-500); color: var(--forest-900);
    display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800;
}
.ad-vs-listing__title { font-family: var(--font-display); font-weight: 700; font-size: 16px; margin-bottom: 4px; color: var(--white); }
.ad-vs-listing__body { font-size: 14.5px; line-height: 1.6; color: var(--ink-100); }
.ad-vs-listing__body a { color: var(--mustard-500); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 640px) {
    .ad-vs-listing { flex-direction: column; }
}

.step-card { position: relative; }
.step-card__num {
    width: 34px; height: 34px; border-radius: 50%; background: var(--coral-500); color: var(--white);
    display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 800; font-family: var(--font-display);
}

/* ---------------------------------------------------------------------
   Stats row (layout columns from .grid.grid--4) — icon to the left,
   label + number stacked to the right, vertical dividers between
   columns, matching the homepage concept exactly.
   --------------------------------------------------------------------- */
.stats-row { text-align: left; }
.stat { display: flex; align-items: center; gap: 12px; padding-left: 4px; border-left: 1px solid var(--ink-100); }
.stat:first-child { border-left: none; padding-left: 0; }
.stat__icon { font-size: 26px; flex: none; line-height: 1; }
.stat__label { font-size: 13px; color: var(--ink-500); }
.stat__num { font-family: var(--font-display); font-weight: 800; font-size: clamp(19px, 2vw, 24px); color: var(--ink-900); margin-top: 2px; }

@media (max-width: 980px) {
    .stat { border-left: none; padding-left: 0; }
}

/* ---------------------------------------------------------------------
   Mood tiles ("What are you in the mood for?")
   --------------------------------------------------------------------- */
.mood-grid { grid-template-columns: repeat(6, 1fr); }
.mood-tile {
    background: var(--white); border-radius: var(--radius-md); padding: 20px;
    border: 1px solid var(--ink-100); transition: transform var(--transition), box-shadow var(--transition), background var(--transition), color var(--transition);
    display: flex; flex-direction: column; gap: 24px; min-height: 148px; position: relative;
}
.mood-tile:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.mood-tile__icon { font-size: 26px; }
.mood-tile__name { font-weight: 700; font-size: 15px; }
.mood-tile__count { font-size: 12px; color: var(--ink-500); }
.mood-tile__arrow { position: absolute; top: 18px; right: 18px; opacity: 0.4; font-size: 13px; }
.mood-tile--active { background: var(--forest-800); color: var(--cream-100); border-color: var(--forest-800); }
.mood-tile--active .mood-tile__count { color: var(--ink-100); }

@media (max-width: 980px) { .mood-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .mood-grid { grid-template-columns: repeat(2, 1fr); } }

/* ---------------------------------------------------------------------
   Listing cards
   --------------------------------------------------------------------- */
.listing-grid { grid-template-columns: repeat(4, 1fr); }
.listing-card {
    background: var(--white); border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--shadow-sm);
    border: 1px solid var(--ink-100); transition: transform var(--transition), box-shadow var(--transition);
    display: flex; flex-direction: column; height: 100%;
}
.listing-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.listing-card__media { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: var(--ink-100); }
.listing-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 400ms ease; }
.listing-card:hover .listing-card__media img { transform: scale(1.04); }
.listing-card__badge {
    position: absolute; top: 12px; left: 12px; background: var(--white); color: var(--ink-900); font-size: 11px;
    font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; padding: 5px 10px; border-radius: var(--radius-full);
}
.listing-card__fav {
    position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; border-radius: 50%; background: rgba(255,255,255,0.92);
    display: flex; align-items: center; justify-content: center; border: none; font-size: 16px; color: var(--coral-500);
    transition: transform var(--transition);
}
.listing-card__fav:hover { transform: scale(1.08); }
.listing-card__fav.is-active { background: var(--coral-500); color: var(--white); }
.listing-card__body { padding: 14px 14px 16px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.listing-card__top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.listing-card__category { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--coral-500); }
.listing-card__rating { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.listing-card__rating .stars { color: var(--mustard-500); letter-spacing: -1px; font-size: 12px; }
.listing-card__title { font-family: var(--font-display); font-size: 19px; font-weight: 700; }
.listing-card__desc { font-size: 13.5px; color: var(--ink-500); flex: 1; }
.listing-card__foot { display: flex; justify-content: space-between; align-items: center; font-size: 12.5px; color: var(--ink-500); margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--ink-100); }
.listing-card__price { font-weight: 700; color: var(--ink-900); }

@media (max-width: 1240px) { .listing-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 980px) { .listing-grid { grid-template-columns: repeat(2, 1fr); } }
/* Mobile — two listings per row rather than one full-width card per row,
   so a phone screen shows more of the results at once without feeling like
   an endless single-column scroll. Card internals shrink a bit to stay
   legible at roughly half a phone's width. */
@media (max-width: 640px) {
    .listing-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
    .listing-card__body { padding: 10px 10px 12px; gap: 4px; }
    .listing-card__title { font-size: 15px; }
    .listing-card__category { font-size: 10px; }
    .listing-card__desc { font-size: 12px; }
    .listing-card__rating { font-size: 12px; }
    .listing-card__foot { font-size: 11px; margin-top: 6px; padding-top: 8px; }
    .listing-card__badge { font-size: 10px; padding: 4px 8px; top: 8px; left: 8px; }
    .listing-card__fav { width: 28px; height: 28px; font-size: 13px; top: 6px; right: 6px; }
    /* Homepage "Featured this week" reuses .listing-card markup inside a
       bare .grid--5 (shared with unrelated grids like advertise.php's step
       list, hence the dedicated .home-featured-grid hook here rather than
       touching .grid--5's own mobile rule) — same two-per-row treatment. */
    .home-featured-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
}

.reset-link { font-size: 13px; font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }

/* "VERIFIED" badge — green, top-right, used on the homepage Featured
   Listings strip (see .listing-card__badge above for the default
   white/top-left variant used elsewhere). */
.listing-card__badge--verified {
    left: auto; right: 12px; background: var(--accent-green); color: var(--white);
}

/* listing_card_html()/cardHtml() (search, category, neighbourhood,
   favorites — everywhere the shared card includes a .listing-card__fav
   heart button) add this alongside --verified to drop the badge below the
   heart instead of the top-right corner, since the homepage's own
   top-right position (above) would otherwise sit directly under the fav
   button there and the two would overlap. The homepage's Featured strip
   has no fav button, so it keeps the plain top-right --verified position. */
.listing-card__badge--stacked { top: 50px; }

/* ---------------------------------------------------------------------
   Popular Categories tiles (homepage) — light icon-only cards, distinct
   from the colored-square pillar cards above them.
   --------------------------------------------------------------------- */
.category-tile {
    background: var(--white); border-radius: var(--radius-md); padding: 18px 16px;
    border: 1px solid var(--ink-100); display: flex; align-items: center; gap: 12px;
    transition: transform var(--transition), box-shadow var(--transition);
}
.category-tile:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.category-tile__icon { font-size: 22px; flex: none; }
.category-tile__label { font-weight: 700; font-size: 14px; line-height: 1.25; color: var(--ink-900); }

/* ---------------------------------------------------------------------
   Upcoming Events + Assistance Programs (homepage two-column section)
   --------------------------------------------------------------------- */
.event-row {
    display: flex; align-items: center; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--ink-100);
}
.event-row:last-child { border-bottom: none; }
.event-row__date {
    flex: none; width: 54px; height: 54px; border-radius: var(--radius-md); border: 1px solid var(--ink-100);
    display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
}
.event-row__date-month { font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent-green); }
.event-row__date-day { font-size: 17px; font-weight: 800; color: var(--ink-900); line-height: 1.1; }
.event-row__thumb { flex: none; width: 64px; height: 54px; border-radius: var(--radius-sm); overflow: hidden; background: var(--ink-100); }
.event-row__thumb img { width: 100%; height: 100%; object-fit: cover; }
.event-row__body { flex: 1; min-width: 0; }
.event-row__title { font-weight: 700; font-size: 14.5px; color: var(--ink-900); }
.event-row__meta { font-size: 12.5px; color: var(--ink-500); margin-top: 2px; }
.event-row__bookmark { flex: none; font-size: 16px; color: var(--ink-300); }

.program-row {
    display: flex; align-items: center; gap: 14px; padding: 16px; border: 1px solid var(--ink-100); border-radius: var(--radius-md);
    margin-bottom: 12px; background: var(--white);
}
.program-row:last-child { margin-bottom: 0; }
.program-row__icon {
    flex: none; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px;
}
.program-row__title { font-weight: 700; font-size: 14.5px; color: var(--ink-900); }
.program-row__org { font-size: 12.5px; color: var(--ink-500); margin-top: 1px; }
.program-row__desc { font-size: 12.5px; color: var(--ink-500); margin-top: 2px; }

.view-all-btn { display: block; width: 100%; text-align: center; padding: 12px; border-radius: var(--radius-md); border: 1px solid var(--accent-green); color: var(--accent-green); font-weight: 700; font-size: 14px; margin-top: 16px; transition: background var(--transition); }
.view-all-btn:hover { background: rgba(47,158,92,0.08); }

/* ---------------------------------------------------------------------
   "Be Part of a Stronger Accra" CTA banner
   --------------------------------------------------------------------- */
.cta-banner {
    position: relative; overflow: hidden; border-radius: var(--radius-lg);
    background: linear-gradient(120deg, var(--forest-700) 0%, var(--accent-green) 100%);
    color: var(--white); padding: 40px 44px;
    display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap;
}
.cta-banner__watermark {
    position: absolute; right: 24px; bottom: -10px; font-size: 130px; opacity: 0.12; line-height: 1; pointer-events: none;
}
.cta-banner h2 { font-size: clamp(20px, 2.4vw, 26px); margin-bottom: 6px; }
.cta-banner p { font-size: 15px; opacity: 0.92; max-width: 480px; }
.cta-banner__actions { display: flex; gap: 12px; flex-wrap: wrap; position: relative; z-index: 1; }
.cta-banner .btn--white { background: var(--white); color: var(--forest-800); }
.cta-banner .btn--outline-white { background: transparent; color: var(--white); border: 1px solid rgba(255,255,255,0.6); }
.cta-banner .btn--outline-white:hover { background: rgba(255,255,255,0.12); }

@media (max-width: 640px) {
    .cta-banner { padding: 32px 24px; }
}

/* ---------------------------------------------------------------------
   Neighbourhoods
   --------------------------------------------------------------------- */
.neighbourhood-split { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: center; }
.neighbourhood-split h2 { font-size: clamp(22px, 2.6vw, 30px); margin-bottom: 10px; }
.neighbourhood-split p { color: var(--ink-500); max-width: 440px; margin-bottom: 16px; }
.chip-row { display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
    padding: 9px 18px; border-radius: var(--radius-full); border: 1px solid var(--ink-100); font-size: 14px; font-weight: 600;
    transition: all var(--transition); background: var(--white);
}
.chip:hover, .chip.is-active { background: var(--forest-800); color: var(--cream-100); border-color: var(--forest-800); }

/* Category pages with a large subtree (category/show.php's $useChipFilter,
   e.g. "Electrical, Plumbing and Technical Services" ~57 professions) swap
   the plain .chip-row for this: a search box plus a collapsed list — see
   initCategoryChipFilter() in main.js. Sits on the same dark forest hero
   as the plain chip row, so it borrows that section's translucent-white
   treatment rather than a light input box that would fight the background. */
.category-filter { margin-top: 22px; }
.category-filter__search {
    display: flex; align-items: center; gap: 10px; max-width: 440px;
    background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.28);
    border-radius: var(--radius-full); padding: 11px 20px; transition: border-color var(--transition);
}
.category-filter__search:focus-within { border-color: var(--mustard-500); }
.category-filter__input { flex: 1; background: none; border: none; color: var(--cream-100); font-size: 14.5px; font-family: inherit; }
.category-filter__input:focus { outline: none; }
.category-filter__input::placeholder { color: var(--ink-100); opacity: .75; }
.category-filter__toggle {
    display: inline-block; margin-top: 14px; background: none; border: none; padding: 0;
    color: var(--mustard-500); font-weight: 700; font-size: 13.5px; cursor: pointer;
}
.category-filter__toggle:hover { text-decoration: underline; }
.category-filter__empty { margin-top: 14px; color: var(--ink-100); font-size: 14px; }

/* "View All Categories" directory (category/all.php, linked from the
   Business/Find a Pro root hero's popular-categories picker below) —
   light-panel cards on the cream section background, unlike
   .category-filter above which is themed for the dark hero. */
.category-directory__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.category-directory__group { background: var(--white); border: 1px solid var(--ink-100); border-radius: var(--radius-md); padding: 16px 20px; }
.category-directory__group summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; cursor: pointer; list-style: none; font-weight: 700; }
.category-directory__group summary::-webkit-details-marker { display: none; }
.category-directory__group summary a { color: var(--ink-900); font-size: 15px; }
.category-directory__group summary a:hover { color: var(--coral-500); }
.category-directory__count { font-size: 12px; font-weight: 700; color: var(--ink-500); background: var(--cream-100); border-radius: var(--radius-full); padding: 2px 9px; flex-shrink: 0; }
.category-directory__children { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--ink-100); }
.category-directory__child { font-size: 13px; color: var(--ink-500); padding: 5px 12px; border-radius: var(--radius-full); border: 1px solid var(--ink-100); transition: all var(--transition); }
.category-directory__child:hover { background: var(--forest-800); color: var(--cream-100); border-color: var(--forest-800); }

/* Popular-categories hero picker (category/show.php, root pages only) —
   a curated card grid plus a "View all" link, replacing a full chip wall
   of every master category (37 for Business, 16 for Find a Pro). */
.popular-cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; margin-top: 22px; }
.popular-cat-card {
    display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.28);
    border-radius: var(--radius-md); padding: 14px 16px; transition: all var(--transition); color: var(--cream-100);
}
.popular-cat-card:hover { background: rgba(255,255,255,0.16); border-color: var(--mustard-500); }
.popular-cat-card__icon { font-size: 20px; }
.popular-cat-card__name { font-size: 14.5px; font-weight: 700; }
.popular-cat-card__count { font-size: 12px; color: var(--ink-100); opacity: .8; }
.popular-cat-viewall { display: inline-block; margin-top: 18px; color: var(--mustard-500); font-weight: 700; font-size: 13.5px; }
.popular-cat-viewall:hover { text-decoration: underline; }

/* Mobile — auto-fill's 200px minimum still only fit 1 column on a phone
   (390px wide minus container padding leaves less than 400px), stacking
   all 8 tiles full-width and pushing the real results grid several
   screens down before a visitor sees anything to click or scroll past.
   Fixed 3-per-row grid instead — same number of screens' worth of
   scrolling either way, but 3 tiles visible together reads as "here's
   the menu" instead of one giant list (a horizontal swipe row was tried
   here first and rejected — sideways scrolling on mobile category tiles
   wasn't wanted). Cards stack icon/name/count vertically and center
   themselves so 3 fit comfortably across a ~390px screen. */
@media (max-width: 560px) {
    .popular-cat-grid { grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 16px; }
    .popular-cat-card { flex-direction: column; align-items: center; text-align: center; padding: 12px 6px; gap: 5px; }
    .popular-cat-card__icon { font-size: 19px; }
    .popular-cat-card__name { font-size: 11.5px; line-height: 1.25; }
    .popular-cat-card__count { font-size: 10px; }
}

@media (max-width: 380px) {
    .popular-cat-grid { grid-template-columns: repeat(2, 1fr); }
}

.map-graphic { position: relative; height: 380px; display: flex; align-items: center; justify-content: center; }
.map-graphic svg { width: 100%; height: 100%; }
.map-pin {
    position: absolute; background: var(--coral-500); color: var(--white); font-size: 12px; font-weight: 700;
    padding: 6px 12px; border-radius: var(--radius-full); box-shadow: var(--shadow-md); white-space: nowrap;
}
.map-pin--alt { background: var(--forest-800); }

@media (max-width: 900px) { .neighbourhood-split { grid-template-columns: 1fr; } .map-graphic { height: 260px; } }

/* ---------------------------------------------------------------------
   Pricing
   --------------------------------------------------------------------- */
.pricing-grid { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
.pricing-card {
    background: var(--cream-100); color: var(--ink-900); border-radius: var(--radius-lg); padding: 32px 28px;
    display: flex; flex-direction: column; gap: 18px; position: relative; border: 1px solid rgba(255,255,255,0.06);
}
.pricing-card--featured { background: var(--white); box-shadow: var(--shadow-lg); transform: translateY(-8px); border: 2px solid var(--coral-500); }
.pricing-card__badge {
    position: absolute; top: -14px; right: 24px; background: var(--mustard-500); color: var(--forest-900);
    font-size: 11px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; padding: 6px 14px; border-radius: var(--radius-full);
}
.pricing-card__name { font-family: var(--font-display); font-size: 22px; font-weight: 700; }
.pricing-card__tagline { font-size: 13.5px; color: var(--ink-500); }
.pricing-card__price { font-size: 40px; font-weight: 800; font-family: var(--font-display); }
.pricing-card__price .pricing-card__period { font-size: 14px; font-weight: 500; color: var(--ink-500); font-family: var(--font-body); margin-left: 2px; }

.billing-interval-toggle { display: inline-flex; gap: 4px; background: rgba(255,255,255,0.08); padding: 4px; border-radius: var(--radius-sm); }
.billing-interval-toggle__opt { border: none; background: transparent; padding: 9px 16px; border-radius: calc(var(--radius-sm) - 2px); font-size: 14px; font-weight: 600; color: var(--cream-100); cursor: pointer; }
.billing-interval-toggle__opt.is-active { background: var(--mustard-500); color: var(--forest-900); }
.billing-interval-toggle__save { font-size: 11px; font-weight: 600; opacity: .75; margin-left: 4px; }

/* On light pricing-card / plan-pick-card backgrounds the default light-on-dark
   toggle styling would be invisible — flip to dark text on a pale field. */
.billing-interval-toggle--card { background: var(--ink-100); flex-wrap: wrap; }
.billing-interval-toggle--card .billing-interval-toggle__opt { color: var(--ink-700); font-size: 12.5px; padding: 7px 11px; }
.billing-interval-toggle--card .billing-interval-toggle__opt.is-active { background: var(--forest-800); color: var(--cream-100); }

/* Client update #2 — promotional pricing badge/message + struck-through
   "was" price, used on both the public pricing page and the business
   dashboard's own checkout page (same markup/classes on both). */
.pricing-card__promo {
    background: #fff4e0; border: 1px solid #f0c744; border-radius: var(--radius-sm);
    padding: 10px 12px; display: flex; flex-direction: column; gap: 4px;
}
.pricing-card__promo-badge {
    display: inline-block; align-self: flex-start; background: var(--coral-500); color: var(--white);
    font-size: 10.5px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
    padding: 4px 10px; border-radius: var(--radius-full);
}
.pricing-card__promo-msg { font-size: 12.5px; color: #5c4a12; line-height: 1.5; margin: 0; }
.pricing-card__amount-original {
    font-size: 18px; font-weight: 600; color: var(--ink-500); text-decoration: line-through;
    margin-right: 8px; font-family: var(--font-body);
}
.pricing-card__features { display: flex; flex-direction: column; gap: 10px; font-size: 14px; padding: 18px 0; border-top: 1px solid var(--ink-100); flex: 1; }
.pricing-card__features li { display: flex; gap: 8px; align-items: flex-start; }
.pricing-card__features li::before { content: '✓'; color: var(--coral-500); font-weight: 800; }

@media (max-width: 980px) { .pricing-grid { grid-template-columns: 1fr; } .pricing-card--featured { transform: none; } }

/* ---------------------------------------------------------------------
   CTA band
   --------------------------------------------------------------------- */
.cta-split { display: flex; align-items: center; justify-content: space-between; gap: 40px; flex-wrap: wrap; }
.cta-split h2 { font-size: clamp(21px, 2.5vw, 28px); max-width: 480px; }
.cta-split p { max-width: 420px; opacity: 0.92; margin-top: 8px; }

/* ---------------------------------------------------------------------
   Footer — dark navy, 4-column layout matching the homepage concept
   design (Quick Links / For Users / For Businesses / Contact Us),
   replacing the previous single-row forest-green footer.
   --------------------------------------------------------------------- */
.site-footer { background: var(--navy-900); color: var(--cream-100); padding: 56px 0 24px; }
.site-footer .brand { color: var(--white); }
.site-footer .brand__find { color: var(--accent-green); }

/* Client request (2026-08-22) — footer's "Contact Us" column (a raw
   mailto: email address) was removed; the /contact form link stays under
   "For Users" and "For Businesses". Down to 4 columns from 5. */
.footer-top { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 32px; padding-bottom: 36px; }
.footer-about__tagline { font-size: 13.5px; color: var(--ink-300); margin: 14px 0 18px; max-width: 260px; line-height: 1.5; }
.footer-social { display: flex; gap: 10px; }
.footer-social a {
    width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.25); color: var(--cream-100);
    display: flex; align-items: center; justify-content: center; font-size: 14px; transition: background var(--transition), border-color var(--transition);
}
.footer-social a:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.5); }

.footer-col h4 { font-size: 14px; font-weight: 700; color: var(--white); margin-bottom: 16px; }
.footer-col ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 11px; }
.footer-col a { font-size: 13.5px; color: var(--ink-300); transition: color var(--transition); }
.footer-col a:hover { color: var(--mustard-500); }
.footer-col li { font-size: 13.5px; color: var(--ink-300); display: flex; align-items: flex-start; gap: 8px; }

.footer-bottom {
    display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 12px;
    padding-top: 22px; border-top: 1px solid rgba(255,255,255,0.1); font-size: 13px; color: var(--ink-300); text-align: center;
}
.footer-legal__owner { font-size: 12px; color: var(--ink-300); opacity: 0.8; }
.footer-legal__links { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 12px; color: var(--ink-300); }
.footer-legal__links a:hover { color: var(--mustard-500); }

@media (max-width: 980px) {
    .footer-top { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
    .footer-top { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------
   Forms (auth, contact, dashboard, reviews)
   --------------------------------------------------------------------- */
/* min-height uses both 100vh and 100dvh: vh is a fixed fallback for older
   browsers, dvh ("dynamic viewport height") tracks the *current* visible
   viewport on mobile, where the browser's address bar hides/shows as you
   scroll. With only vh, this box's height snaps taller/shorter every time
   the address bar toggles mid-scroll, which feels like the page jumping.
   Browsers that don't know dvh ignore that line and keep the vh value. */
.form-page { display: flex; min-height: calc(100vh - 76px); min-height: calc(100dvh - 76px); align-items: center; justify-content: center; padding: 56px 24px; background: var(--cream-200); }
.form-card { background: var(--white); border-radius: var(--radius-lg); padding: 32px; width: 100%; max-width: 460px; box-shadow: var(--shadow-md); }
.form-card h1 { font-size: 23px; margin-bottom: 6px; }
.form-card__sub { color: var(--ink-500); font-size: 14.5px; margin-bottom: 28px; }

.field { margin-bottom: 18px; }
.field label { display: block; font-size: 13.5px; font-weight: 600; margin-bottom: 7px; }
.field input, .field select, .field textarea {
    width: 100%; padding: 12px 15px; border-radius: var(--radius-sm); border: 1.5px solid var(--ink-100);
    outline: none; transition: border-color var(--transition); background: var(--white); font-size: 14.5px;
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--coral-500); }
.field--checkbox { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.field--checkbox input { width: auto; }
.field-error { color: var(--coral-600); font-size: 12.5px; margin-top: 5px; }

/* Payment-portal human check (2026-09-04) — see human_check_question()/
   human_check_input() (app/Core/helpers.php) and Controller::verifyHuman().
   .human-check-hp is the honeypot field: moved off-screen rather than
   display:none/visibility:hidden, since some bots specifically skip those
   two but still blindly fill in anything else — a real visitor never
   sees or reaches it (aria-hidden + tabindex="-1" keep it out of screen
   readers and keyboard tabbing too), so any value in it on submit means
   whatever posted the form didn't render it like a browser does. */
.human-check-hp { position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; overflow: hidden; }
.human-check-question { background: var(--cream-200); border-radius: var(--radius-sm); padding: 12px 14px; font-size: 13.5px; color: var(--ink-700); margin-bottom: 14px; }
.human-check-answer { max-width: 160px; }
.field-hint { color: var(--ink-500); font-size: 12.5px; margin-top: 5px; }
/* Usability audit (Should Improve #11) — no form on the site marked which
   fields were required until you tried to submit and hit the browser's own
   validation tooltip. A small red asterisk next to the label, plus a
   one-line "* Required" note at the top of longer forms, now says so
   upfront — added only where the underlying field already carries the
   `required` HTML attribute, nothing here changes what's actually enforced. */
.required-mark { color: var(--coral-600); font-weight: 700; }

/* Migration 0043 — "custom URL" field (Listing info / admin business
   Profile). A static, non-editable domain prefix glued to the left of the
   real <input name="slug">, so the field reads as "accrafind.com/place/
   [your text]" instead of asking an owner to retype the whole URL. The
   input keeps matching the generic `.field input` selector above (and
   every role-scoped override of it) since it's still nested inside a
   .field — only its own corners are squared off on the left to sit flush
   against the prefix. */
.field-url-group { display: flex; align-items: stretch; }
.field-url-group__prefix {
    display: flex; align-items: center; padding: 0 12px; white-space: nowrap;
    background: var(--ink-100); border: 1.5px solid var(--ink-100); border-right: none;
    border-radius: var(--radius-sm) 0 0 var(--radius-sm); color: var(--ink-500); font-size: 13.5px;
}
.field-url-group input { border-radius: 0 var(--radius-sm) var(--radius-sm) 0 !important; }

/* "Add up to 3 more categories" picker (migration 0032, partials/secondary-
   categories-picker.php) — grouped by master category in collapsible
   <details>, since the full Business taxonomy is 464 leaves. Light-panel
   styling to match the surrounding .dash-panel form, unlike .category-filter
   above which is themed for the dark hero. */
.secondary-cat-picker__hint { font-size: 13.5px; color: var(--ink-500); margin-bottom: 6px; }
.secondary-cat-picker__count { font-size: 13px; font-weight: 700; color: var(--forest-700); margin-bottom: 14px; }
.secondary-cat-picker__groups { display: flex; flex-direction: column; gap: 8px; max-height: 420px; overflow-y: auto; border: 1px solid var(--ink-100); border-radius: var(--radius-sm); padding: 6px 14px; }
.secondary-cat-picker__group { border-bottom: 1px solid var(--ink-100); padding: 10px 0; }
.secondary-cat-picker__group:last-child { border-bottom: none; }
.secondary-cat-picker__group summary { cursor: pointer; font-size: 14px; font-weight: 700; list-style: none; }
.secondary-cat-picker__group summary::-webkit-details-marker { display: none; }
.secondary-cat-picker__group summary::before { content: '▸ '; color: var(--ink-500); }
.secondary-cat-picker__group[open] summary::before { content: '▾ '; }
.secondary-cat-picker__options { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px 16px; margin-top: 10px; }
.secondary-cat-picker__options .field--checkbox input:disabled + * { opacity: .5; }
.secondary-cat-picker__options .field--checkbox:has(input:disabled) { opacity: .5; }

.form-alert { padding: 13px 16px; border-radius: var(--radius-sm); font-size: 14px; margin-bottom: 20px; }
.form-alert--error { background: #fbe9e5; color: var(--coral-600); }
.form-alert--success { background: #e6f2ea; color: #276b47; }

.form-card__foot { text-align: center; margin-top: 20px; font-size: 14px; color: var(--ink-500); }
.form-card__foot a { font-weight: 700; color: var(--coral-500); }

.role-switch { display: flex; gap: 10px; margin-bottom: 26px; }
.role-switch a { flex: 1; text-align: center; padding: 10px; border-radius: var(--radius-sm); border: 1.5px solid var(--ink-100); font-size: 13.5px; font-weight: 600; }
.role-switch a.is-active { background: var(--forest-800); color: var(--cream-100); border-color: var(--forest-800); }

/* Shared "Step X of Y" progress bar for every staged registration wizard
   (Business, Artisan, Event, Assistance Program) — see
   app/Views/partials/wizard-steps.php. A row of numbered dots connected by
   a line; the current step is highlighted, completed steps get a
   checkmark and a filled connector, upcoming steps stay muted. */
.wizard-steps { display: flex; align-items: flex-start; margin-bottom: 28px; }
.wizard-steps__step { display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 0 0 auto; width: 84px; text-align: center; }
.wizard-steps__dot {
    width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    font-size: 13px; font-weight: 700; background: var(--cream-200); color: var(--ink-500); border: 1.5px solid var(--ink-100);
    flex-shrink: 0;
}
.wizard-steps__label { font-size: 11px; font-weight: 600; color: var(--ink-500); line-height: 1.25; }
.wizard-steps__step.is-current .wizard-steps__dot { background: var(--coral-500); border-color: var(--coral-500); color: var(--white); }
.wizard-steps__step.is-current .wizard-steps__label { color: var(--ink-900, #1c1c1c); }
.wizard-steps__step.is-done .wizard-steps__dot { background: var(--forest-800); border-color: var(--forest-800); color: var(--cream-100); }
.wizard-steps__line { flex: 1 1 auto; height: 2px; background: var(--ink-100); margin-top: 15px; min-width: 16px; }
.wizard-steps__line.is-done { background: var(--forest-800); }
@media (max-width: 480px) {
    .wizard-steps__step { width: 64px; }
    .wizard-steps__label { font-size: 10px; }
}

/* ---------------------------------------------------------------------
   Search / discovery page
   --------------------------------------------------------------------- */
.search-layout { display: grid; grid-template-columns: 280px 1fr; gap: 28px; align-items: flex-start; }
.filters-panel {
    background: var(--white); border: 1px solid var(--ink-100); border-radius: var(--radius-lg); padding: 18px 16px;
    position: sticky; top: 96px;
}
.filters-panel__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.filters-panel__head h2 { font-size: 15.5px; font-weight: 800; letter-spacing: -0.01em; }
.filters-panel__clear { font-size: 12px; font-weight: 700; color: var(--coral-500); background: none; border: none; cursor: pointer; padding: 4px 0; }
.filters-panel__clear:hover { text-decoration: underline; }

.filters-panel h3 {
    font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.07em; font-weight: 800;
    margin: 14px 0 8px; color: var(--ink-900);
}
.filters-panel h3:first-of-type { margin-top: 10px; }
.filter-section { padding-top: 12px; border-top: 1px solid var(--ink-100); }
.filter-section:first-of-type { border-top: none; padding-top: 0; }

.filter-group { display: flex; flex-direction: column; gap: 2px; }
.filter-check {
    display: flex; align-items: center; gap: 10px; font-size: 14px; cursor: pointer;
    padding: 8px 10px; border-radius: var(--radius-sm); margin: 0 -10px; transition: background var(--transition);
}
.filter-check:hover { background: var(--cream-200); }
.filter-check:has(input:checked) { background: rgba(221,107,76,0.09); font-weight: 600; color: var(--coral-600); }
.filter-check input { width: 17px; height: 17px; accent-color: var(--coral-500); flex-shrink: 0; }

/* Price filter head ("Price range" + small "Clear" link) — shared by the
   dual-handle slider below it (see .price-slider__* further down and
   search.js for the drag/live-count logic). */
.price-tier-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.price-tier-head h3 { margin: 0; }
.price-tier-clear {
    font-size: 12.5px; font-weight: 600; color: var(--coral-600, #dd6b4c); background: none; border: none; padding: 2px 0; cursor: pointer;
}
.price-tier-clear:hover { text-decoration: underline; }

/* Dual-handle price range slider — replaced the 4-row radio list per direct
   user feedback ("let's just use an adjustable bar range"). Two overlapping
   <input type=range> (see data-price-slider-min/max in search/results.php +
   search.js) share one track; each input is transparent except its own
   thumb (pointer-events: none on the input, auto on ::-*-slider-thumb) so
   both handles are independently draggable. The underlying filter is still
   the existing 1-4 businesses.price_range tier system — the slider just
   picks a [min tier, max tier] range instead of a single tier, so no
   backend/schema changes were needed. */
.price-slider__value { font-weight: 700; font-size: 13.5px; color: var(--ink-900); margin-bottom: 14px; }
.price-slider__track { position: relative; height: 4px; background: var(--ink-100); border-radius: var(--radius-full); margin: 0 8px; }
.price-slider__fill { position: absolute; top: 0; height: 100%; background: var(--accent-green); border-radius: var(--radius-full); }
.price-slider__input {
    position: absolute; top: 50%; left: -8px; width: calc(100% + 16px); margin: 0; transform: translateY(-50%);
    -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none; height: 18px;
}
.price-slider__input::-webkit-slider-runnable-track { background: transparent; -webkit-appearance: none; height: 18px; }
.price-slider__input::-moz-range-track { background: transparent; height: 18px; border: none; }
.price-slider__input::-webkit-slider-thumb {
    -webkit-appearance: none; pointer-events: auto; width: 17px; height: 17px; border-radius: 50%;
    background: var(--white); border: 2.5px solid var(--accent-green); box-shadow: var(--shadow-sm); cursor: pointer; margin-top: 0;
}
.price-slider__input::-moz-range-thumb {
    pointer-events: auto; width: 17px; height: 17px; border-radius: 50%;
    background: var(--white); border: 2.5px solid var(--accent-green); box-shadow: var(--shadow-sm); cursor: pointer;
}
.price-slider__ticks { display: flex; justify-content: space-between; margin: 10px 0 12px; font-size: 10px; color: var(--ink-500); }
.price-slider__count { font-size: 12.5px; color: var(--ink-500); }

.rating-filter { display: flex; flex-direction: column; gap: 2px; }
.rating-filter label {
    display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer;
    padding: 8px 10px; border-radius: var(--radius-sm); margin: 0 -10px; transition: background var(--transition);
}
.rating-filter label:hover { background: var(--cream-200); }
.rating-filter label:has(input:checked) { background: rgba(221,107,76,0.09); font-weight: 600; }
.rating-filter .stars { color: var(--mustard-500); letter-spacing: 1px; }

/* Collapsible filter groups (native <details>, zero JS) — keeps the long
   amenity/hours/payment checklists tidy instead of one long childish wall
   of checkboxes. Open by default so nothing feels hidden. */
.filter-accordion { border-top: 1px solid var(--ink-100); padding-top: 4px; margin-top: 18px; }
.filter-accordion:first-of-type { border-top: none; margin-top: 0; padding-top: 0; }
.filter-accordion summary {
    list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between;
    font-size: 12px; text-transform: uppercase; letter-spacing: 0.07em; font-weight: 800; color: var(--ink-900);
    padding: 14px 0 12px;
}
.filter-accordion summary::-webkit-details-marker { display: none; }
.filter-accordion summary::after { content: '\2303'; transform: rotate(180deg); color: var(--ink-500); font-size: 12px; transition: transform var(--transition); }
.filter-accordion[open] summary::after { transform: rotate(0deg); }

.search-results-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 22px; gap: 16px; flex-wrap: wrap; }
.search-results-head h1 { font-size: 24px; }
.sort-select select { padding: 10px 14px; border-radius: var(--radius-sm); border: 1.5px solid var(--ink-100); font-size: 14px; }
.active-filter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
.active-filter-chip {
    display: inline-flex; align-items: center; gap: 6px; background: var(--cream-300); padding: 6px 12px;
    border-radius: var(--radius-full); font-size: 13px; font-weight: 600;
}
.active-filter-chip button { border: none; background: none; color: var(--ink-500); font-size: 14px; }

.pagination { display: flex; justify-content: center; gap: 8px; margin-top: 40px; }
.pagination a, .pagination span {
    width: 38px; height: 38px; display: flex; align-items: center; justify-content: center; border-radius: 50%;
    border: 1.5px solid var(--ink-100); font-size: 14px; font-weight: 600;
}
.pagination a:hover { background: var(--cream-300); }
.pagination .is-active { background: var(--forest-800); color: var(--cream-100); border-color: var(--forest-800); }

.empty-state { text-align: center; padding: 64px 24px; color: var(--ink-500); }
.empty-state h3 { color: var(--ink-900); margin-bottom: 10px; font-size: 22px; }

/* "Advanced search" mobile collapse — hidden entirely above 980px, where
   .filters-advanced__body always renders open (no display rule needed;
   see the 980px override below for the collapsed/toggle behavior). */
.filters-advanced__toggle { display: none; }

@media (max-width: 980px) {
    .search-layout { grid-template-columns: 1fr; }
    .filters-panel { position: static; }

    .filters-advanced__toggle {
        display: flex; align-items: center; justify-content: space-between; width: 100%;
        background: var(--cream-200); border: 1.5px solid var(--ink-100); border-radius: var(--radius-sm);
        padding: 13px 14px; margin-top: 18px; cursor: pointer;
        font-size: 12px; text-transform: uppercase; letter-spacing: 0.07em; font-weight: 800; color: var(--ink-900);
    }
    .filters-advanced__chevron { transition: transform var(--transition); color: var(--ink-500); }
    .filters-advanced__toggle[aria-expanded="true"] .filters-advanced__chevron { transform: rotate(180deg); }
    .filters-advanced__body { display: none; }
    .filters-advanced__body.is-open { display: block; }

    /* Usability audit (Should Improve #4) — swap which copy of the sponsored
       ad shows: the sidebar copy (which would otherwise land above the
       results once .search-layout collapses to one column, per the rule
       just above) hides, and the copy after the results grid shows instead. */
    .search-sidebar-ad--desktop { display: none; }
    .search-sidebar-ad--mobile { display: block; margin-top: 20px; }
}
.search-sidebar-ad--mobile { display: none; }

/* ---------------------------------------------------------------------
   Listing detail (digital storefront)
   --------------------------------------------------------------------- */
.listing-hero-gallery { display: grid; grid-template-columns: 2fr 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 8px; height: 460px; border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 40px; }
.listing-hero-gallery img { width: 100%; height: 100%; object-fit: cover; cursor: pointer; }
.listing-hero-gallery a:first-child { grid-row: 1 / 3; }
/* Graceful fallback for listings with fewer than 5 photos (e.g. new listings
   before the Phase 2 gallery uploader ships more content). */
.listing-hero-gallery:has(a:only-child) { grid-template-columns: 1fr; height: 380px; }
.listing-hero-gallery:has(a:only-child) a:first-child { grid-row: auto; }
.listing-hero-gallery:has(a:nth-child(2):last-child) { grid-template-columns: 1fr 1fr; height: 380px; }
.listing-hero-gallery:has(a:nth-child(2):last-child) a:first-child { grid-row: auto; }
.gallery-more { position: relative; }
.gallery-more::after {
    content: attr(data-more); position: absolute; inset: 0; background: rgba(16,31,24,0.55); color: var(--white);
    display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px;
}

.listing-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; padding: 22px 0 16px; flex-wrap: wrap; }
.listing-header__crumb { font-size: 13px; color: var(--ink-500); margin-bottom: 8px; }
.listing-header__crumb a { text-decoration: underline; text-underline-offset: 3px; }
.listing-header h1 { font-size: clamp(23px, 3vw, 32px); }
.listing-header__meta { display: flex; align-items: center; gap: 14px; margin-top: 8px; font-size: 14px; color: var(--ink-500); flex-wrap: wrap; }
.listing-header__meta .stars { color: var(--mustard-500); }
.listing-header__actions { display: flex; gap: 10px; }

.listing-body { display: grid; grid-template-columns: 1fr 360px; gap: 40px; align-items: flex-start; padding-bottom: 56px; }
.listing-section { padding: 22px 0; border-top: 1px solid var(--ink-100); }
.listing-section:first-child { border-top: none; padding-top: 0; }
.listing-section h2 { font-size: 22px; margin-bottom: 16px; }
.listing-section p { color: var(--ink-700); }

.amenity-grid { grid-template-columns: repeat(3, 1fr); gap: 14px; }
.amenity-item { display: flex; align-items: center; gap: 10px; font-size: 14px; padding: 12px 14px; background: var(--cream-200); border-radius: var(--radius-sm); }

.hours-table { width: 100%; border-collapse: collapse; font-size: 14.5px; }
.hours-table td { padding: 8px 0; border-bottom: 1px solid var(--ink-100); }
.hours-table td:last-child { text-align: right; color: var(--ink-500); }
.hours-table tr.is-today td { font-weight: 700; color: var(--ink-900); }

.menu-block { margin-bottom: 28px; }
.menu-block h3 { font-size: 17px; margin-bottom: 14px; }
.menu-item { display: flex; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px dashed var(--ink-100); }
.menu-item__name { font-weight: 700; font-size: 14.5px; }
.menu-item__desc { font-size: 13px; color: var(--ink-500); margin-top: 3px; }
.menu-item__price { font-weight: 700; white-space: nowrap; }

.review-card { padding: 20px 0; border-top: 1px solid var(--ink-100); }
.review-card:first-child { border-top: none; padding-top: 0; }
.review-card__head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.review-card__author { font-weight: 700; font-size: 14.5px; }
.review-card__date { font-size: 12.5px; color: var(--ink-500); }
.review-card .stars { color: var(--mustard-500); font-size: 14px; }
.review-card__title { font-weight: 700; margin: 6px 0 4px; }
.review-card__reply { margin-top: 12px; padding: 14px; background: var(--cream-200); border-radius: var(--radius-sm); font-size: 13.5px; }
.review-card__reply strong { color: var(--coral-500); }

.faq-item { border-top: 1px solid var(--ink-100); padding: 16px 0; }
.faq-item:first-child { border-top: none; }
.faq-item summary { font-weight: 700; cursor: pointer; list-style: none; display: flex; justify-content: space-between; }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: '+'; font-size: 20px; color: var(--coral-500); }
.faq-item[open] summary::after { content: '−'; }
.faq-item p { margin-top: 10px; color: var(--ink-700); font-size: 14.5px; }

.promo-banner { background: var(--mustard-500); color: var(--forest-900); border-radius: var(--radius-md); padding: 18px 20px; display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 16px; }
.promo-banner strong { display: block; font-size: 15px; }
.promo-banner code { background: rgba(16,31,24,0.1); padding: 3px 8px; border-radius: 6px; font-weight: 700; }

.contact-card { background: var(--white); border: 1px solid var(--ink-100); border-radius: var(--radius-lg); padding: 26px; position: sticky; top: 96px; box-shadow: var(--shadow-sm); }
.contact-card__row { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--ink-100); font-size: 14.5px; }
.contact-card__row:last-of-type { border-bottom: none; }
.contact-card__row a { font-weight: 600; }
.contact-card .btn { margin-top: 16px; }
.mini-map { height: 160px; border-radius: var(--radius-sm); overflow: hidden; margin-top: 16px; background: var(--cream-200); }

@media (max-width: 980px) {
    .listing-body { grid-template-columns: 1fr; }
    .listing-hero-gallery { grid-template-columns: 1fr 1fr; height: auto; grid-auto-rows: 160px; }
    .listing-hero-gallery a:first-child { grid-row: auto; grid-column: 1 / 3; }
    .amenity-grid { grid-template-columns: repeat(2, 1fr); }
    .contact-card { position: static; }
}

/* ---------------------------------------------------------------------
   Dashboard placeholder (Phase 2 notice)
   --------------------------------------------------------------------- */
.dash-shell { min-height: calc(100vh - 76px); min-height: calc(100dvh - 76px); background: var(--cream-200); padding: 64px 24px; }
.dash-card { max-width: 640px; margin: 0 auto; background: var(--white); border-radius: var(--radius-lg); padding: 48px; text-align: center; box-shadow: var(--shadow-md); }
.dash-card__icon { font-size: 40px; margin-bottom: 18px; }
.dash-card h1 { font-size: 26px; margin-bottom: 12px; }
.dash-card p { color: var(--ink-500); margin-bottom: 24px; }
.dash-card ul { text-align: left; display: inline-flex; flex-direction: column; gap: 8px; margin: 0 auto 28px; font-size: 14.5px; }
.dash-card ul li::before { content: '→ '; color: var(--coral-500); font-weight: 700; }

/* ---------------------------------------------------------------------
   Misc
   --------------------------------------------------------------------- */
.badge { display: inline-block; padding: 4px 10px; border-radius: var(--radius-full); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.badge--verified { background: #e6f2ea; color: #276b47; }
.badge--featured { background: var(--mustard-500); color: var(--forest-900); }
/* Migration 0029 — a second, visually distinct verification badge so
   "Identity Verified" and "Credential Verified" (artisan/professional
   listings only) never look interchangeable when both appear together.
   Reuses the same blue already established for .status-pill--blue. */
.badge--credential { background: #e6eefb; color: #2454a6; }

.stars { color: var(--mustard-500); letter-spacing: -1px; }

.fade-in-up { opacity: 0; transform: translateY(18px); transition: opacity 600ms ease, transform 600ms ease; }
.fade-in-up.is-visible { opacity: 1; transform: translateY(0); }

.skip-link { position: absolute; left: -999px; top: 0; background: var(--forest-800); color: var(--white); padding: 12px 18px; z-index: 100; }
.skip-link:focus { left: 12px; top: 12px; }

.error-page { min-height: calc(100vh - 76px); min-height: calc(100dvh - 76px); display: flex; align-items: center; justify-content: center; text-align: center; padding: 40px; }
.error-page h1 { font-size: 72px; color: var(--coral-500); }
.error-page p { color: var(--ink-500); margin: 12px 0 24px; }
/* errors/419.php — a session-expired notice reads as a short sentence,
   not a 3-digit status code, so it doesn't fit the same 72px numeral
   sizing the 403/404/500 pages use for their big number. */
.error-page h1.error-page__heading--text { font-size: 30px; max-width: 480px; }

/* =====================================================================
   Dashboards (business owner / sales rep / admin)
   ===================================================================== */
.dash-wrap { display: grid; grid-template-columns: 240px 1fr; min-height: calc(100vh - 76px); min-height: calc(100dvh - 76px); background: var(--cream-200); }
.dash-sidebar { background: var(--forest-800); color: var(--cream-100); padding: 28px 0; }

/* Logo sits directly above the sidebar as its own column, matching the
   sidebar's width so the two stack into one continuous panel; the rest of
   the top bar (role pill, view site, log out) runs alongside it. */
.dash-topheader { display: flex; align-items: stretch; min-height: 84px; padding: 0; }
.dash-topheader__logo {
    width: 240px; flex-shrink: 0; display: flex; align-items: center;
    padding: 22px; background: var(--forest-900);
}
.dash-topheader__bar { flex: 1; display: flex; align-items: center; justify-content: flex-end; padding: 22px 32px; min-width: 0; }

/* Admin/Super Admin: give the top bar its own light "app chrome" strip —
   corrected to the canonical eucalyptus/porcelain/champagne palette (see
   client update #20 block further down, which sets the actual logo-panel
   gradient and search/icon-button colors). This base rule only needs to
   stop the site-wide dark .site-header background from showing through
   behind the search bar and icon buttons. */
.dash-role-admin .site-header,
.dash-role-super_admin .site-header { background: rgba(250, 248, 243, .94); border-bottom: 1px solid #ddd9cf; }
.dash-sidebar__label { padding: 0 22px; font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-300); margin: 18px 0 8px; }
.dash-nav a {
    display: flex; align-items: center; gap: 10px; padding: 11px 22px; font-size: 14.5px; font-weight: 500;
    color: var(--cream-100); opacity: .78; border-left: 3px solid transparent; transition: all var(--transition);
}
.dash-nav a:hover { opacity: 1; background: rgba(255,255,255,0.06); }
.dash-nav a.is-active { opacity: 1; background: rgba(255,255,255,0.1); border-left-color: var(--mustard-500); font-weight: 700; }
/* Client request — business_owner/sales_rep sidebar icons now render via
   admin_icon() (see layouts/dashboard.php), the same helper the admin
   console's own sidebar uses — sized to match its icons exactly (the admin
   scope already has its own copy of this same rule, .admin-nav a svg,
   further down; this bare .dash-nav version covers business_owner/
   sales_rep, whose links live in plain .dash-nav rather than .admin-nav). */
.dash-nav a svg { flex-shrink: 0; width: 18px; height: 18px; }

/* A first-time business owner doesn't know this sidebar is "the" place to
   build out their listing — this caption says so on every visit, not just
   the first, so returning owners are never left guessing what the menu is
   for either. Sits above the nav links, in the sidebar's own dim/cream
   palette so it reads as a label, not another clickable row. */
.dash-nav__caption { padding: 0 22px 14px; font-size: 12.5px; line-height: 1.4; color: var(--cream-100); opacity: .6; }

/* min-width: 0 override — .dash-content is a CSS grid item (.dash-wrap is
   `display: grid`, at every viewport width, not just the mobile
   breakpoint below). Grid items default to min-width: auto, meaning a
   grid item never shrinks narrower than its widest descendant's intrinsic
   content width — previously only fixed for a wide .table-scroll at the
   980px mobile breakpoint (see the min-width: 0 fix in that media query
   below), but the new horizontally-scrollable .plan-cards row (My Plans —
   see that rule further down) hits the exact same blowout on ordinary
   desktop widths whenever an admin has enough active plans that the row's
   natural width exceeds the content column: without this, .dash-content
   (and the whole .dash-wrap grid track) stretches wider than the
   viewport instead of letting .plan-cards scroll inside its own box. This
   unconditional min-width: 0 is the general fix for that entire class of
   bug, not just the one table it was first written for. */
.dash-content { padding: 36px 40px; max-width: 1180px; min-width: 0; }
.dash-topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 28px; gap: 16px; flex-wrap: wrap; }
.dash-topbar h1 { font-size: 24px; }
.dash-topbar p { color: var(--ink-500); font-size: 14px; margin-top: 4px; }

.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-bottom: 32px; }

/* Business owner Overview page (business/dashboard.php) — checklist on
   the left (wider), plan usage + live-listing call-out stacked on the
   right (narrower), matching the reference layout. Stacks to a single
   column below 980px alongside the rest of the dashboard's responsive
   breakpoint (see .dash-wrap's own @media (max-width: 980px) block). */
.dash-owner-overview-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; align-items: start; }
.dash-owner-overview-grid > div:last-child { display: flex; flex-direction: column; gap: 16px; }
.dash-owner-overview-grid .dash-panel { margin-bottom: 0; }
@media (max-width: 980px) {
    .dash-owner-overview-grid { grid-template-columns: 1fr; }
}
/* Client request — "there should be a space where the user knows exactly
   which business account is being currently managed" (business/
   dashboard.php's Overview page). Base layout only — colors/border/radius
   all come from the .dash-panel class already on this element. */
.dash-current-listing { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.dash-current-listing__info { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.dash-current-listing__label { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-500); }
.dash-current-listing__name { font-size: 16px; }
.dash-current-listing__ref { font-size: 13px; color: var(--ink-500); }
.dash-role-business_owner .dash-current-listing__label,
.dash-role-business_owner .dash-current-listing__ref { color: var(--admin-muted); }

.dash-panel--live-listing {
    background: linear-gradient(145deg, #1f725b, #174d40); border-color: #1f725b; color: var(--cream-100);
}
.dash-panel--live-listing h2,
.dash-panel--live-listing p { color: var(--cream-100); }
.dash-panel--live-listing .btn--outline-dark { border-color: rgba(255,255,255,.5); color: var(--cream-100); background: transparent; }
.dash-panel--live-listing .btn--outline-dark:hover { background: rgba(255,255,255,.12); }
.stat-card { background: var(--white); border-radius: var(--radius-md); padding: 20px; border: 1px solid var(--ink-100); }
.stat-card__label { font-size: 12.5px; color: var(--ink-500); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.stat-card__value { font-family: var(--font-display); font-size: 30px; font-weight: 700; margin-top: 6px; }
.stat-card__meta { font-size: 12.5px; color: var(--ink-500); margin-top: 4px; }

/* Client update #14 — plan usage meters on the business dashboard overview
   (see business/dashboard.php). Three states by percent-used: forest
   (plenty of room), mustard (getting close), coral (at/over the limit) —
   reusing the same three brand colors status pills already use elsewhere
   in the dashboard rather than introducing a fourth palette. */
.usage-meter { margin-bottom: 16px; }
.usage-meter:last-child { margin-bottom: 0; }
.usage-meter__head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; gap: 8px; }
.usage-meter__label { font-size: 13.5px; font-weight: 600; }
.usage-meter__count { font-size: 12.5px; color: var(--ink-500); }
.usage-meter__track { background: var(--cream-200); border-radius: 999px; height: 8px; overflow: hidden; }
.usage-meter__fill { height: 100%; border-radius: 999px; background: var(--forest-500); transition: width var(--transition); }
.usage-meter--warn .usage-meter__fill { background: var(--mustard-500); }
.usage-meter--full .usage-meter__fill { background: var(--coral-500); }

/* Revision #1 (mobile overflow) — overflow-x:hidden here is a defensive
   backstop: nothing rendered inside a panel (a nested form, a fixed-width
   inline style someone adds later, etc.) can ever bleed past the panel's
   own box and drag .dash-content wider than the viewport. It does NOT
   clip the intentional horizontal scroll on wide tables — that scrolling
   happens inside .table-scroll's own box (see .table-scroll rules below),
   which never grows wider than this panel to begin with. */
.dash-panel { background: var(--white); border-radius: var(--radius-md); border: 1px solid var(--ink-100); padding: 18px; margin-bottom: 16px; overflow-x: hidden; scroll-margin-top: 100px; transition: background 1.8s ease; }
.dash-panel h2 { font-size: 16px; margin-bottom: 12px; }
.dash-panel__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 12px; flex-wrap: wrap; }

/* Business-detail page jump nav (business-show.php) — clicking a menu
   item smooth-scrolls to and briefly flashes the target panel; see the
   scroll-margin-top above (clears the fixed header) and the inline
   <script> at the bottom of that view for the click + scrollspy logic. */
.biz-jump-nav { margin-bottom: 8px; }
.biz-jump-nav a { cursor: pointer; }
.dash-panel--jumped-to { background: #fdf1de; }

/* Small icon-chip + status row, used on the Get Featured / Sponsored
   Search & Category pages (business/featured.php, sponsorship.php) to
   show current status above the package picker. */
.status-callout { display: flex; align-items: center; gap: 14px; }
.status-callout__icon {
    flex: none; width: 40px; height: 40px; border-radius: var(--radius-md); background: var(--cream-200);
    display: flex; align-items: center; justify-content: center; font-size: 16px; color: var(--forest-500);
}
.status-callout strong { display: block; font-size: 15px; }
.status-callout p { font-size: 13.5px; color: var(--ink-500); margin-top: 2px; }
.dash-role-business_owner .status-callout__icon { background: #eef2ef; color: var(--admin-active); }
.dash-role-business_owner .status-callout p { color: var(--admin-muted); }

/* "Let's build your listing" onboarding guide (see business/dashboard.php
   + Business::setupSteps()) — the first thing a new owner sees, meant to
   read as a friendly walkthrough rather than another data panel. Sits on
   .dash-panel's base card but with a forest accent border so it visually
   leads the page; collapses to .setup-guide--done once every required
   step is complete, so it never lingers as clutter for an owner who's
   already finished setting up. */
.setup-guide { border: 1px solid var(--forest-500); border-top: 4px solid var(--forest-500); }
.setup-guide__eyebrow {
    display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    color: var(--forest-700); background: var(--cream-200); padding: 4px 10px; border-radius: var(--radius-full); margin-bottom: 10px;
}
.setup-guide__title { font-family: var(--font-display); font-size: 22px; margin-bottom: 6px; }
.setup-guide__intro { color: var(--ink-700); font-size: 14.5px; margin-bottom: 18px; }
.setup-guide__progress { display: flex; align-items: center; gap: 14px; margin-bottom: 22px; }
.setup-guide__progress-track { flex: 1; background: var(--cream-200); border-radius: var(--radius-full); height: 8px; overflow: hidden; }
.setup-guide__progress-fill { height: 100%; border-radius: var(--radius-full); background: var(--forest-500); transition: width var(--transition); }
.setup-guide__progress-label { font-size: 12.5px; font-weight: 700; color: var(--ink-700); white-space: nowrap; }

.setup-guide__steps { list-style: none; display: flex; flex-direction: column; }
.setup-guide__step {
    display: flex; align-items: flex-start; gap: 16px; padding: 16px 12px;
    border-bottom: 1px solid var(--ink-100); border-radius: var(--radius-md);
}
.setup-guide__step:last-child { border-bottom: none; padding-bottom: 4px; }
.setup-guide__step.is-next { background: rgba(47,92,70,0.06); position: relative; }

.setup-guide__badge {
    flex: none; width: 28px; height: 28px; margin-top: 2px; border-radius: 50%;
    border: 2px solid var(--ink-100); display: flex; align-items: center; justify-content: center;
    font-size: 13px; font-weight: 800; color: var(--ink-500);
}
.setup-guide__step.is-done .setup-guide__badge { background: var(--forest-500); border-color: var(--forest-500); color: var(--white); }

.setup-guide__step-body { flex: 1; min-width: 0; }
.setup-guide__step-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.setup-guide__step-label { font-size: 15px; font-weight: 700; }
.setup-guide__step.is-done .setup-guide__step-label { color: var(--ink-500); text-decoration: line-through; text-decoration-color: var(--ink-300); }
.setup-guide__optional { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-500); background: var(--cream-200); padding: 2px 8px; border-radius: var(--radius-full); }
.setup-guide__attention {
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #8a5a12;
    background: #fdf1de; padding: 2px 8px; border-radius: var(--radius-full);
}
/* Small ribbon pinned to the highlighted "what to do next" step — only
   ever rendered on the single step matching $nextStepKey (see
   business/dashboard.php), so there's never more than one on screen. */
.setup-guide__ribbon {
    position: absolute; top: -1px; right: 14px; font-size: 10px; font-weight: 800; text-transform: uppercase;
    letter-spacing: .04em; color: #6b4a17; background: var(--mustard-400); padding: 4px 10px;
    border-radius: 0 0 6px 6px;
}
.setup-guide__step-hint { font-size: 13px; color: var(--ink-500); margin-top: 3px; }
.setup-guide__step .btn { flex: none; align-self: center; }

.setup-guide--done { border-color: var(--forest-500); background: #f2f7f4; }
.setup-guide--done p { color: var(--forest-700); font-size: 15px; }

@media (max-width: 640px) {
    .setup-guide__step { flex-wrap: wrap; }
    .setup-guide__step .btn { margin-left: 44px; }
}

/* Admin multi-criteria filter panels (Users, Businesses) — a responsive
   grid of same-height fields, wrapping to fewer columns as the screen
   narrows rather than the horizontal-scroll single-row toolbar this
   replaced, which only had room for two fields. */
.admin-filter-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; }
.admin-filter-grid .field label { font-size: 12px; font-weight: 700; color: var(--ink-500); text-transform: uppercase; letter-spacing: .03em; display: block; margin-bottom: 5px; }
.admin-filter-grid .field input,
.admin-filter-grid .field select { width: 100%; padding: 10px 12px; border-radius: var(--radius-sm); border: 1.5px solid var(--ink-100); font-size: 14px; }

/* Usability audit (Minor #31) — Search pulled out above .admin-filter-grid
   on admin/users.php and admin/businesses.php (both otherwise unchanged);
   this gives it real visual weight — wider, taller, a bolder border — since
   it's what most admins reach for first, versus the rarely-touched filters
   below it which all still share equal weight with each other. */
.admin-filter-search label { font-size: 12px; font-weight: 700; color: var(--ink-500); text-transform: uppercase; letter-spacing: .03em; display: block; margin-bottom: 5px; }
.admin-filter-search input { width: 100%; max-width: 480px; padding: 13px 16px; border-radius: var(--radius-sm); border: 1.5px solid var(--ink-200, #ddd); font-size: 15px; }
.admin-filter-search input:focus { border-color: var(--coral-500, #e0602c); outline: none; }

/* Wraps every dashboard <table class="data-table"> (admin, business owner
   and sales rep views alike). overflow-x:auto applies at every width —
   Revision #3 found the admin Users table (9 columns: name, email, phone,
   role select, status, verified, last login, joined, actions) is wide
   enough to spill past the white .dash-panel card even on a laptop-width
   desktop, not just on mobile. Giving every .table-scroll a horizontal
   scrollbar unconditionally keeps a wide table contained and reachable at
   any viewport width instead of visually breaking out of its card; on
   mobile (see the 980px media query below) it also gets a min-width so
   columns don't get squeezed illegibly thin. Tables that already fit
   their container are completely unaffected — overflow:auto only shows a
   scrollbar when content actually overflows. */
.table-scroll { width: 100%; overflow-x: auto; }
/* Client update #18 — setting overflow-x:auto above, with overflow-y left
   unset, doesn't actually leave overflow-y as "visible": per spec, a
   non-visible overflow-x forces overflow-y to compute as "auto" too (the
   browser can't scroll one axis and not the other). That's invisible
   almost everywhere, since .table-scroll only clips content that actually
   overflows its own box — but a row's "•••" dropdown (.admin-kebab-panel)
   is position:absolute, extending below the table's own bottom edge, and
   absolutely-positioned overflow doesn't grow that box the way normal
   in-flow content would. For every row except the last one or two, there's
   still plenty of table below to contain it and nothing looks wrong; open
   the LAST row's menu, though — e.g. Admin > Businesses' new "Also delete
   owner's account" checkbox made that panel just tall enough to notice —
   and the bottom of the panel, sometimes including the Delete button
   itself, gets clipped clean off. Reserving extra room only while a panel
   is actually open (rather than unconditional padding, which would leave
   a permanent blank gap under every admin table on the site) fixes it
   without changing how any table looks the rest of the time.
   Bumped from the original 170px (2026-08-21) — Advertisements' new
   mobile-image edit field made that panel taller than a fixed 170px could
   keep covering forever, so the panel itself now caps its own height at
   70vh and scrolls internally (see .admin-kebab-panel's max-height) rather
   than depending on this number matching whatever the tallest panel needs;
   this reserve just needs to comfortably fit that capped height on a
   normal screen, with room to spare for any table's panel, not just
   Advertisements'. */
.table-scroll:has(.admin-row-actions[open]) { padding-bottom: 460px; }
.data-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.data-table th { text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-500); padding: 10px 12px; border-bottom: 2px solid var(--ink-100); }
/* overflow-wrap:anywhere here (Revision #3) was a mistake — it doesn't
   just protect against one long unbroken email/URL, it makes the browser
   hard-wrap ANY cell content mid-word the moment a column gets a little
   tight, including short strings like "ACTIVE" or a phone number, which
   is uglier than the overflow it was meant to prevent. .table-scroll's
   horizontal scroll (see below) is what actually handles a table wider
   than its container — this cell rule should never fight that by forcing
   text to break instead of letting the table scroll. */
.data-table td { padding: 12px; border-bottom: 1px solid var(--ink-100); vertical-align: middle; }
/* Long unbroken strings (emails, URLs) still shouldn't be able to force a
   column wide open — scoped to just those cells instead of every cell. */
.data-table td.cell-wrap { overflow-wrap: anywhere; }
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: var(--cream-200); }

.status-pill { display: inline-flex; padding: 4px 10px; border-radius: var(--radius-full); font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; white-space: nowrap; }
.status-pill--green { background: #e6f2ea; color: #276b47; }
.status-pill--amber { background: #fdf1de; color: #a15c07; }
.status-pill--red { background: #fbe9e5; color: var(--coral-600); }
.status-pill--grey { background: var(--ink-100); color: var(--ink-700); }
.status-pill--blue { background: #e6eefb; color: #2454a6; }

.tab-row { display: flex; gap: 6px; margin-bottom: 22px; border-bottom: 1px solid var(--ink-100); flex-wrap: wrap; }
.tab-row a { padding: 10px 16px; font-size: 14px; font-weight: 600; color: var(--ink-500); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tab-row a.is-active { color: var(--ink-900); border-bottom-color: var(--coral-500); }

.icon-btn { background: none; border: none; cursor: pointer; font-size: 15px; color: var(--ink-500); padding: 6px; border-radius: 6px; }
.icon-btn:hover { background: var(--cream-200); color: var(--coral-500); }
.icon-btn--danger:hover { color: var(--coral-600); }

.inline-form { display: inline; }
.dash-empty { padding: 40px 20px; text-align: center; color: var(--ink-500); }

/* Business Insights upgrade (2026-08-22) — a plain horizontal fill bar,
   used for the star-rating distribution and the Customer Action
   channel breakdown on the Insights page. Deliberately not a full
   charting-library progress component — this is the same "no JS
   dependency, inline everything" philosophy as bf_bar_svg()/
   af_bar_chart_svg() right next to it. */
.insight-bar { flex: 1; height: 8px; background: var(--ink-100); border-radius: var(--radius-full); overflow: hidden; }
.insight-bar__fill { display: block; height: 100%; background: var(--forest-600, #244937); border-radius: var(--radius-full); }

.plan-picker { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.plan-pick-card { border: 2px solid var(--ink-100); border-radius: var(--radius-md); padding: 20px; cursor: pointer; transition: all var(--transition); }
.plan-pick-card.is-selected { border-color: var(--coral-500); background: #fef6f3; }
.plan-pick-card h3 { font-size: 16px; margin-bottom: 4px; }
.plan-pick-card .price { font-family: var(--font-display); font-size: 24px; font-weight: 700; margin: 8px 0; }
/* Get Featured / Sponsored Search & Category packages (business/featured.php,
   sponsorship.php) — selection accent matches the rest of the owner
   dashboard's forest/gold palette instead of the public site's coral. */
.dash-role-business_owner .plan-pick-card { border-color: var(--admin-line); }
.dash-role-business_owner .plan-pick-card.is-selected { border-color: var(--admin-active); background: #eef5f1; }
.dash-role-business_owner .plan-pick-card .price { color: var(--admin-active); }

/* My Plans (business/plans.php) — side-by-side plan cards, one per plan,
   the current plan sorted first by the controller. Deliberately its own
   small component rather than reusing .pricing-card wholesale: this needs
   a ribbon, a full feature-matrix (check/✕ per admin-defined benefit, not
   just each plan's own short list), and a "Plan limits" section neither
   the public pricing cards nor the Billing page's plan-picker cards have. */
/* Client request (2026-08-21) — "never show the paid plans by themselves.
   The Free Plan must always stand beside them so the user has a clear
   baseline for comparison" + "allow horizontal scrolling of the plan
   comparison area instead of shrinking the cards until they become
   difficult to read." Switched from a wrapping auto-fit grid (which
   shrank/reflowed cards onto extra rows as more paid plans were added) to
   a single horizontally-scrollable row: every card keeps a comfortable,
   readable fixed width no matter how many plans an admin has active, and
   the Free plan (always rendered first — see BillingController::plans())
   stays the leftmost, always-visible "you are here" baseline as the
   visitor scrolls right through the paid options. Same scroll-row markup
   serves both desktop (mouse-wheel/trackpad scroll, or drag) and mobile
   (native touch swipe) — no separate mobile layout needed. */
/* Client request (2026-08-21, reference screenshot) — "everything should
   line up like this image so the user can see exactly the differences."
   Rebuilt from N independent flex cards into ONE CSS grid: every plan is a
   column (an inline grid-template-columns set per-page-load in plans.php,
   since the column count depends on how many plans an admin has active),
   and every field — ribbon, name, price, button, tagline, each plan limit,
   each feature — is the SAME grid row across every column (see the row-by-
   row comments in business/plans.php). CSS grid auto-sizes each row to its
   tallest cell and stretches every other cell in that row to match by
   default, so e.g. "Featured badge" always lines up at the exact same
   height in every column automatically — even when one plan's tagline
   wraps to two lines and another's doesn't, which independent cards could
   never guarantee. .plan-grid__bg is a separate per-column background box
   (grid-row: 1 / -1, spanning every row) painted behind the real content
   cells so each plan still visually reads as its own bordered card. */
/* Client request (2026-08-21) — "REDESIGN the My Plans page... I want the
   STRUCTURE... to closely reproduce the FastComet pricing design," with
   reference screenshots specifying the structure exactly: DESKTOP must be
   ONE CONNECTED comparison table — equal-width columns touching each
   other with thin dividers, no gaps, no per-column rounded corners or
   floating-card shadow (superseding the previous round's dark-backdrop
   "floating card" treatment entirely) — while MOBILE must be a
   genuinely different structure, not a shrunk copy of the desktop table:
   one full-width card per plan, collapsed to just name/price/button/
   tagline by default, with its own independent "View all features"
   accordion. Those are two different DOM shapes, not two sizes of the
   same one, so business/billing.php renders BOTH — this same .plan-grid
   markup for ≥769px, and a separate .plan-mcards block (further below)
   for ≤768px — and this stylesheet shows exactly one of them at a time
   via the max-width:768px query near the bottom of this section. */
.plan-grid-wrap {
    background: var(--white); border: 1px solid var(--ink-100); border-radius: 16px;
    overflow: hidden; box-shadow: var(--shadow-sm);
}
.plan-grid { display: grid; row-gap: 0; column-gap: 0; align-items: stretch; position: relative; }
/* Column background box (grid-row: 1 / N, spanning every row — see the PHP
   view) painted behind that column's real content cells. No border-radius,
   shadow, or margin of its own — a border-right (removed on the last
   column via --last-col, added in the view) is the ONLY thing separating
   one column from the next, so the four read as one continuous table
   instead of four independent cards. */
.plan-grid__bg { grid-row: 1 / -1; background: var(--white); border-right: 1px solid var(--ink-100); z-index: 0; }
.plan-grid__bg--last-col { border-right: none; }
/* The current plan's column gets an inset highlight ring instead of a
   real border, so it doesn't add width and throw off the equal-column
   math or double up with the divider — same visual effect (a distinctly
   outlined column, matching the reference's "Plus" treatment) without
   disturbing the grid. */
.plan-grid__bg--current { box-shadow: inset 0 0 0 2px var(--mustard-500, #d9a441); z-index: 1; }
.plan-grid__bg--free { background: #fafaf7; }
.plan-grid__cell { position: relative; z-index: 2; padding: 9px 22px; min-width: 0; }
.plan-grid__cell--name { padding-top: 22px; }
.plan-grid__cell--last { padding-bottom: 22px; }
.plan-grid__ribbon-cell { padding: 0; }
/* Flat banner spanning the full column width — no rounded corners of its
   own (the outer .plan-grid-wrap's overflow:hidden + border-radius handles
   rounding only where the table itself ends, exactly like the reference's
   "BEST VALUE" bar sitting flush across the top of its column). */
.plan-card__ribbon {
    color: #fff; font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
    text-align: center; padding: 10px 12px;
    background-image: linear-gradient(180deg, rgba(255,255,255,.18), rgba(255,255,255,0));
}
.plan-card__free-tag {
    display: inline-block; width: fit-content; background: var(--ink-900); color: var(--white);
    font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
    padding: 4px 10px; border-radius: 999px; margin-bottom: 10px;
}
.plan-card__name { font-family: var(--font-display); font-size: 23px; font-weight: 800; letter-spacing: -.01em; }
.plan-card__price-row { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.plan-card__price { font-family: var(--font-display); font-size: 34px; font-weight: 800; color: var(--ink-900); letter-spacing: -.01em; }
.plan-card__period { font-size: 13px; font-weight: 500; color: var(--ink-500); }
.plan-card__current-badge {
    display: block; text-align: center; background: #fdf3e2; color: #7a5410; border: 1px solid #f0d99b;
    font-weight: 700; cursor: default;
}
/* min-width: 0 on the form AND its button/select — same CSS grid/flex
   "min-width: auto never shrinks below content" bug class as the
   .dash-content fix elsewhere in this file, just on a flex axis instead
   of a grid one: without it, a flex ITEM (the button/select here) refuses
   to shrink narrower than its own text content, which at a squeezed
   4-column tablet width was pushing "Upgrade to Starter" wider than its
   column and spilling it into the divider/next column instead of
   wrapping. white-space: normal (overriding the site's global .btn
   {white-space: nowrap} baseline, scoped to just this form the same way
   .plan-card__form .btn--coral already scopes its own override below)
   lets it wrap onto a second line instead. */
.plan-card__form { display: flex; flex-direction: column; gap: 9px; min-width: 0; }
.plan-card__form .btn { min-width: 0; white-space: normal; line-height: 1.25; text-align: center; }
.plan-card__interval { font-size: 12.5px; padding: 9px 10px; border-radius: var(--radius-sm); border: 1px solid var(--ink-100); width: 100%; min-width: 0; }
.plan-card__tagline { font-size: 13px; color: var(--ink-500); margin: 0; }
.plan-card__divider { border-top: 1px solid var(--ink-100); margin: 6px 0 2px; }
.plan-card__section-title { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-500); margin-bottom: 2px; }
.plan-card__row { display: flex; align-items: center; gap: 10px; color: var(--ink-700); font-size: 13.5px; }
.plan-card__row-icon {
    flex-shrink: 0; width: 18px; height: 18px; border-radius: 50%; font-size: 10.5px; font-weight: 800;
    display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}
.plan-card__row--yes .plan-card__row-icon { background: #e3f4ea; color: #1d8a4f; }
.plan-card__row--no { color: var(--ink-300); }
.plan-card__row--no .plan-card__row-icon { background: #fbeceb; color: #c94b4b; }
/* CTA buttons on this page specifically get a touch more presence
   (shadow + a hair more padding) than the site's generic .btn baseline —
   this is the one action on the whole page every plan is built around. */
.plan-card__form .btn--coral { box-shadow: 0 10px 22px -8px rgba(200, 79, 61, .55); padding-top: 13px; padding-bottom: 13px; }

/* ---------- Mobile: one full-width card per plan, own accordion ----------
   Client request — "Each plan must become its OWN FULL-WIDTH CARD... Do
   NOT display the complete feature list automatically on mobile... The
   accordion must be accessible and work independently for each plan."
   Each .plan-mcard is a <details> pair below its always-visible summary
   info, exactly like the existing .admin-plan-card / .admin-sidebar__menu
   details/summary pattern already used elsewhere in this app — native,
   accessible, and each one opens independently with no JS needed. Hidden
   by default (only shown ≤768px — see the media query at the end of this
   section); .plan-grid-wrap above is the ≥769px version of this exact
   same plan data. */
.plan-mcards { display: none; }
.plan-mcard {
    background: var(--white); border: 1px solid var(--ink-100); border-radius: 14px;
    overflow: hidden; margin-bottom: 16px; box-shadow: var(--shadow-sm);
}
.plan-mcard:last-child { margin-bottom: 0; }
.plan-mcard--current { border-color: var(--mustard-500, #d9a441); border-width: 2px; }
.plan-mcard__ribbon {
    color: #fff; font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
    text-align: center; padding: 9px 12px;
    background-image: linear-gradient(180deg, rgba(255,255,255,.18), rgba(255,255,255,0));
}
.plan-mcard--free .plan-mcard__body { background: #fafaf7; }
.plan-mcard__body { padding: 24px 22px 20px; text-align: center; }
.plan-mcard__body .plan-card__free-tag { margin: 0 0 10px; }
.plan-mcard__name { font-family: var(--font-display); font-size: 22px; font-weight: 800; margin: 0 0 8px; letter-spacing: -.01em; }
.plan-mcard__price-row { display: flex; align-items: baseline; justify-content: center; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
.plan-mcard__price { font-family: var(--font-display); font-size: 32px; font-weight: 800; color: var(--ink-900); letter-spacing: -.01em; }
.plan-mcard__period { font-size: 13px; font-weight: 500; color: var(--ink-500); }
.plan-mcard__body .pricing-card__promo { text-align: center; margin-bottom: 14px; }
.plan-mcard__body .plan-card__current-badge { margin-top: 2px; }
.plan-mcard__tagline { font-size: 13px; color: var(--ink-500); margin: 14px 0 0; }
/* Details/summary accordion — collapsed by default (no [open] attribute),
   independent per card since each is its own <details> element. */
.plan-mcard__features { border-top: 1px solid var(--ink-100); }
.plan-mcard__features summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; justify-content: center; gap: 6px;
    padding: 13px 16px; font-size: 13.5px; font-weight: 700; color: var(--ink-700);
    background: var(--cream-200);
}
.plan-mcard__features summary::-webkit-details-marker { display: none; }
/* Text-swap toggle (spec's own wording: "View all features ˅" / "Hide
   features ˄") — CSS-only via the [open] attribute, no JS. */
.plan-mcard__features-toggle-open { display: none; }
.plan-mcard__features[open] .plan-mcard__features-toggle-closed { display: none; }
.plan-mcard__features[open] .plan-mcard__features-toggle-open { display: inline; }
.plan-mcard__features-body { padding: 16px 22px 22px; display: flex; flex-direction: column; gap: 10px; }
.plan-mcard__features-body .plan-card__section-title:first-child { margin-top: 0; }
.plan-mcard__features-body .plan-card__section-title { margin-top: 14px; }

/* Client-specified breakpoint ("Use an appropriate breakpoint around
   768px") — deliberately separate from the dashboard shell's own 980px
   sidebar-collapse breakpoint further down: that one is about the sidebar/
   .dash-wrap, this one is specifically about which of the two plan-
   comparison structures above is shown, per the client's own spec. */
@media (max-width: 768px) {
    .plan-grid-wrap { display: none; }
    .plan-mcards { display: block; }
}

/* Client requirement — "Make sure intermediate tablet widths remain
   usable." Four equal columns (minmax(0,1fr), by design — see the view)
   divide a much narrower content area on a tablet than on a full desktop
   screen, so type sized for desktop reads uncomfortably tight before the
   layout switches over to full-width mobile cards at 768px. Scales the
   desktop table's key sizes down a step in that in-between range only —
   above 1200px this has no effect at all. */
@media (max-width: 1200px) and (min-width: 769px) {
    .plan-grid__cell { padding: 8px 14px; }
    .plan-grid__cell--name { padding-top: 18px; }
    .plan-grid__cell--last { padding-bottom: 18px; }
    .plan-card__name { font-size: 18px; }
    .plan-card__price { font-size: 25px; }
    .plan-card__row { font-size: 12.5px; gap: 7px; }
    .plan-card__row-icon { width: 15px; height: 15px; font-size: 9.5px; }
    .plan-card__interval { font-size: 11px; padding: 8px 8px; }
    .plan-card__form .btn { font-size: 13px; padding: 11px 10px; }
}

.upload-drop {
    border: 2px dashed var(--ink-100); border-radius: var(--radius-md); padding: 28px; text-align: center; color: var(--ink-500);
    font-size: 14px; background: var(--cream-200);
}

/* Emoji picker, injected by initEmojiPickers() around any
   input[data-emoji-picker] — a click-to-insert palette so admins don't
   need to know an OS emoji-keyboard shortcut to fill in an icon field. */
/* Client request — "add a view password here": Show/Hide toggle injected
   next to every password field by initPasswordToggles() in main.js (see
   that function's own comment for why this is JS-driven instead of a
   markup change on every registration/login/settings view). The wrapping
   div only adds position:relative — the input keeps its normal .field
   width/height/border styling untouched; the toggle button is laid on top
   with absolute positioning and reserved padding-right on the input so its
   own text never sits under the button. */
.password-field { position: relative; }
.password-field input[type="password"],
.password-field input[type="text"] { padding-right: 56px; }
.password-field__toggle {
    position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
    background: none; border: none; padding: 6px 8px; cursor: pointer;
    font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
    color: var(--ink-500);
}
.password-field__toggle:hover { color: var(--ink-900); }

.emoji-picker { position: relative; display: flex; gap: 8px; align-items: center; }
.emoji-picker input { flex: 1; }
.emoji-picker__trigger {
    padding: 9px 14px; border-radius: var(--radius-sm); border: 1.5px solid var(--ink-100);
    background: var(--white); font-size: 13.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.emoji-picker__trigger:hover { background: var(--cream-200); }
.emoji-picker__panel {
    display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
    background: var(--white); border: 1px solid var(--ink-100); border-radius: var(--radius-md);
    box-shadow: 0 12px 32px rgba(16,31,24,0.16); padding: 10px; width: 260px;
    grid-template-columns: repeat(6, 1fr); gap: 2px; max-height: 220px; overflow-y: auto;
}
.emoji-picker__panel.is-open { display: grid; }
.emoji-picker__option {
    background: none; border: none; font-size: 20px; line-height: 1; padding: 7px;
    border-radius: 6px; cursor: pointer; transition: background var(--transition);
}
.emoji-picker__option:hover { background: var(--cream-200); }

/* Upload progress bar, injected by initAjaxUploadForms() into any
   form[data-ajax-upload]. Hidden until a submit is in flight. */
.upload-progress { display: none; margin-top: 14px; grid-column: 1 / -1; }
.upload-progress.is-active { display: block; }
.upload-progress__track { width: 100%; height: 8px; border-radius: var(--radius-full); background: var(--ink-100); overflow: hidden; }
.upload-progress__bar { height: 100%; width: 0%; background: var(--coral-500); border-radius: var(--radius-full); transition: width 0.15s ease; }
.upload-progress__label { margin-top: 6px; font-size: 13px; color: var(--ink-500); font-weight: 600; }
.upload-progress__error { margin-top: 12px; }
.thumb-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 16px; }
.thumb-card { position: relative; border-radius: var(--radius-sm); overflow: hidden; aspect-ratio: 1; }
.thumb-card img { width: 100%; height: 100%; object-fit: cover; }
.thumb-card__remove { position: absolute; top: 6px; right: 6px; background: rgba(16,31,24,0.7); color: #fff; border: none; width: 26px; height: 26px; border-radius: 50%; font-size: 13px; }
/* Client request — per-device photo repositioning; see initPhotoReposition()
   in main.js and migration 0048. */
.thumb-card__reposition {
    position: absolute; bottom: 6px; right: 6px; background: rgba(16,31,24,0.7); color: #fff;
    border: none; padding: 5px 9px; border-radius: var(--radius-sm); font-size: 11px; font-weight: 700; cursor: pointer;
}
.thumb-card__reposition:hover { background: rgba(16,31,24,0.9); }

/* Photo reposition tool — a full-screen overlay where an owner drags a
   single focal point directly on the photo while three live preview boxes
   (desktop / tablet / mobile, each a different aspect ratio) update
   instantly, so it's obvious the same chosen point stays visible no matter
   what shape the photo ends up cropped to on a given screen size. */
.reposition-overlay {
    position: fixed; inset: 0; background: rgba(16,31,24,0.82); z-index: 300;
    display: none; align-items: center; justify-content: center; padding: 20px;
}
.reposition-overlay.is-open { display: flex; }
.reposition-modal { background: var(--white); border-radius: var(--radius-lg); padding: 24px; max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto; }
.reposition-modal h3 { margin-bottom: 4px; }
.reposition-modal__hint { color: var(--ink-500); font-size: 13px; margin-bottom: 16px; }
.reposition-modal__stage {
    position: relative; width: 100%; aspect-ratio: 4 / 3; border-radius: var(--radius-md);
    overflow: hidden; cursor: crosshair; background: var(--cream-200); touch-action: none;
}
.reposition-modal__stage img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; user-select: none; }
.reposition-modal__pin {
    position: absolute; width: 22px; height: 22px; border-radius: 50%; border: 3px solid #fff;
    background: var(--coral-500); box-shadow: 0 0 0 1.5px rgba(0,0,0,.4); transform: translate(-50%, -50%); pointer-events: none;
}
.reposition-modal__previews { display: flex; gap: 14px; margin-top: 18px; flex-wrap: wrap; }
.reposition-preview { flex: 1 1 140px; min-width: 120px; }
.reposition-preview__label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-500); margin-bottom: 6px; }
.reposition-preview__frame { border-radius: var(--radius-sm); overflow: hidden; background: var(--cream-200); }
.reposition-preview__frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.reposition-preview--desktop .reposition-preview__frame { aspect-ratio: 16 / 7; }
.reposition-preview--tablet .reposition-preview__frame { aspect-ratio: 4 / 3; }
.reposition-preview--mobile .reposition-preview__frame { aspect-ratio: 3 / 4; }
.reposition-modal__actions { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 20px; }
.reposition-modal__status { font-size: 12.5px; color: var(--ink-500); margin-right: auto; }
@media (max-width: 640px) {
    .reposition-preview { flex: 1 1 100%; }
}

/* Client request — Growth pages (Get Featured, Boost Search/Category
   Listing, Get Verified) were explaining what each paid package does as a
   wall of text right under the page title. Each now pairs a short one-line
   summary with a "See what this does?" link that opens the full
   explanation in a small popup instead — same "one overlay built once,
   shared by every trigger on the page" approach as the photo reposition
   tool above (.reposition-overlay / initPhotoReposition() in main.js) —
   see initInfoModals() in main.js. */
.info-link-btn {
    background: none; border: none; padding: 0; margin-top: 6px; display: inline-flex; align-items: center; gap: 4px;
    color: var(--forest-700, #1c4d33); font-weight: 700; font-size: 13.5px; cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.info-link-btn:hover { color: var(--forest-800, #143726); }
.info-modal-overlay {
    position: fixed; inset: 0; background: rgba(16,31,24,0.82); z-index: 300;
    display: none; align-items: center; justify-content: center; padding: 20px;
}
.info-modal-overlay.is-open { display: flex; }
.info-modal { background: var(--white); border-radius: var(--radius-lg); padding: 26px; max-width: 520px; width: 100%; max-height: 85vh; overflow-y: auto; position: relative; }
.info-modal__close { position: absolute; top: 12px; right: 12px; background: none; border: none; font-size: 22px; line-height: 1; color: var(--ink-500); cursor: pointer; padding: 6px; }
.info-modal__close:hover { color: var(--ink-900); }
.info-modal__body h3 { margin: 0 0 12px; padding-right: 24px; }
.info-modal__body p { color: var(--ink-700); font-size: 14px; line-height: 1.6; margin: 0 0 12px; }
.info-modal__body p:last-child { margin-bottom: 0; }
.info-modal__body strong { color: var(--ink-900); }

/* ---------------------------------------------------------------------
   Dashboard mobile navigation — hamburger button + off-canvas drawer.
   Hidden entirely above 980px, where the sidebar already sits statically
   next to the content (see .dash-nav-toggle / .dash-sidebar-overlay
   overrides inside the @media block below).
   --------------------------------------------------------------------- */
.dash-nav-toggle {
    display: none; flex-direction: column; justify-content: center; gap: 5px;
    width: 34px; height: 34px; background: none; border: none; cursor: pointer;
    padding: 0; margin-right: 14px; flex-shrink: 0;
}
/* Client request — "the navigation 3 bars should be more visible." The
   hamburger's bars were --cream-100 (near-white, #fdfbf6) sitting on the
   light-sage .dash-topheader__logo background every dashboard role uses
   (admin/super_admin: linear-gradient #d2e3d9→#bfd6ca; business_owner/
   sales_rep: the same gradient — see those rules further down) — a
   light-on-light combination that was hard to see. Switched to a dark
   forest color (matching the site's own dark-green palette rather than
   plain black) for real contrast against that background, and thickened
   the bars slightly for better visibility. */
.dash-nav-toggle span { display: block; width: 22px; height: 3px; background: var(--forest-800); border-radius: 2px; transition: transform var(--transition), opacity var(--transition); }
.dash-nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.dash-nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.dash-nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.dash-sidebar-overlay {
    display: none; position: fixed; inset: 0; z-index: 150;
    background: rgba(16, 31, 24, 0.5); opacity: 0; pointer-events: none;
    transition: opacity var(--transition);
}
.dash-sidebar-overlay.is-open { opacity: 1; pointer-events: auto; }

@media (max-width: 980px) {
    /* CSS grid items default to min-width:auto, meaning a grid item will
       NEVER shrink narrower than its widest descendant's intrinsic content
       — so the .table-scroll min-width added below (640px, so table
       columns stay legible) was bubbling all the way up through
       .dash-content and forcing the whole .dash-wrap grid track, and with
       it the entire page, wider than the phone's viewport. That's what was
       causing the admin dashboard to render off-screen to the right and
       the page to lurch left/right on scroll. min-width: 0 here lets
       .dash-content shrink back to the actual viewport width, so the wide
       table scrolls inside its own .table-scroll box instead of dragging
       the whole layout wider than the screen. */
    .dash-wrap { grid-template-columns: 1fr; overflow-x: hidden; padding-top: 84px; }
    .dash-content { min-width: 0; }
    .dash-nav-toggle { display: flex; }
    .dash-sidebar-overlay { display: block; }

    /* Revision #2 (mobile nav stays fixed) — same root cause and fix as
       .site-header--public above: position:sticky silently fails on phones
       once an ancestor (html/body) has overflow-x:hidden, which this
       dashboard shell relies on to stop wide tables/panels from dragging
       the page sideways. Switching to position:fixed on mobile sidesteps
       that entirely. z-index 170 keeps it above the off-canvas sidebar
       drawer (160) and its overlay (150), so the hamburger button stays
       reachable even while the drawer is open. .dash-wrap's new
       padding-top (above) reserves the 84px the header used to occupy in
       normal flow, so content doesn't render underneath it. */
    .dash-topheader { position: fixed; top: 0; left: 0; right: 0; z-index: 170; }

    /* Off-canvas drawer: same vertical nav list as desktop, slid in from
       the left instead of squeezed into a horizontally-scrolling strip —
       with 15+ admin nav items, scrolling sideways to find one made half
       of them undiscoverable on a phone.
       Bug fix (client report, 2026-08-21) — this used to sit at top:0,
       meaning its first nav items (the "WORKSPACE" group label and
       "Overview" — the two items right above "Revenue" in the admin
       nav) rendered directly underneath the fixed header (.dash-topheader,
       84px tall, higher z-index) with no way to scroll to them: the
       drawer's own internal scroll already starts at its first item, so
       content hidden behind the header at the very top of the box can
       never be scrolled into view — there's nothing "above" position 0 to
       reveal it. Same fix as the desktop fixed sidebar just below in this
       file already uses for the identical reason: top:84px instead of
       top:0, matching .dash-wrap's own padding-top for this breakpoint,
       so the drawer's content starts right below the header instead of
       partly behind it. bottom:0 is unchanged, so the drawer is still the
       full remaining viewport height and scrolls independently to reach
       every item down to the last one. */
    .dash-sidebar {
        position: fixed; top: 84px; left: 0; bottom: 0; z-index: 160;
        width: 78vw; max-width: 300px; overflow-y: auto;
        transform: translateX(-100%); transition: transform var(--transition);
        box-shadow: var(--shadow-lg);
    }
    .dash-sidebar.is-open { transform: translateX(0); }
    .dash-nav a { border-left: 3px solid transparent; white-space: normal; }
    .dash-nav a.is-active { border-left-color: var(--mustard-500); border-bottom: none; }
    .dash-sidebar__label { display: block; }


    .dash-topheader__logo { width: auto; padding: 0 16px; display: flex; align-items: center; }
    .dash-topheader__bar { padding: 0 16px; }
    .header-actions { gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
    .header-actions .pill { display: none; }
    .header-actions .btn { padding: 8px 14px; font-size: 13px; }

    /* Client request — "Find a better way to appear the View Live
       Listing, View Site, and Log out in the mobile top." On a narrow
       phone those three (plus "View live site" + Log out on the admin
       header) had nowhere left to shrink and wrapped onto a second row.
       Every one of them is a duplicate of a link already sitting one tap
       away in the sidebar's "..." account menu (opened via the hamburger
       — .admin-sidebar__profile / .dash-nav--pinned .admin-sidebar__menu
       above), which already had "View site"/"Log out" and now also has
       "View live listing" (see dashboard.php). So on mobile the header
       keeps just the essentials — logo, hamburger, and (for admin) the
       notifications bell — and the link/logout buttons drop out here
       rather than wrapping. Desktop is unaffected; those buttons still
       show in the header there since there's room for them.
       Bug fix (client report, 2026-08-22) — this was originally written
       as a bare ".header-actions .btn" with no scoping ancestor. That's
       supposed to mean "inside the dashboard's .dash-topheader", but
       .header-actions is also the class name the PUBLIC site header
       (partials/header.php, .site-header--public) uses for its own,
       completely unrelated "Add Your Listing" button + mobile-menu-toggle
       row — and a bare descendant selector doesn't care which ancestor it
       is, so this was matching there too, hiding "Add Your Listing" on
       every public page at this same breakpoint. Scoped to .dash-topheader
       (the element .header-actions/.header-actions--admin actually sits
       inside on the dashboard, per .dash-topheader__bar above) so this
       only ever touches the dashboard header it was written for. */
    .dash-topheader .header-actions .btn,
    .dash-topheader .header-actions form { display: none; }

    .stat-grid { grid-template-columns: repeat(2, 1fr); }
    .plan-picker { grid-template-columns: 1fr; }
    .thumb-grid { grid-template-columns: repeat(2, 1fr); }
    .dash-content { padding: 24px 18px; }

    /* Tablet range (641–980px): still a real table, just horizontally
       scrollable inside its own wrapper instead of being clipped, with a
       visible momentum-scroll on iOS. The table gets a min-width so its
       columns keep a readable size rather than being squeezed illegibly
       thin. Below 640px this is superseded by the card layout further
       down this file, which turns each row into a stacked card instead —
       see that block's comment for why. */
    .table-scroll { -webkit-overflow-scrolling: touch; }
    .table-scroll .data-table { min-width: 640px; }
}

/* ---------------------------------------------------------------------
   Mobile data tables → stacked cards. A table simply shrunk to fit a
   phone screen — even with the horizontal-scroll fallback above — still
   forces every column into a sliver of its normal width, wrapping names,
   emails and phone numbers into an unreadable ladder of 3-4 characters
   per line. Below 640px (phones; tablets above this width keep the
   regular scrollable table from the 980px block above), every
   `<table class="data-table">` sitewide is restyled so each `<tr>`
   becomes its own card and each `<td>` becomes a labeled line inside it —
   label above, value below, exactly like a mobile order receipt. No
   markup change is required for the table/tr/td structure itself; each
   `<td>` just needs a `data-label="Column Name"` attribute (matching
   its `<th>`) for the label line to appear via the ::before rule below.
   A `<td>` with no data-label (e.g. a lone actions cell where buttons are
   already self-explanatory) still renders, just without a label line.
   --------------------------------------------------------------------- */
@media (max-width: 640px) {
    .table-scroll { overflow-x: visible; }
    /* The tablet rule above (.table-scroll .data-table { min-width: 640px })
       has two classes vs. this block's one, so it out-specifies a plain
       .data-table override regardless of source order — it was still
       forcing every card table to a 640px floor on phones, pushing content
       off the right edge. Matching its exact selector here is what actually
       resets it. */
    .table-scroll .data-table { min-width: 0; }
    .data-table, .data-table thead, .data-table tbody, .data-table tr, .data-table td {
        display: block; width: 100%; min-width: 0;
    }
    .data-table thead { display: none; }
    .data-table tr:hover td { background: transparent; }
    .data-table tr {
        background: var(--white); border: 1px solid var(--ink-100); border-radius: var(--radius-md);
        padding: 4px 16px; margin-bottom: 14px; box-shadow: var(--shadow-sm);
    }
    .data-table td {
        padding: 11px 0; border-bottom: 1px solid var(--ink-100);
        text-align: left; white-space: normal; overflow-wrap: anywhere; vertical-align: top;
    }
    .data-table td:last-child { border-bottom: none; }
    .data-table td[data-label]::before {
        content: attr(data-label); display: block; margin-bottom: 5px;
        font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-500);
    }
    /* Dropdowns, text inputs and button groups fill the card's width
       instead of sitting at their cramped intrinsic size. */
    .data-table td select, .data-table td input { width: 100%; }
    .data-table td .inline-form { display: flex; flex: 1 1 auto; }
    .data-table td .inline-form .btn,
    .data-table td .inline-form .icon-btn { flex: 1; text-align: center; }
    .data-table td[data-label="Actions"], .data-table td.cell-actions {
        display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
    }
    .data-table td[data-label="Actions"] > *, .data-table td.cell-actions > * { flex: 1 1 auto; }

    /* Client request (2026-08-21) — "MOBILE = SUMMARY FIRST + EXPAND FOR
       DETAILS." Extends the stacked-card table above (which already turns
       every row into a card and every <td> into a labeled line) so a row
       can show only its 2-4 most identifying fields by default, with
       everything else tucked behind a per-row "View details ˅ / Hide
       details ˄" toggle — same idea as .plan-mcard__features' accordion
       on Billing & Plans, just reused here for <table> rows instead of a
       bespoke card, since this one component (see the shared *_row_html()
       helpers in helpers.php, and assets/js/main.js's data-table-toggle
       module) already backs ~20 tables across the admin and business
       dashboards.
       Opt-in per page: a <td> is only ever hidden if the SAME <tr> also
       has at least one <td data-summary> marking which fields stay
       visible — a table nobody has annotated yet (data-summary not used
       anywhere in it) is completely unaffected and keeps showing every
       field exactly as before, so this rolls out one page at a time with
       zero risk to pages not yet updated. The actual toggle control is
       injected by JS (main.js), not written here, since it needs to
       appear once per row and only on rows that actually have something
       to hide — see that file for the injection logic. */
    /* :not(.data-table__toggle-cell) matters here — without it, this
       selector (being a plain <td> with no data-summary attribute of its
       own) also matches the toggle cell that JS just appended, hiding the
       "View details" control before it can ever be tapped to reveal it. */
    .data-table tr[data-dt-enhanced] td:not([data-summary]):not(.data-table__toggle-cell) { display: none; }
    .data-table tr[data-dt-enhanced].is-expanded td:not([data-summary]):not(.data-table__toggle-cell) { display: block; }
    .data-table__toggle-cell { padding: 0 !important; border-bottom: none !important; }
    .data-table__toggle {
        display: flex; align-items: center; justify-content: center; gap: 6px;
        width: 100%; padding: 11px 0; margin-top: 2px;
        border: none; border-top: 1px solid var(--ink-100); background: none;
        font-size: 12.5px; font-weight: 700; color: var(--ink-500);
        text-transform: uppercase; letter-spacing: .03em; cursor: pointer;
    }
    .data-table__toggle-open { display: none; }
    tr.is-expanded .data-table__toggle-open { display: inline; }
    tr.is-expanded .data-table__toggle-closed { display: none; }
}
/* Toggle cell only exists (injected by JS) on rows with hidden detail
   fields — hidden outside the mobile breakpoint above since desktop/
   tablet already show every field in the regular table, nothing to
   expand there. */
.data-table__toggle-cell { display: none; }

/* Revision #1 (mobile overflow) — on genuinely narrow phones, 2 stat cards
   per row still has enough room for the number but not always the label
   ("Active subscriptions", "Reviews awaiting moderation"), so it drops to
   a single column instead of letting a long label force its card wider
   than the others and stretch the row. */
@media (max-width: 460px) {
    .stat-grid { grid-template-columns: 1fr; }
}

/* Prevents the page behind the open drawer from scrolling along with it. */
body.dash-nav-locked { overflow: hidden; }

/* ---------------------------------------------------------------------
   Lightbox — click any [data-lightbox] image (banner previews, uploaded
   photos, cover images) to see it full-size. One overlay is created once
   and reused for every image; see initLightbox() in main.js.
   --------------------------------------------------------------------- */
[data-lightbox] { cursor: zoom-in; }
.lightbox-overlay {
    display: none; position: fixed; inset: 0; z-index: 200;
    background: rgba(16,31,24,0.92); padding: 40px 24px;
    align-items: center; justify-content: center;
}
.lightbox-overlay.is-open { display: flex; }
.lightbox-overlay__img {
    max-width: min(1100px, 100%); max-height: 100%; width: auto; height: auto;
    border-radius: var(--radius-md); box-shadow: var(--shadow-lg); cursor: default;
    object-fit: contain;
}
.lightbox-overlay__close {
    position: absolute; top: 20px; right: 24px; width: 44px; height: 44px; border-radius: 50%;
    background: rgba(255,255,255,0.12); color: var(--white); border: none; font-size: 22px;
    display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background var(--transition);
}
.lightbox-overlay__close:hover { background: rgba(255,255,255,0.22); }

@media (max-width: 640px) {
    .lightbox-overlay { padding: 20px 14px; }
    .lightbox-overlay__close { top: 10px; right: 10px; width: 38px; height: 38px; font-size: 18px; }
}

/* =====================================================================
   Admin / Super Admin console redesign (client update #19)
   Everything below is scoped to .dash-role-admin / .dash-role-super_admin
   so the Business Owner and Sales Rep dashboards (which share the same
   .dash-sidebar / .dash-topheader / .stat-card / .dash-panel markup and
   classes above) are completely unaffected.
   ===================================================================== */
.btn--dark { background: var(--admin-active); color: var(--white); }
.btn--dark:hover { background: var(--admin-active-hover); }

.dash-role-admin .dash-wrap,
.dash-role-super_admin .dash-wrap { grid-template-columns: 272px 1fr; background: var(--admin-cream); }
.dash-role-admin .dash-topheader__logo,
.dash-role-super_admin .dash-topheader__logo {
    width: 272px; background: linear-gradient(180deg, #d2e3d9 0%, #bfd6ca 100%);
    border-bottom: 1px solid #aec9ba;
}
.dash-role-admin .dash-content,
.dash-role-super_admin .dash-content { max-width: none; padding: 32px 40px 48px; }
.dash-role-admin .dash-content,
.dash-role-super_admin .dash-content,
.dash-role-admin .dash-wrap,
.dash-role-super_admin .dash-wrap { font-family: var(--admin-font); color: var(--admin-ink); }

/* Brand lockup (shield + wordmark) that replaces the plain pin logo only
   inside the admin console's topheader panel — light sage bg, dark text,
   matching the canonical package's .brand-mark/.brand-copy. */
.admin-brand { display: flex; align-items: center; gap: 10px; color: #173b31; }
.admin-brand__icon { color: var(--admin-forest); display: inline-flex; }
.admin-brand__icon svg { width: 26px; height: 26px; }
.admin-brand__name { display: block; font-family: var(--admin-font); font-weight: 600; font-size: 17px; letter-spacing: -.15px; line-height: 1.2; }
.admin-brand__sub { display: block; font-size: 12px; color: #567566; line-height: 1.2; }

/* Search bar + icon button + "View live site" replace the role-pill/
   view-site/logout row the other two roles still use in .dash-topheader__bar. */
.dash-role-admin .dash-topheader__bar,
.dash-role-super_admin .dash-topheader__bar { gap: 20px; }
.admin-search {
    flex: 1; display: flex; align-items: center; gap: 10px; max-width: 440px;
    background: var(--admin-white); border: 1px solid #e3dfd5; border-radius: var(--radius-full);
    padding: 10px 16px; color: var(--admin-muted);
}
.admin-search__icon { flex-shrink: 0; width: 17px; height: 17px; }
.admin-search input { flex: 1; border: none; background: none; font-size: 14px; color: var(--admin-ink); min-width: 0; }
.admin-search input::placeholder { color: var(--admin-muted); }
.admin-search input:focus { outline: none; }
.admin-search__kbd {
    flex-shrink: 0; font-size: 11px; font-weight: 700; color: var(--admin-muted);
    background: var(--admin-white); border: 1px solid var(--admin-line); border-radius: 6px; padding: 2px 7px;
}
.header-actions--admin { gap: 12px; }
.admin-icon-btn {
    width: 38px; height: 38px; border-radius: 50%; border: 1px solid #d9d5ca; background: var(--admin-white);
    display: inline-flex; align-items: center; justify-content: center; color: var(--admin-ink); cursor: pointer;
    position: relative; flex-shrink: 0; transition: background var(--transition);
}
.admin-icon-btn:hover { background: #faf8f3; }
.admin-icon-btn svg { width: 18px; height: 18px; }

/* Sidebar: dark column, grouped nav, badges, profile card pinned to the
   bottom — genuinely position:fixed (desktop only, 901px+; mobile keeps
   the existing slide-in drawer, which was already position:fixed for the
   same reason below) so it stays completely stationary while the page
   content scrolls, no matter what. position:sticky was tried here first
   and looked right in isolation, but sticky silently stops engaging the
   moment ANY ancestor between the sidebar and <html> ends up with a
   non-visible computed overflow — and <html> itself already carries
   overflow-x:hidden (see the .site-header--public comment above), which
   is exactly the failure mode that forces the mobile drawer below to use
   fixed instead of sticky too. Fixed is computed purely against the
   viewport and is completely immune to that.
   Fixed positioning removes .dash-sidebar from the grid's normal item
   flow, so .dash-content is pinned to the second explicit grid-template-
   columns track below (grid-column: 2) — otherwise it would silently
   auto-place itself into the now-vacant first track and render underneath
   the sidebar instead of beside it. Generalized to every role's
   .dash-sidebar/.dash-nav (was admin-only) so this applies to every
   dashboard, not just the admin console — each role's own width below
   must stay equal to that role's .dash-wrap grid-template-columns first
   track (240px by default, 272px for admin/super_admin — see those rules
   elsewhere in this file). */
.dash-role-admin .dash-sidebar,
.dash-role-super_admin .dash-sidebar {
    background: linear-gradient(180deg, #d2e3d9 0%, #bfd6ca 100%);
    border-right: 1px solid #aec9ba; box-shadow: 10px 0 35px rgba(31, 78, 60, .07);
    padding: 18px 0 0;
}
@media (min-width: 901px) {
    /* Desktop counterpart to the mobile "header stays fixed" fix below
       (same position:sticky-silently-fails-under-overflow-x:hidden root
       cause — see that comment) — the logo/search/profile bar at the very
       top must stay stationary while only the page content scrolls, not
       just look sticky. .dash-wrap's new padding-top reserves the 84px the
       header used to occupy in normal flow so content doesn't render
       underneath it; same z-index as the mobile fixed header so the two
       rules behave identically at the breakpoint. */
    .dash-topheader { position: fixed; top: 0; left: 0; right: 0; z-index: 170; }
    .dash-wrap { padding-top: 84px; }
    .dash-sidebar {
        /* No overflow:hidden here — .dash-nav below already scrolls its own
           content within the flex column (flex:1 + overflow-y:auto), so
           this container doesn't need to clip anything itself. Clipping
           here was cutting off the profile "..." dropdown menu
           (.admin-sidebar__menu-panel), which intentionally pops outside
           the sidebar's box when opened. */
        position: fixed; top: 84px; left: 0; bottom: 0; width: 240px; z-index: 40;
        display: flex; flex-direction: column;
    }
    .dash-role-admin .dash-sidebar,
    .dash-role-super_admin .dash-sidebar { width: 272px; }
    .dash-content { grid-column: 2; }
    /* Desktop-only counterpart to the body.is-impersonating rules above
       .impersonation-banner — scoped inside this media query (rather than
       written unconditionally) so it can't also match .dash-sidebar's
       separate mobile off-canvas-drawer rule, which is meant to stay at
       top:0 (it's a full-height overlay, not offset from the header). */
    body.is-impersonating .dash-sidebar { top: 139px; }
}
.dash-nav { flex: 1; overflow-y: auto; padding-bottom: 12px; scrollbar-gutter: stable; }
/* Client request — "My Account" (business_owner/sales_rep sidebar) stays
   stationary instead of scrolling away with the rest of the nav list. This
   second, separate .dash-nav block (see layouts/dashboard.php) reuses every
   .dash-nav a / .dash-role-business_owner .dash-nav a rule for identical
   look and hover/active states, but turns off the flex-grow + scrolling
   that make the FIRST .dash-nav block scroll — .dash-sidebar is already a
   flex column (see the desktop and mobile-drawer rules for .dash-sidebar
   elsewhere in this file), so a non-growing sibling after the scrollable
   nav naturally sits pinned at the bottom, the same way .admin-sidebar__
   profile already does for admin/super_admin. */
.dash-nav--pinned {
    flex: 0 0 auto; overflow: visible; padding-bottom: 0; margin-top: auto;
    border-top: 1px solid rgba(255,255,255,.12); padding-top: 6px;
}
/* Client request — the business_owner/sales_rep pinned footer should look
   exactly like the admin sidebar's own avatar/name/role/"..." profile row
   (.admin-sidebar__profile, defined further down) instead of a plain link.
   layouts/dashboard.php now reuses those same classes here directly. Their
   existing colors (dark text) already read correctly as-is for admin AND
   business_owner, since business_owner's sidebar uses the identical light
   sage background — see .dash-role-business_owner .dash-sidebar further
   down. sales_rep has no light-sidebar override of its own, though, so it
   falls through to the base dark sidebar (.dash-sidebar { background:
   var(--forest-800) }); this override flips the profile row's colors to
   light-on-dark for that one case only, the same light/dark split every
   other sidebar element here already handles per role. */
.dash-role-sales_rep .admin-sidebar__profile { border-top-color: rgba(255,255,255,.12); }
.dash-role-sales_rep .admin-sidebar__avatar { background: var(--mustard-500); color: #183c31; }
.dash-role-sales_rep .admin-sidebar__name { color: var(--cream-100); }
.dash-role-sales_rep .admin-sidebar__role { color: var(--cream-100); opacity: .65; }
.dash-role-sales_rep .admin-sidebar__menu summary { color: var(--cream-100); opacity: .78; }
.dash-role-sales_rep .admin-sidebar__menu summary:hover { opacity: 1; background: rgba(255,255,255,.1); }
.dash-role-sales_rep .admin-sidebar__menu[open] summary { background: rgba(255,255,255,.1); }
.admin-nav .dash-sidebar__label { margin: 20px 0 6px; color: #537565; }
.admin-nav .dash-sidebar__label:first-child { margin-top: 4px; }
.admin-nav a { position: relative; border-radius: 0; color: #355f4d; opacity: 1; }
.admin-nav a:hover { background: rgba(255,253,249,.62); color: #164e3d; }
.admin-nav a svg { flex-shrink: 0; width: 18px; height: 18px; }
.admin-nav__label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-nav__badge {
    flex-shrink: 0; font-size: 11px; font-weight: 700; color: #365f4f;
    background: rgba(255,255,255,.45); border-radius: var(--radius-full); padding: 2px 8px; min-width: 22px; text-align: center;
}
.dash-nav__badge--warn { background: var(--admin-gold); color: #fff; opacity: 1; }
.admin-nav a.is-active .dash-nav__badge:not(.dash-nav__badge--warn) { background: rgba(255,255,255,.16); color: #fff; opacity: 1; }
.dash-role-admin .dash-nav a.is-active,
.dash-role-super_admin .dash-nav a.is-active {
    background: var(--admin-active); color: #fff; opacity: 1; font-weight: 600;
    border-left-color: var(--admin-gold-deep); box-shadow: 0 8px 20px rgba(29, 93, 73, .18);
}

.admin-sidebar__profile {
    display: flex; align-items: center; gap: 10px; padding: 14px 18px; margin-top: auto;
    border-top: 1px solid #a9c5b5; flex-shrink: 0;
}
.admin-sidebar__avatar {
    width: 34px; height: 34px; border-radius: 50%; background: var(--admin-active); color: #fff;
    display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px; flex-shrink: 0;
}
.admin-sidebar__who { flex: 1; min-width: 0; }
.admin-sidebar__name { display: block; font-size: 13.5px; font-weight: 700; color: #183c31; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-sidebar__role { display: block; font-size: 11.5px; color: #587668; }
.admin-sidebar__menu { position: relative; flex-shrink: 0; }
.admin-sidebar__menu summary {
    list-style: none; width: 26px; height: 26px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
    color: #587668; cursor: pointer;
}
.admin-sidebar__menu summary::-webkit-details-marker { display: none; }
.admin-sidebar__menu summary:hover { color: #183c31; background: rgba(255,255,255,.35); }
.admin-sidebar__menu summary svg { width: 16px; height: 16px; }
.admin-sidebar__menu-panel {
    position: absolute; bottom: calc(100% + 8px); left: 0; min-width: 160px; background: var(--admin-white);
    border-radius: var(--radius-md); box-shadow: var(--shadow-lg); padding: 6px; display: flex; flex-direction: column; z-index: 20;
}
.admin-sidebar__menu-panel a,
.admin-sidebar__menu-panel button {
    display: flex; align-items: center; gap: 8px; padding: 9px 10px; border-radius: 8px; font-size: 13.5px; font-weight: 600;
    color: var(--admin-ink); background: none; border: none; text-align: left; width: 100%; cursor: pointer;
}
.admin-sidebar__menu-panel a:hover,
.admin-sidebar__menu-panel button:hover { background: #faf8f3; }
.admin-sidebar__menu-panel svg { width: 14px; height: 14px; }
.admin-sidebar__menu[open] summary { background: rgba(255,255,255,.35); }

/* Found while adding "View live listing" to this menu for mobile (see
   .header-actions .btn/form above) — the "..." account menu panel opens
   anchored to its button's LEFT edge and grows rightward, which is fine
   on desktop where the sidebar doesn't clip overflow, but on the
   off-canvas drawer (.dash-sidebar { overflow-y: auto } in the
   max-width:980px block, which implicitly clips overflow-x too) that
   pushed the panel's right side past the drawer's edge and cut it off —
   exactly where "View live listing"/"View site"/"Log out" live.
   Anchoring it to the button's RIGHT edge instead on mobile, so it opens
   leftward into the drawer's own width, keeps it fully visible. Placed
   here, after the base .admin-sidebar__menu-panel rule above (rather
   than inside that earlier max-width:980px block), because equal-
   specificity CSS rules resolve by source order — a media-query-scoped
   rule positioned BEFORE an unconditional rule of the same specificity
   silently loses to it at every screen width, the same bug already found
   and fixed once this project for .dash-wrap. */
@media (max-width: 980px) {
    .admin-sidebar__menu-panel { left: auto; right: 0; }
}

/* Overview page header */
.admin-overview__head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-bottom: 28px; }
.admin-overview__date { font-size: 12px; font-weight: 600; letter-spacing: .07em; color: var(--admin-muted); margin-bottom: 6px; }
.admin-overview__head h1 { font-family: var(--admin-font); font-size: 34px; font-weight: 600; letter-spacing: -1.15px; color: #192e32; }
.admin-overview__sub { color: var(--admin-muted); font-size: 15px; margin-top: 4px; }
.admin-overview__actions { display: flex; gap: 10px; flex-wrap: wrap; }
.admin-overview__actions .btn svg { width: 15px; height: 15px; }

/* Stat cards */
/* 5 columns (was 4) — Overview gained an "Artisans on the site" tile
   (client request); the two responsive breakpoints below already collapse
   to 2 then 1 column and need no change to accommodate the 5th tile. */
.admin-stat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 18px; margin-bottom: 24px; }
.admin-stat-card { background: var(--admin-white); border: 1px solid var(--admin-line); border-radius: var(--radius-lg); padding: 20px; box-shadow: 0 12px 32px rgba(27, 48, 56, .055); }
.admin-stat-card__top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }
.admin-stat-card__label { font-size: 13px; color: var(--admin-muted); font-weight: 600; }
.admin-stat-card__icon--light {
    width: 34px; height: 34px; border-radius: 50%; background: #e4eee8; color: #1c6953;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.admin-stat-card__icon--light svg { width: 17px; height: 17px; }
.admin-stat-card__value { font-family: var(--admin-font); font-size: 34px; font-weight: 600; line-height: 1; margin-bottom: 10px; color: var(--admin-ink); }
.admin-stat-card__foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 20px; }
.admin-stat-card__meta { font-size: 12.5px; color: var(--admin-muted); }
.admin-delta { display: inline-flex; align-items: center; gap: 3px; font-size: 12.5px; font-weight: 700; }
.admin-delta--up { color: var(--admin-forest); }
.admin-delta--down { color: #9b4c3f; }
.admin-delta svg { width: 13px; height: 13px; }

.admin-stat-card--dark {
    background: linear-gradient(145deg, #1f725b, #174d40);
    color: var(--admin-white); border: 1px solid #1f725b; position: relative; overflow: hidden;
}
.admin-stat-card--dark .admin-stat-card__label { color: var(--admin-white); opacity: .8; }
.admin-stat-card--dark .admin-stat-card__value { color: var(--admin-white); }
.admin-stat-card--dark .admin-stat-card__meta { color: var(--admin-white); opacity: .7; }
.admin-stat-card--dark .admin-delta--up { color: #f0d594 !important; }
.admin-stat-card--dark .admin-stat-card__icon--light { background: rgba(214,179,108,.14); color: #e4c47f; }
.admin-stat-card__icon {
    width: 30px; height: 30px; border-radius: 50%; background: rgba(255,255,255,0.14);
    display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; flex-shrink: 0; color: #e4c47f;
}
.admin-sparkline { display: flex; align-items: flex-end; gap: 3px; height: 24px; margin-left: auto; }
.admin-sparkline span { width: 5px; background: var(--admin-gold-deep); opacity: .85; border-radius: 2px 2px 0 0; min-height: 3px; }
.admin-sparkline span:last-child { opacity: 1; }

/* Two-column panel rows */
.admin-grid-2 { display: grid; grid-template-columns: 1.6fr 1fr; gap: 18px; align-items: start; margin-bottom: 18px; }
.admin-panel { background: var(--admin-white); border: 1px solid var(--admin-line); border-radius: var(--radius-lg); padding: 22px; box-shadow: 0 12px 32px rgba(27, 48, 56, .055); }
.admin-panel h2 { font-size: 18px; font-weight: 600; color: var(--admin-ink); }
.admin-panel__sub { font-size: 13px; color: var(--admin-muted); margin-top: 2px; }
/* Bug fix (client report, 2026-08-21, real mobile screenshots — "Program
   submissions" panel/search box on Admin > Assistance showed cut off at
   the right edge on a phone): this row has no flex-wrap, so on any narrow
   screen where the panel title and its inline search/filter form (see
   Admin > Assistance, Analytics, CMS, Neighbourhoods — every panel that
   puts a search box next to its heading) genuinely don't both fit on one
   line, the search form was squeezed into whatever sliver of space was
   left and its own contents (an unshrinkable text input + button) simply
   overflowed off the right edge instead of visibly wrapping — same root
   cause as the admin top-header search bar bug fixed in an earlier round
   (a flex row with no wrap and no shrink allowance), just a second,
   separate spot in the CSS that fix never reached. flex-wrap: wrap here
   lets the search/filter form drop to its own line below the title
   whenever there isn't room beside it — invisible on desktop, where both
   already fit on one line, so nothing else about this row changes. */
.admin-panel__head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
.admin-panel__count {
    flex-shrink: 0; background: #f5f2eb; color: var(--admin-ink); font-weight: 700; font-size: 12.5px;
    border-radius: var(--radius-full); padding: 3px 10px;
}
.admin-select-form select {
    font-size: 12.5px; font-weight: 600; border: 1px solid var(--admin-line); border-radius: var(--radius-full);
    padding: 6px 12px; color: var(--admin-ink); background: var(--admin-white);
}

/* Revenue chart */
.admin-revenue-summary { display: flex; align-items: baseline; gap: 10px; margin-bottom: 18px; }
.admin-revenue-summary__value { font-family: var(--admin-font); font-size: 26px; font-weight: 600; letter-spacing: -.65px; color: var(--admin-ink); }
.admin-chart { display: flex; gap: 10px; }
.admin-chart__axis { display: flex; flex-direction: column; justify-content: space-between; font-size: 11px; color: var(--admin-muted); height: 180px; padding-bottom: 22px; text-align: right; }
.admin-chart__plot { flex: 1; position: relative; height: 180px; }
.admin-chart__gridline { border-top: 1px dashed var(--admin-line); position: absolute; left: 0; right: 0; }
.admin-chart__plot .admin-chart__gridline:nth-child(1) { top: 0; }
.admin-chart__plot .admin-chart__gridline:nth-child(2) { top: 25%; }
.admin-chart__plot .admin-chart__gridline:nth-child(3) { top: 50%; }
.admin-chart__plot .admin-chart__gridline:nth-child(4) { top: 75%; }
.admin-chart__plot .admin-chart__gridline:nth-child(5) { top: calc(100% - 22px); }
.admin-chart__bars { position: absolute; inset: 0 0 22px 0; display: flex; align-items: flex-end; gap: 14px; padding: 0 4px; }
.admin-chart__col { flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; justify-content: flex-end; position: relative; }
.admin-chart__bar { width: 60%; max-width: 34px; background: #f5f2eb; border-radius: 5px 5px 0 0; transition: background var(--transition); }
.admin-chart__bar.is-current { background: linear-gradient(to top, #1c634f, #60917c); }
.admin-chart__label { position: absolute; bottom: -22px; font-size: 11.5px; color: var(--admin-muted); }
.admin-chart__label.is-current { color: var(--admin-ink); font-weight: 700; }

/* Needs attention list */
.admin-attention-list { display: flex; flex-direction: column; gap: 4px; }
.admin-attention-item {
    display: flex; align-items: center; gap: 12px; padding: 10px 8px; border-radius: var(--radius-md);
    color: inherit; transition: background var(--transition);
}
.admin-attention-item:hover { background: #f5f2eb; }
.admin-attention-item__icon {
    width: 34px; height: 34px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.admin-attention-item__icon svg { width: 16px; height: 16px; }
.admin-attention-item__icon--amber { background: #f5ead4; color: #8b682e; }
.admin-attention-item__icon--blue { background: #e6eefb; color: #2454a6; }
.admin-attention-item__icon--red { background: #f5e3df; color: #9b4c3f; }
.admin-attention-item__body { flex: 1; min-width: 0; }
.admin-attention-item__title { display: block; font-size: 13.5px; font-weight: 700; color: var(--admin-ink); }
.admin-attention-item__subtitle { display: block; font-size: 12px; color: var(--admin-muted); margin-top: 1px; }
.admin-attention-item__count {
    flex-shrink: 0; background: #f5f2eb; color: var(--admin-ink); font-weight: 700; font-size: 11.5px;
    border-radius: var(--radius-full); padding: 2px 8px; min-width: 22px; text-align: center;
}
.admin-attention-item__chevron { flex-shrink: 0; width: 16px; height: 16px; color: #b7c2bc; }

/* Platform health */
.admin-status-pill {
    flex-shrink: 0; display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700;
    border-radius: var(--radius-full); padding: 5px 12px;
}
.admin-status-pill--green { background: #e1eee7; color: #1b654f; }
.admin-status-pill--amber { background: #f5ead4; color: #8b682e; }
.admin-status-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.admin-health { display: flex; align-items: center; gap: 18px; }
.admin-health__label { font-family: var(--admin-font); font-size: 19px; font-weight: 600; color: var(--admin-ink); }
.admin-health__sub { font-size: 13px; color: var(--admin-muted); margin-top: 2px; }

.dash-empty { color: var(--admin-muted); font-size: 14px; padding: 18px 0; text-align: center; }

@media (max-width: 1180px) {
    .admin-stat-grid { grid-template-columns: repeat(2, 1fr); }
    .admin-grid-2 { grid-template-columns: 1fr; }
}
@media (max-width: 900px) {
    .dash-role-admin .dash-topheader__logo,
    .dash-role-super_admin .dash-topheader__logo { width: auto; }
    /* Mobile responsiveness fix (client report) — .admin-search used to
       keep its full preferred width here (placeholder text + icon + ⌘K
       badge) with nothing forcing it to actually shrink: a flex item's
       default min-width is "auto" (its own content's minimum size), not
       0, so flex-shrink alone doesn't help unless that's overridden. On a
       phone that meant .admin-search (plus the bell/View live site/Log
       out buttons next to it) genuinely didn't fit in the remaining width
       next to the logo — and because .dash-topheader__bar right-aligns
       its content (justify-content: flex-end), the overflow silently
       bled off the LEFT edge of the screen instead of wrapping or
       clipping visibly, so the search box rendered half off-screen and
       sat on top of the hamburger menu button, making it unclickable.
       min-width: 0 here lets it actually shrink on a tablet-width screen;
       flex-wrap below is a safety net if the row still doesn't fit; fully
       hiding it below 640px (next rule) is what actually fixes a phone,
       where there just isn't enough width for a search box, an icon
       button, and two more buttons on one line no matter how much
       shrinking is allowed — the Businesses page's own search still
       covers the same search from a phone. */
    .admin-search { max-width: none; min-width: 0; }
    .admin-search__kbd { display: none; }
    .dash-topheader__bar { flex-wrap: wrap; row-gap: 8px; }
}
@media (max-width: 640px) {
    .admin-search { display: none; }
}
@media (max-width: 640px) {
    .admin-stat-grid { grid-template-columns: 1fr; }
    .admin-overview__head { flex-direction: column; align-items: flex-start; }
    .admin-chart__axis { display: none; }
}

/* ---------------------------------------------------------------------
   Admin console redesign, part 2 — every other admin page (Users,
   Businesses, Revenue, Settings, and 25+ more) is built from the same
   shared components the Overview page above uses: .dash-topbar page
   headers, .dash-panel cards, .stat-card tiles, .data-table, .field form
   inputs, .admin-filter-grid, .tab-row. Restyling those components once,
   scoped to the admin role, means every one of those pages inherits the
   new look automatically — instead of hand-rewriting 30+ individual view
   files (which have no mockup of their own to build against). Nothing
   here changes markup, routes, or behaviour on any of those pages.
   --------------------------------------------------------------------- */
.dash-role-admin .dash-topbar,
.dash-role-super_admin .dash-topbar { margin-bottom: 26px; }
.dash-role-admin .dash-topbar h1,
.dash-role-super_admin .dash-topbar h1 { font-family: var(--admin-font); font-size: 28px; font-weight: 600; color: #192e32; }
.dash-role-admin .dash-topbar p,
.dash-role-super_admin .dash-topbar p { color: var(--admin-muted); font-size: 14.5px; margin-top: 4px; }

.dash-role-admin .dash-panel,
.dash-role-super_admin .dash-panel {
    background: var(--admin-white); border-color: var(--admin-line); border-radius: var(--radius-lg); padding: 22px;
    box-shadow: 0 12px 32px rgba(27, 48, 56, .055);
}
.dash-role-admin .dash-panel h2,
.dash-role-super_admin .dash-panel h2 { font-size: 17px; font-weight: 600; color: var(--admin-ink); }

.dash-role-admin .stat-card,
.dash-role-super_admin .stat-card {
    background: var(--admin-white); border-color: var(--admin-line); border-radius: var(--radius-lg); padding: 20px;
    box-shadow: 0 12px 32px rgba(27, 48, 56, .055);
}
.dash-role-admin .stat-card__label,
.dash-role-super_admin .stat-card__label { font-weight: 700; color: var(--admin-muted); }
.dash-role-admin .stat-card__value,
.dash-role-super_admin .stat-card__value { font-size: 30px; font-weight: 600; color: var(--admin-ink); }

/* Revenue page — Total revenue / Monthly revenue call-out colors.
   Total revenue's dark green matches the "Revenue this month" hero card
   on the Overview page exactly (see .admin-stat-card--dark's gradient
   above). Monthly revenue uses the brand's brighter solid green
   (--accent-green) — a lighter, more vivid green than Total revenue's
   dark gradient, so the two cards read as a light/dark pair of the same
   hue rather than two unrelated colors. */
.dash-role-admin .stat-card--green,
.dash-role-super_admin .stat-card--green {
    background: linear-gradient(145deg, #1f725b, #174d40); border-color: #1f725b;
}
.dash-role-admin .stat-card--green .stat-card__label,
.dash-role-super_admin .stat-card--green .stat-card__label { color: var(--admin-white); opacity: .8; }
.dash-role-admin .stat-card--green .stat-card__value,
.dash-role-super_admin .stat-card--green .stat-card__value { color: var(--admin-white); }
.dash-role-admin .stat-card--green .stat-card__meta,
.dash-role-super_admin .stat-card--green .stat-card__meta { color: var(--admin-white); opacity: .8; }

/* Matches the sidebar nav's own background exactly (see .dash-role-admin
   .dash-sidebar above) rather than a brand accent color, per request —
   dark text instead of white since this tint is too light for white to
   read on. */
.dash-role-admin .stat-card--green-light,
.dash-role-super_admin .stat-card--green-light {
    background: linear-gradient(180deg, #d2e3d9 0%, #bfd6ca 100%); border-color: #aec9ba;
}
.dash-role-admin .stat-card--green-light .stat-card__label,
.dash-role-super_admin .stat-card--green-light .stat-card__label { color: #355f4d; opacity: .85; }
.dash-role-admin .stat-card--green-light .stat-card__value,
.dash-role-super_admin .stat-card--green-light .stat-card__value { color: #183c31; }
.dash-role-admin .stat-card--green-light .stat-card__meta,
.dash-role-super_admin .stat-card--green-light .stat-card__meta { color: #355f4d; opacity: .85; }

.dash-role-admin .data-table th,
.dash-role-super_admin .data-table th { background: #f5f2eb; border-bottom: none; border-radius: 0; color: var(--admin-muted); }
.dash-role-admin .data-table th:first-child,
.dash-role-super_admin .data-table th:first-child { border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
.dash-role-admin .data-table th:last-child,
.dash-role-super_admin .data-table th:last-child { border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
.dash-role-admin .data-table td,
.dash-role-super_admin .data-table td { color: var(--admin-ink); border-bottom-color: var(--admin-line); }
.dash-role-admin .data-table tr:hover td,
.dash-role-super_admin .data-table tr:hover td { background: #faf8f3; }

.dash-role-admin .field input,
.dash-role-super_admin .field input,
.dash-role-admin .field select,
.dash-role-super_admin .field select,
.dash-role-admin .field textarea,
.dash-role-super_admin .field textarea { border-radius: var(--radius-sm); border-color: #ddd9cf; background: #faf8f3; color: var(--admin-ink); }
.dash-role-admin .field input:focus,
.dash-role-super_admin .field input:focus,
.dash-role-admin .field select:focus,
.dash-role-super_admin .field select:focus,
.dash-role-admin .field textarea:focus,
.dash-role-super_admin .field textarea:focus { border-color: #5d8c79; box-shadow: 0 0 0 3px rgba(29, 93, 73, .10); }

.dash-role-admin .admin-filter-grid .field input,
.dash-role-super_admin .admin-filter-grid .field input,
.dash-role-admin .admin-filter-grid .field select,
.dash-role-super_admin .admin-filter-grid .field select { border-radius: var(--radius-sm); border-color: #ddd9cf; background: #faf8f3; }

.dash-role-admin .tab-row,
.dash-role-super_admin .tab-row { border-bottom-color: var(--admin-line); }
.dash-role-admin .tab-row a,
.dash-role-super_admin .tab-row a { color: var(--admin-muted); }
.dash-role-admin .tab-row a.is-active,
.dash-role-super_admin .tab-row a.is-active { color: #1a5f4c; border-bottom-color: var(--admin-coral); }

.dash-role-admin .btn--coral,
.dash-role-super_admin .btn--coral { background: var(--admin-active); }
.dash-role-admin .btn--coral:hover,
.dash-role-super_admin .btn--coral:hover { background: var(--admin-active-hover); }

/* .btn--forest and .btn--ghost are site-wide button classes (also used on
   the public site and other dashboards) — the base rules keep their
   existing colors everywhere else. Scoped here only so the ~30 admin pages
   that already call btn--forest/btn--ghost for primary/secondary actions
   (Save, Export, Back links, etc.) pick up the canonical emerald/porcelain
   look instead of the site's near-black forest or the dark-header-only
   translucent ghost style, with zero changes to any view file. */
.dash-role-admin .btn--forest,
.dash-role-super_admin .btn--forest { background: var(--admin-active); color: var(--white); }
.dash-role-admin .btn--forest:hover,
.dash-role-super_admin .btn--forest:hover { background: var(--admin-active-hover); }
.dash-role-admin .btn--ghost,
.dash-role-super_admin .btn--ghost { background: var(--admin-white); border: 1px solid #d9d5ca; color: var(--admin-ink); }
.dash-role-admin .btn--ghost:hover,
.dash-role-super_admin .btn--ghost:hover { background: #faf8f3; }
.dash-role-admin .icon-btn:hover,
.dash-role-super_admin .icon-btn:hover { background: #f5f2eb; color: var(--admin-forest); }
.dash-role-admin .icon-btn--danger:hover,
.dash-role-super_admin .icon-btn--danger:hover { color: #9b4c3f; }

/* ---------------------------------------------------------------------
   Admin console redesign, part 3 — real per-page work for the list-style
   pages (Users first; Businesses and the rest of the "N results in a
   table" pages reuse the same classes below), matching the approved
   reference mockup's Users page instead of relying only on the shared
   part-2 theming above. Every class here is new markup added to the view
   files themselves (admin_user_row_html() / admin-users.js, etc.) — the
   underlying routes, forms, field names, CSRF tokens and confirm() text
   are untouched; this only changes how the existing data is presented.
   Scoped to .dash-role-admin/.dash-role-super_admin like everything else
   in this file, and every class is admin-prefixed to avoid colliding
   with the generic names layouts/dashboard.php already uses (.sidebar,
   .brand, .panel, .search, .content, .toggle).
   --------------------------------------------------------------------- */
.dash-role-admin .dash-topbar__eyebrow,
.dash-role-super_admin .dash-topbar__eyebrow {
    display: block; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
    color: #9d7440; margin-bottom: 4px;
}
.dash-role-admin .dash-topbar__actions,
.dash-role-super_admin .dash-topbar__actions { display: flex; gap: 10px; flex-wrap: wrap; }

.dash-role-admin .admin-filter-head,
.dash-role-super_admin .admin-filter-head {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 18px; flex-wrap: wrap;
}
.dash-role-admin .admin-filter-head__left,
.dash-role-super_admin .admin-filter-head__left { display: flex; align-items: center; gap: 11px; }
.dash-role-admin .admin-filter-head__icon,
.dash-role-super_admin .admin-filter-head__icon {
    width: 34px; height: 34px; border-radius: 9px; background: #e4eee8; color: var(--admin-active);
    display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0;
}
.dash-role-admin .admin-filter-head strong,
.dash-role-super_admin .admin-filter-head strong { display: block; font-size: 14px; font-weight: 600; color: var(--admin-ink); }
.dash-role-admin .admin-filter-head small,
.dash-role-super_admin .admin-filter-head small { display: block; font-size: 12px; color: var(--admin-muted); margin-top: 2px; }

.dash-role-admin .dash-panel__head p,
.dash-role-super_admin .dash-panel__head p { font-size: 13px; color: var(--admin-muted); margin-top: 2px; }

.dash-role-admin .admin-person-cell,
.dash-role-super_admin .admin-person-cell { display: flex; align-items: center; gap: 10px; }
.dash-role-admin .admin-avatar,
.dash-role-super_admin .admin-avatar {
    width: 32px; height: 32px; border-radius: 9px; color: #fff; font-size: 11px; font-weight: 800;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.dash-role-admin .admin-avatar--clay,   .dash-role-super_admin .admin-avatar--clay   { background: #a65d46; }
.dash-role-admin .admin-avatar--navy,   .dash-role-super_admin .admin-avatar--navy   { background: #2c485d; }
.dash-role-admin .admin-avatar--gold,   .dash-role-super_admin .admin-avatar--gold   { background: #af8a34; }
.dash-role-admin .admin-avatar--forest, .dash-role-super_admin .admin-avatar--forest { background: #426f56; }
.dash-role-admin .admin-person-cell strong,
.dash-role-super_admin .admin-person-cell strong { display: block; font-size: 13.5px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-person-cell small,
.dash-role-super_admin .admin-person-cell small { display: block; font-size: 11.5px; color: var(--admin-muted); margin-top: 1px; }
.dash-role-admin .admin-person-cell a,
.dash-role-super_admin .admin-person-cell a { text-decoration: none; }
.dash-role-admin .admin-person-cell a:hover strong,
.dash-role-super_admin .admin-person-cell a:hover strong { text-decoration: underline; }

.dash-role-admin .admin-contact-cell,
.dash-role-super_admin .admin-contact-cell { min-width: 190px; }
.dash-role-admin .admin-contact-cell strong,
.dash-role-super_admin .admin-contact-cell strong { display: block; font-size: 13px; font-weight: 600; color: var(--admin-ink); }
.dash-role-admin .admin-contact-cell small,
.dash-role-super_admin .admin-contact-cell small { display: block; font-size: 11.5px; color: var(--admin-muted); margin-top: 2px; }

.dash-role-admin .admin-inline-role,
.dash-role-super_admin .admin-inline-role {
    padding: 7px 10px; border-radius: 8px; border: 1px solid var(--admin-line); background: var(--admin-white);
    color: var(--admin-ink); font-size: 12.5px; font-weight: 600;
}

.dash-role-admin .admin-verified-badge,
.dash-role-super_admin .admin-verified-badge {
    display: inline-flex; align-items: center; gap: 3px; padding: 4px 9px; border-radius: 9px;
    background: #e1eee7; color: #1b654f; font-size: 11px; font-weight: 800;
}
.dash-role-admin .admin-verified-badge--grey,
.dash-role-super_admin .admin-verified-badge--grey { background: var(--admin-line); color: var(--admin-muted); }

.dash-role-admin .admin-row-actions,
.dash-role-super_admin .admin-row-actions { position: relative; display: inline-block; text-align: left; }
.dash-role-admin .admin-kebab-btn,
.dash-role-super_admin .admin-kebab-btn {
    list-style: none; width: 28px; height: 28px; border-radius: 7px; display: inline-flex; align-items: center;
    justify-content: center; color: var(--admin-muted); background: #f3f5f2; cursor: pointer; font-weight: 700; font-size: 13px;
}
.dash-role-admin .admin-kebab-btn::-webkit-details-marker,
.dash-role-super_admin .admin-kebab-btn::-webkit-details-marker { display: none; }
.dash-role-admin .admin-row-actions[open] .admin-kebab-btn,
.dash-role-super_admin .admin-row-actions[open] .admin-kebab-btn { background: #e4eee8; color: var(--admin-active); }
.dash-role-admin .admin-kebab-panel,
.dash-role-super_admin .admin-kebab-panel {
    position: absolute; top: calc(100% + 6px); right: 0; min-width: 170px; background: var(--admin-white);
    border: 1px solid var(--admin-line); border-radius: 10px; box-shadow: 0 12px 30px rgba(20, 39, 30, .14);
    padding: 5px; display: flex; flex-direction: column; z-index: 20;
    /* Found while adding Advertisements' new mobile-image edit field
       (2026-08-21) — with two file-upload fields plus their hint text and
       an existing-image thumbnail/"remove" checkbox, that one edit form's
       panel measures ~1000px tall, several times the ~170px the table's
       own overflow reserve below (.table-scroll:has(...)) was tuned for.
       Capping the panel's own height and letting IT scroll internally,
       rather than always relying on outside page space to fit whatever a
       future panel grows to, is the fix that can't quietly break again
       the next time a form gains one more field. */
    max-height: 70vh; overflow-y: auto;
}
.dash-role-admin .admin-kebab-panel .inline-form,
.dash-role-super_admin .admin-kebab-panel .inline-form { display: block; }
.dash-role-admin .admin-kebab-panel button,
.dash-role-admin .admin-kebab-panel a,
.dash-role-super_admin .admin-kebab-panel button,
.dash-role-super_admin .admin-kebab-panel a {
    display: block; width: 100%; text-align: left; padding: 8px 10px; border-radius: 6px; font-size: 12.5px; font-weight: 600;
    color: var(--admin-ink); background: none; border: none; cursor: pointer; text-decoration: none;
}
.dash-role-admin .admin-kebab-panel button:hover,
.dash-role-admin .admin-kebab-panel a:hover,
.dash-role-super_admin .admin-kebab-panel button:hover,
.dash-role-super_admin .admin-kebab-panel a:hover { background: #f5f2eb; }
.dash-role-admin .admin-kebab-panel .admin-danger-action,
.dash-role-super_admin .admin-kebab-panel .admin-danger-action { color: #b54848; }

.dash-role-admin .admin-account-link,
.dash-role-super_admin .admin-account-link { font-weight: 800; font-size: 12.5px; color: var(--admin-active); text-decoration: none; }
.dash-role-admin .admin-account-link:hover,
.dash-role-super_admin .admin-account-link:hover { text-decoration: underline; }

.dash-role-admin .admin-table-footer,
.dash-role-super_admin .admin-table-footer {
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
    padding: 14px 20px; border-top: 1px solid var(--admin-line); font-size: 12px; color: var(--admin-muted);
}

/* Businesses list page (reuses admin-person-cell/admin-avatar/admin-
   row-actions/admin-table-footer from the Users page above) — a few extra
   bits specific to a listing row: owner link/no-owner, featured star,
   subscription badge, and the "Manage" button + kebab combo. */
.dash-role-admin .admin-owner-link,
.dash-role-super_admin .admin-owner-link { font-weight: 700; font-size: 13px; color: var(--admin-active); text-decoration: none; }
.dash-role-admin .admin-owner-link:hover,
.dash-role-super_admin .admin-owner-link:hover { text-decoration: underline; }
.dash-role-admin .admin-owner-email,
.dash-role-super_admin .admin-owner-email { display: block; font-size: 11.5px; color: var(--admin-muted); margin-top: 2px; }
.dash-role-admin .admin-no-owner,
.dash-role-super_admin .admin-no-owner { color: var(--admin-muted); font-size: 12.5px; }

.dash-role-admin .admin-featured-star,
.dash-role-super_admin .admin-featured-star { color: #b88818; margin-left: 3px; }

.dash-role-admin .admin-subscription-badge,
.dash-role-super_admin .admin-subscription-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 700; color: #27734f; }
.dash-role-admin .admin-subscription-badge::before,
.dash-role-super_admin .admin-subscription-badge::before { content: '●'; font-size: 8px; }
.dash-role-admin .admin-subscription-badge--none,
.dash-role-super_admin .admin-subscription-badge--none { color: var(--admin-muted); }

.dash-role-admin .admin-table-actions-inline,
.dash-role-super_admin .admin-table-actions-inline { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.dash-role-admin .admin-manage-btn,
.dash-role-super_admin .admin-manage-btn {
    display: inline-flex; align-items: center; padding: 7px 12px; border-radius: 8px; border: 1px solid #c8d8d0;
    background: var(--admin-white); color: var(--admin-active); font-size: 13px; font-weight: 700; text-decoration: none;
}
.dash-role-admin .admin-manage-btn:hover,
.dash-role-super_admin .admin-manage-btn:hover { background: #f5f2eb; }

/* Events page — card grid instead of a table (matches the reference
   mockup's "event board"). Each .admin-event-card also carries the
   .dash-panel class so it inherits that card's admin-scoped background/
   border/radius/shadow above for free; the rules here only add the
   internal card layout. */
.dash-role-admin .admin-event-grid,
.dash-role-super_admin .admin-event-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.dash-role-admin .admin-event-card,
.dash-role-super_admin .admin-event-card { display: flex; flex-direction: column; gap: 10px; margin-bottom: 0; }
.dash-role-admin .admin-event-card__top,
.dash-role-super_admin .admin-event-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.dash-role-admin .admin-event-card__date,
.dash-role-super_admin .admin-event-card__date {
    display: flex; flex-direction: column; align-items: center; justify-content: center; width: 46px; height: 46px;
    border-radius: 10px; background: #f5f2eb; color: var(--admin-ink); flex-shrink: 0;
}
.dash-role-admin .admin-event-card__date strong,
.dash-role-super_admin .admin-event-card__date strong { font-size: 17px; font-weight: 800; line-height: 1; }
.dash-role-admin .admin-event-card__date span,
.dash-role-super_admin .admin-event-card__date span { font-size: 10px; text-transform: uppercase; color: var(--admin-muted); font-weight: 700; }
.dash-role-admin .admin-event-card__title,
.dash-role-super_admin .admin-event-card__title { font-size: 15px; font-weight: 700; margin: 0; }
.dash-role-admin .admin-event-card__title a,
.dash-role-super_admin .admin-event-card__title a { color: var(--admin-ink); text-decoration: none; }
.dash-role-admin .admin-event-card__title a:hover,
.dash-role-super_admin .admin-event-card__title a:hover { text-decoration: underline; }
.dash-role-admin .admin-event-card__meta,
.dash-role-super_admin .admin-event-card__meta { font-size: 12.5px; color: var(--admin-muted); margin: 0; }
.dash-role-admin .admin-event-card__meta a,
.dash-role-super_admin .admin-event-card__meta a { color: var(--admin-active); text-decoration: none; }
.dash-role-admin .admin-event-card__actions,
.dash-role-super_admin .admin-event-card__actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: auto; padding-top: 12px; border-top: 1px solid var(--admin-line);
}

/* =====================================================================
   Business Owner dashboard redesign (client update #21) — the matching
   approved design package for the business dashboard uses the exact same
   "eucalyptus, porcelain and champagne" palette as the Admin console
   above (confirmed by its own CLAUDE_HANDOFF.md). Scoped to
   .dash-role-business_owner only — Sales Rep keeps the original dark
   sidebar (no design package was provided for that role), and none of
   this touches the public site.
   ===================================================================== */
.dash-role-business_owner .site-header { background: rgba(250, 248, 243, .94); border-bottom: 1px solid #ddd9cf; }
/* Reference-design fixes: the sidebar/content widths here were left at
   this file's generic 240px-sidebar / max-width:1180px-content defaults,
   which is what the Admin console explicitly overrides for itself a few
   hundred lines up (.dash-role-admin .dash-wrap / .dash-content) — this
   role never got the same override, so on any viewport wider than
   roughly 1220px the content area stopped short of the right edge instead
   of filling it, and the sidebar rendered 32px narrower than the
   reference design's own sidebar. Matching the admin values exactly,
   since the design package for this role is confirmed to share the same
   layout metrics (see this block's own header comment). */
.dash-role-business_owner .dash-wrap { grid-template-columns: 272px 1fr; background: var(--admin-cream); }
.dash-role-business_owner .dash-topheader__logo { width: 272px; }
.dash-role-business_owner .dash-sidebar { width: 272px; }
.dash-role-business_owner .dash-content { max-width: none; padding: 32px 40px 48px; font-family: var(--admin-font); color: var(--admin-ink); }
.dash-role-business_owner .dash-topheader__logo { background: linear-gradient(180deg, #d2e3d9 0%, #bfd6ca 100%); }
.dash-role-business_owner .brand { color: #173b31; }
.dash-role-business_owner .header-actions .pill {
    background: var(--admin-white); border: 1px solid #d9d5ca; color: var(--admin-ink);
}

/* Sidebar: light sage gradient, flat nav (business owner has no grouped
   sections — matches the canonical package's single-list sidebar nav). */
.dash-role-business_owner .dash-sidebar {
    background: linear-gradient(180deg, #d2e3d9 0%, #bfd6ca 100%);
    border-right: 1px solid #aec9ba; box-shadow: 10px 0 35px rgba(31, 78, 60, .07);
}
.dash-role-business_owner .dash-nav__caption { color: #577568; opacity: 1; }
/* "GROWTH" / "PERFORMANCE" section labels (see layouts/dashboard.php's
   nav-group loop) — same treatment as the admin sidebar's own group
   labels, since the base .dash-sidebar__label color (--ink-300, a light
   grey meant for a dark sidebar) is nearly invisible against this role's
   light sage background. */
.dash-role-business_owner .dash-sidebar__label { color: #537565; margin: 20px 0 6px; }
.dash-role-business_owner .dash-sidebar__label:first-child { margin-top: 4px; }
.dash-role-business_owner .dash-nav--pinned { border-top-color: #a9c5b5; }
.dash-role-business_owner .dash-nav a { color: #355f4d; opacity: 1; }
.dash-role-business_owner .dash-nav a:hover { background: rgba(255,253,249,.62); color: #164e3d; }
.dash-role-business_owner .dash-nav a.is-active {
    background: var(--admin-active); color: #fff; opacity: 1; font-weight: 600;
    border-left-color: var(--admin-gold-deep); box-shadow: 0 8px 20px rgba(29, 93, 73, .18);
}

/* Shared panel/table/form/button components — identical treatment to the
   Admin console so Overview, Listing info, Photos & video, Events,
   Promotions, Featured/Sponsorship/Insights (which reuse .dash-panel +
   .data-table), Reviews and Billing all inherit the corrected look with
   no markup changes. */
.dash-role-business_owner .dash-topbar h1 { font-family: var(--admin-font); font-size: 28px; font-weight: 600; color: #192e32; }
.dash-role-business_owner .dash-topbar p { color: var(--admin-muted); font-size: 14.5px; }
/* Missed when .dash-topbar__eyebrow was first added — it only had
   .dash-role-admin/.dash-role-super_admin rules, so on every one of the
   business_owner dashboard pages (Overview, Listing info, Photos & video,
   Events, Promotions, Get Featured, Get Sponsored, Get Verified, Insights,
   Reviews, Billing) the eyebrow label rendered as plain unstyled inline
   text instead of the small bold uppercase tag used everywhere else. */
.dash-role-business_owner .dash-topbar__eyebrow {
    display: block; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
    color: var(--admin-forest); margin-bottom: 4px;
}
.dash-role-business_owner .dash-panel {
    background: var(--admin-white); border-color: var(--admin-line); border-radius: var(--radius-lg); padding: 22px;
    box-shadow: 0 12px 32px rgba(27, 48, 56, .055);
}
.dash-role-business_owner .dash-panel h2 { font-size: 17px; font-weight: 600; color: var(--admin-ink); }
/* "Your live listing" CTA card on Overview (business/dashboard.php) stays
   the same dark-green call-out on every role, so it needs to win back
   over the plain white-panel rule directly above (higher specificity —
   both are role + class selectors). */
.dash-role-business_owner .dash-panel--live-listing {
    background: linear-gradient(145deg, #1f725b, #174d40); border-color: #1f725b; box-shadow: 0 12px 32px rgba(15,45,35,.18);
}
.dash-role-business_owner .dash-panel--live-listing h2,
.dash-role-business_owner .dash-panel--live-listing p { color: var(--cream-100); }
.dash-role-business_owner .dash-panel--live-listing .btn--outline-dark {
    border-color: rgba(255,255,255,.5); color: var(--cream-100); background: transparent;
}
.dash-role-business_owner .dash-panel--live-listing .btn--outline-dark:hover { background: rgba(255,255,255,.12); }
.dash-role-business_owner .stat-card {
    background: var(--admin-white); border-color: var(--admin-line); border-radius: var(--radius-lg); padding: 20px;
    box-shadow: 0 12px 32px rgba(27, 48, 56, .055);
}
.dash-role-business_owner .stat-card__label { font-weight: 700; color: var(--admin-muted); }
.dash-role-business_owner .stat-card__value { font-size: 30px; font-weight: 600; color: var(--admin-ink); }
.dash-role-business_owner .data-table th { background: #f5f2eb; border-bottom: none; color: var(--admin-muted); }
.dash-role-business_owner .data-table td { color: var(--admin-ink); border-bottom-color: var(--admin-line); }
.dash-role-business_owner .data-table tr:hover td { background: #faf8f3; }
.dash-role-business_owner .field input,
.dash-role-business_owner .field select,
.dash-role-business_owner .field textarea { border-radius: var(--radius-sm); border-color: #ddd9cf; background: #faf8f3; color: var(--admin-ink); }
.dash-role-business_owner .field input:focus,
.dash-role-business_owner .field select:focus,
.dash-role-business_owner .field textarea:focus { border-color: #5d8c79; box-shadow: 0 0 0 3px rgba(29, 93, 73, .10); }
.dash-role-business_owner .grid .field input,
.dash-role-business_owner .grid .field select { border-radius: var(--radius-sm); border-color: #ddd9cf; background: #faf8f3; }
.dash-role-business_owner .tab-row { border-bottom-color: var(--admin-line); }
.dash-role-business_owner .tab-row a { color: var(--admin-muted); }
.dash-role-business_owner .tab-row a.is-active { color: #1a5f4c; border-bottom-color: var(--admin-coral); }
.dash-role-business_owner .btn--forest,
.dash-role-business_owner .btn--coral { background: var(--admin-active); color: var(--white); }
.dash-role-business_owner .btn--forest:hover,
.dash-role-business_owner .btn--coral:hover { background: var(--admin-active-hover); }
.dash-role-business_owner .btn--ghost { background: var(--admin-white); border: 1px solid #d9d5ca; color: var(--admin-ink); }
.dash-role-business_owner .btn--ghost:hover { background: #faf8f3; }
.dash-role-business_owner .btn--outline-dark { border-color: var(--admin-ink); color: var(--admin-ink); }
.dash-role-business_owner .btn--outline-dark:hover { background: var(--admin-ink); }
.dash-role-business_owner .icon-btn:hover { background: #f5f2eb; color: var(--admin-forest); }
.dash-role-business_owner .icon-btn--danger:hover { color: #9b4c3f; }

/* Billing page reuses the public site's .pricing-card plan-picker component
   (see app/Views/business/billing.php) — re-accent it from coral to the
   canonical emerald/gold so it matches the rest of the dashboard; no
   markup or pricing-logic changes. */
.dash-role-business_owner .pricing-card--featured { border-color: var(--admin-active); }
.dash-role-business_owner .pricing-card__badge { background: var(--admin-active); }
.dash-role-business_owner .pricing-card__promo-badge { background: var(--admin-gold-deep); }
.dash-role-business_owner .pricing-card__features li::before { color: var(--admin-forest); }
.dash-role-business_owner .btn--coral,
.dash-role-business_owner .pricing-card .btn--coral { background: var(--admin-active); }
.dash-role-business_owner .pricing-card .btn--coral:hover { background: var(--admin-active-hover); }

/* Listing-completion checklist ("Let's build your listing") — the
   canonical package treats this as the single highest-priority panel on
   Overview (gold-highlighted next step, dimmed done steps). Re-themes the
   existing .setup-guide component in place; no markup/controller changes,
   same $setupSteps data from Business\DashboardController. */
.dash-role-business_owner .setup-guide { border-color: #adc7b8; border-top: 4px solid var(--admin-active); }
.dash-role-business_owner .setup-guide__progress-track { background: #e4e3dc; }
.dash-role-business_owner .setup-guide__progress-fill { background: linear-gradient(90deg, #1d5d49, #5c947c); }
.dash-role-business_owner .setup-guide__progress-label { color: #2d5d4c; }
.dash-role-business_owner .setup-guide__step { border-bottom-color: #ebe7df; }
.dash-role-business_owner .setup-guide__step.is-next {
    background: linear-gradient(100deg, #fff8e8, #fffcf5); border: 2px solid #c6a35d; border-radius: 12px; padding: 12px;
}
.dash-role-business_owner .setup-guide__badge { border-color: #d8ddd9; background: #eef2ef; color: #77837e; }
.dash-role-business_owner .setup-guide__step.is-done .setup-guide__badge { background: var(--admin-active); border-color: var(--admin-active); color: #fff; }
.dash-role-business_owner .setup-guide__optional { background: #ece7dc; color: #7a756b; }
.dash-role-business_owner .setup-guide__eyebrow { background: #eef2ef; color: var(--admin-forest); }
.dash-role-business_owner .setup-guide__attention { background: #fdf1de; color: #8a5a12; }
.dash-role-business_owner .setup-guide__ribbon { background: var(--admin-gold-deep); color: #3a2a06; }
.dash-role-business_owner .setup-guide--done { border-color: #adc7b8; background: linear-gradient(145deg, #fffdf9 0%, #f8fbf8 70%, #edf4ef 100%); }
.dash-role-business_owner .setup-guide--done p { color: var(--admin-forest); }

/* Verification type cards (client update #21) — CLAUDE_HANDOFF.md for the
   business package requires identity/credential verification to be shown
   as two selectable cards with the live fee on each, not a <select>
   dropdown. See app/Views/business/verification.php for the matching
   markup change (radio inputs styled with this component). */
.verification-choice {
    position: relative; display: grid; grid-template-columns: 42px minmax(0,1fr) auto; align-items: center; gap: 12px;
    min-height: 86px; padding: 14px; border: 1px solid #dedbd1; border-radius: 12px; background: #faf8f3; cursor: pointer;
}
.verification-choice:hover { border-color: #9db9aa; background: #f6faf7; }
.verification-choice input { position: absolute; opacity: 0; }
.verification-choice input:checked ~ .verification-choice-icon { background: var(--admin-active); color: #fff; }
.verification-choice:has(input:checked) { border: 2px solid var(--admin-active); background: #eef5f1; box-shadow: 0 8px 20px rgba(29, 93, 73, .08); }
.verification-choice-icon {
    display: grid; place-items: center; width: 42px; height: 42px; border-radius: 11px; background: #e0ebe5;
    color: var(--admin-active); font-size: 17px;
}
.verification-choice strong,
.verification-choice small { display: block; }
.verification-choice strong { font-size: 13px; }
.verification-choice small { margin-top: 5px; color: #72807a; font-size: 11px; line-height: 1.4; }
.verification-choice b {
    padding: 6px 8px; border-radius: 8px; background: #efe4d1; color: #765a2d; font-size: 13px; min-width: 48px; text-align: center;
}
.verification-choice input:checked ~ b { background: var(--admin-active); color: #fff; }
@media (max-width: 700px) { .verification-types { grid-template-columns: 1fr; } }

/* Email-verification banner — shared across every dashboard role (see
   app/Views/layouts/dashboard.php); restyled to match the canonical
   package's red-bordered notice so the unverified state reads as urgent,
   same real $currentUser['email'] and resend action as before. */
.email-verify-banner {
    display: flex; align-items: center; gap: 14px; margin-bottom: 20px; padding: 16px 18px;
    border: 1px solid #d9544f; border-radius: 14px; background: linear-gradient(100deg, #fff0ee, #fff7f5);
    box-shadow: 0 10px 26px rgba(173, 54, 47, .10); flex-wrap: wrap;
}
.email-verify-banner__icon {
    display: grid; place-items: center; width: 40px; height: 40px; flex: 0 0 auto; border-radius: 11px;
    background: #d94b45; color: #fff; font-size: 17px;
}
.email-verify-banner p { flex: 1; margin: 0; min-width: 220px; }
.email-verify-banner strong { display: block; font-size: 14px; color: #9e302c; }
.email-verify-banner small { display: block; margin-top: 4px; color: #754b48; font-size: 12px; line-height: 1.5; }
.email-verify-banner small b { font-weight: 600; color: #8c322e; }
.email-verify-banner button {
    min-height: 40px; padding: 0 13px; border: 1px solid #c6423d; border-radius: 9px; background: #c6423d; color: #fff;
    font: 600 12px inherit; cursor: pointer; white-space: nowrap;
}
.email-verify-banner button:hover { background: #ab332f; }

/* ---------------------------------------------------------------------
   Mobile input auto-zoom fix — iOS Safari (and some Android browsers)
   automatically zoom the whole page in the moment a focused input's
   computed font-size is under 16px, on the assumption the text is too
   small to read. The homepage hero search bar's fields were set to 14px
   (see .search-bar__field input above), which is exactly what triggered
   this on the mobile Home page: tapping the search box snapped the whole
   viewport to a zoomed-in state, shifting the layout and making it hard
   to keep typing or get back to a normal view. There is no reliable
   CSS-only way to disable this per element — the fix has to be that no
   focusable form control anywhere is smaller than 16px on a small
   viewport. Deliberately placed as the LAST rule in this file so it wins
   the cascade over every earlier component-specific font-size (search
   bar, admin filter grids, etc.) without having to hunt down and edit
   each one individually — this can't regress by a future component
   quietly reintroducing a sub-16px input.  Desktop is untouched; this
   only applies at mobile widths where iOS/Android auto-zoom kicks in. */
@media (max-width: 640px) {
    input, select, textarea { font-size: 16px; }
}

/* =======================================================================
   Admin console redesign, part 4 — Operations & Settings pages (Client
   update #19 continued). New shared components used across these pages;
   everything below is additive (no existing rule anywhere above is
   changed) and double-scoped to .dash-role-admin/.dash-role-super_admin
   like every earlier redesign block, reusing the same custom properties
   and tint pairs already established in parts 1-3 above.
   ======================================================================= */

/* Small uppercase label above an .admin-panel's <h2> (e.g. "REVIEW
   CHECKLIST" above "Before publishing") — same treatment as
   .dash-topbar__eyebrow, sized down for a panel context. */
.dash-role-admin .admin-panel__eyebrow,
.dash-role-super_admin .admin-panel__eyebrow {
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
    color: #9d7440; margin-bottom: 4px;
}

/* Full-width status banner (Assistance/Reviews "queue is clear" strip,
   System's "maintenance is ready" strip, etc.) — an icon chip, a
   title+subtitle, and an optional right-aligned pill. */
.dash-role-admin .admin-banner,
.dash-role-super_admin .admin-banner {
    display: flex; align-items: center; gap: 14px; border: 1px solid var(--admin-line);
    border-radius: var(--radius-lg); padding: 16px 20px; margin-bottom: 20px;
}
.dash-role-admin .admin-banner--good,
.dash-role-super_admin .admin-banner--good { background: linear-gradient(135deg, #eef4ee, #f8f6ef); }
.dash-role-admin .admin-banner__icon,
.dash-role-super_admin .admin-banner__icon {
    width: 38px; height: 38px; border-radius: 50%; background: #d8e8dd; color: #1b654f; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
}
.dash-role-admin .admin-banner__icon svg,
.dash-role-super_admin .admin-banner__icon svg { width: 18px; height: 18px; }
.dash-role-admin .admin-banner__body,
.dash-role-super_admin .admin-banner__body { flex: 1; min-width: 0; }
.dash-role-admin .admin-banner__body strong,
.dash-role-super_admin .admin-banner__body strong { display: block; font-size: 14.5px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-banner__body p,
.dash-role-super_admin .admin-banner__body p { font-size: 13px; color: var(--admin-muted); margin-top: 2px; }

/* Large centered empty state with a soft icon circle (Assistance/Support/
   CMS "nothing here yet" panels) — optional numbered-steps strip below. */
.dash-role-admin .admin-empty-state,
.dash-role-super_admin .admin-empty-state { padding: 56px 24px; text-align: center; }
.dash-role-admin .admin-empty-state__icon,
.dash-role-super_admin .admin-empty-state__icon {
    display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px;
    border-radius: 50%; background: #e1eee7; color: #1b654f; margin-bottom: 18px;
}
.dash-role-admin .admin-empty-state__icon svg,
.dash-role-super_admin .admin-empty-state__icon svg { width: 24px; height: 24px; }
.dash-role-admin .admin-empty-state h3,
.dash-role-super_admin .admin-empty-state h3 { font-size: 17px; font-weight: 700; color: var(--admin-ink); margin-bottom: 6px; }
.dash-role-admin .admin-empty-state p,
.dash-role-super_admin .admin-empty-state p { font-size: 13.5px; color: var(--admin-muted); max-width: 440px; margin: 0 auto; }
.dash-role-admin .admin-empty-state__steps,
.dash-role-super_admin .admin-empty-state__steps {
    display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 22px;
    flex-wrap: wrap; font-size: 12.5px; color: var(--admin-muted);
}
.dash-role-admin .admin-empty-state__steps b,
.dash-role-super_admin .admin-empty-state__steps b {
    display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
    border-radius: 50%; background: #f5f2eb; color: var(--admin-ink); font-size: 10.5px; font-weight: 700; margin-right: 5px;
}
.dash-role-admin .admin-empty-state__arrow,
.dash-role-super_admin .admin-empty-state__arrow { color: #c7c2b4; }

/* KPI card with a small icon button pinned to the top-right corner
   (Sales & Commissions' 4 summary tiles) — extends .stat-card. */
.dash-role-admin .stat-card__icon,
.dash-role-super_admin .stat-card__icon {
    float: right; width: 30px; height: 30px; border-radius: 8px; background: #f5f2eb; color: var(--admin-muted);
    display: flex; align-items: center; justify-content: center;
}
.dash-role-admin .stat-card__icon svg,
.dash-role-super_admin .stat-card__icon svg { width: 14px; height: 14px; }

/* Numbered step marker before a form-section heading (Subscriptions'
   "01 Plan details", "02 Included benefits", "03 Promotional pricing"). */
.dash-role-admin .admin-step,
.dash-role-super_admin .admin-step { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 14px; }
.dash-role-admin .admin-step__num,
.dash-role-super_admin .admin-step__num {
    flex-shrink: 0; width: 26px; height: 26px; border-radius: 8px; background: #f5f2eb; color: var(--admin-ink);
    font-size: 11px; font-weight: 800; display: flex; align-items: center; justify-content: center; margin-top: 1px;
}
.dash-role-admin .admin-step h3,
.dash-role-super_admin .admin-step h3 { font-size: 15px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-step p,
.dash-role-super_admin .admin-step p { font-size: 12.5px; color: var(--admin-muted); margin-top: 1px; }

/* Toggle chip checkbox (Subscriptions' "included benefits" grid) — a
   bordered rounded rect with a checkbox + label; checked state tints
   green, matching .admin-verified-badge's palette. */
.dash-role-admin .admin-chip-check,
.dash-role-super_admin .admin-chip-check {
    display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-radius: 9px;
    border: 1.5px solid var(--admin-line); font-size: 13px; color: var(--admin-ink); cursor: pointer;
}
.dash-role-admin .admin-chip-check input,
.dash-role-super_admin .admin-chip-check input { width: 15px; height: 15px; flex-shrink: 0; accent-color: var(--admin-active); }
.dash-role-admin .admin-chip-check:has(input:checked),
.dash-role-super_admin .admin-chip-check:has(input:checked) { background: #e1eee7; border-color: #b7d6c3; color: #1b654f; font-weight: 600; }

/* Colored legend dot (Subscriptions' "PROMOTIONAL / GROWTH / PREMIUM"
   plan-type legend) — reuses the ::before dot trick already used by
   .admin-subscription-badge, generalised to any color via inline style. */
.dash-role-admin .admin-legend-dot,
.dash-role-super_admin .admin-legend-dot { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--admin-muted); }
.dash-role-admin .admin-legend-dot::before,
.dash-role-super_admin .admin-legend-dot::before { content: '●'; font-size: 9px; color: var(--dot-color, var(--admin-muted)); }

/* Category/neighbourhood tree row — drag handle, depth indent, small
   plain action buttons (matches the reference mockup's Categories page,
   which uses plain "Edit"/"Delete" buttons rather than a kebab menu). */
.dash-role-admin .admin-tree-row,
.dash-role-super_admin .admin-tree-row { display: flex; align-items: center; gap: 10px; }
.dash-role-admin .admin-tree-row__handle,
.dash-role-super_admin .admin-tree-row__handle { color: #c7c2b4; cursor: grab; flex-shrink: 0; font-size: 13px; }
.dash-role-admin .admin-tree-row__emoji,
.dash-role-super_admin .admin-tree-row__emoji { flex-shrink: 0; }

/* Maintenance-style 2-up icon card (System page) — icon chip top-left,
   small pill top-right, big heading, description, action button. */
.dash-role-admin .admin-maint-grid,
.dash-role-super_admin .admin-maint-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin-bottom: 18px; }
.dash-role-admin .admin-maint-card__top,
.dash-role-super_admin .admin-maint-card__top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 20px; }
.dash-role-admin .admin-maint-card__icon,
.dash-role-super_admin .admin-maint-card__icon {
    width: 42px; height: 42px; border-radius: 11px; background: #f5f2eb; color: var(--admin-active);
    display: flex; align-items: center; justify-content: center;
}
.dash-role-admin .admin-maint-card__icon svg,
.dash-role-super_admin .admin-maint-card__icon svg { width: 19px; height: 19px; }
.dash-role-admin .admin-maint-card__tag,
.dash-role-super_admin .admin-maint-card__tag { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--admin-muted); }
.dash-role-admin .admin-maint-card__label,
.dash-role-super_admin .admin-maint-card__label { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #9d7440; margin-bottom: 6px; }
.dash-role-admin .admin-maint-card__value,
.dash-role-super_admin .admin-maint-card__value { font-family: var(--admin-font); font-size: 30px; font-weight: 700; color: var(--admin-ink); margin-bottom: 10px; }
.dash-role-admin .admin-maint-card__value small,
.dash-role-super_admin .admin-maint-card__value small { font-size: 15px; font-weight: 600; color: var(--admin-muted); margin-left: 4px; }
.dash-role-admin .admin-maint-card h3,
.dash-role-super_admin .admin-maint-card h3 { font-size: 17px; font-weight: 700; color: var(--admin-ink); margin-bottom: 6px; }
.dash-role-admin .admin-maint-card p,
.dash-role-super_admin .admin-maint-card p { font-size: 13px; color: var(--admin-muted); margin-bottom: 14px; }
.dash-role-admin .admin-maint-card__hint,
.dash-role-super_admin .admin-maint-card__hint { font-size: 11.5px; color: var(--admin-muted); margin-top: 8px; }
@media (max-width: 900px) {
    .dash-role-admin .admin-maint-grid, .dash-role-super_admin .admin-maint-grid { grid-template-columns: 1fr; }
}

/* Collapsible plan card (Subscriptions) — <details><summary> row with
   avatar + name/tagline + price + sync pill + chevron; body holds the
   existing plan-edit form untouched. */
.dash-role-admin .admin-plan-card__summary,
.dash-role-super_admin .admin-plan-card__summary {
    list-style: none; display: flex; align-items: center; gap: 14px; padding: 18px 20px; cursor: pointer;
}
.dash-role-admin .admin-plan-card__summary::-webkit-details-marker,
.dash-role-super_admin .admin-plan-card__summary::-webkit-details-marker { display: none; }
.dash-role-admin .admin-plan-card__title,
.dash-role-super_admin .admin-plan-card__title { flex: 1; min-width: 0; }
.dash-role-admin .admin-plan-card__title strong,
.dash-role-super_admin .admin-plan-card__title strong { display: block; font-size: 15px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-plan-card__title small,
.dash-role-super_admin .admin-plan-card__title small { display: block; font-size: 12.5px; color: var(--admin-muted); margin-top: 1px; }
.dash-role-admin .admin-plan-card__price,
.dash-role-super_admin .admin-plan-card__price { text-align: right; flex-shrink: 0; }
.dash-role-admin .admin-plan-card__price strong,
.dash-role-super_admin .admin-plan-card__price strong { display: block; font-size: 16px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-plan-card__price small,
.dash-role-super_admin .admin-plan-card__price small { display: block; font-size: 11px; color: var(--admin-muted); }
.dash-role-admin .admin-plan-card__chevron,
.dash-role-super_admin .admin-plan-card__chevron { flex-shrink: 0; color: var(--admin-muted); transform: rotate(90deg); transition: transform var(--transition); }
.dash-role-admin .admin-plan-card__chevron svg,
.dash-role-super_admin .admin-plan-card__chevron svg { width: 16px; height: 16px; }
.dash-role-admin details[open] > .admin-plan-card__summary .admin-plan-card__chevron,
.dash-role-super_admin details[open] > .admin-plan-card__summary .admin-plan-card__chevron { transform: rotate(-90deg); }
.dash-role-admin .admin-plan-card__body,
.dash-role-super_admin .admin-plan-card__body { padding: 0 20px 22px; border-top: 1px solid var(--admin-line); padding-top: 20px; }

/* Analytics — mini chart cards (New signups / New businesses / Revenue) */
.dash-role-admin .admin-chart-card,
.dash-role-super_admin .admin-chart-card {
    background: var(--admin-white); border: 1px solid var(--admin-line); border-radius: var(--radius-lg);
    padding: 20px; box-shadow: 0 12px 32px rgba(27, 48, 56, .05);
}
.dash-role-admin .admin-chart-card__head,
.dash-role-super_admin .admin-chart-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.dash-role-admin .admin-chart-card__title,
.dash-role-super_admin .admin-chart-card__title { font-size: 15px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-chart-card__value,
.dash-role-super_admin .admin-chart-card__value { font-family: var(--admin-font); font-size: 22px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-chart-card__sub,
.dash-role-super_admin .admin-chart-card__sub { font-size: 12px; color: var(--admin-muted); margin-bottom: 14px; }
.dash-role-admin .admin-chart-card__chart,
.dash-role-super_admin .admin-chart-card__chart { border-radius: var(--radius-md); overflow: hidden; padding: 12px 10px 0; height: 116px; }
.dash-role-admin .admin-chart-card--dark,
.dash-role-super_admin .admin-chart-card--dark { background: linear-gradient(160deg, #1f725b, #174d40); border-color: #174d40; }
.dash-role-admin .admin-chart-card--dark .admin-chart-card__title,
.dash-role-super_admin .admin-chart-card--dark .admin-chart-card__title,
.dash-role-admin .admin-chart-card--dark .admin-chart-card__value,
.dash-role-super_admin .admin-chart-card--dark .admin-chart-card__value { color: var(--admin-white); }
.dash-role-admin .admin-chart-card--dark .admin-chart-card__sub,
.dash-role-super_admin .admin-chart-card--dark .admin-chart-card__sub { color: rgba(255,255,255,.7); }

/* Analytics — numbered rank lists (Top categories / Top neighbourhoods) */
.dash-role-admin .admin-rank-list,
.dash-role-super_admin .admin-rank-list { display: flex; flex-direction: column; }
.dash-role-admin .admin-rank-row,
.dash-role-super_admin .admin-rank-row {
    display: flex; align-items: center; gap: 14px; padding: 12px 2px; border-bottom: 1px solid var(--admin-line);
}
.dash-role-admin .admin-rank-row:last-child,
.dash-role-super_admin .admin-rank-row:last-child { border-bottom: none; }
.dash-role-admin .admin-rank-row__num,
.dash-role-super_admin .admin-rank-row__num { font-size: 12px; font-weight: 700; color: var(--admin-muted); width: 20px; flex-shrink: 0; }
.dash-role-admin .admin-rank-row__name,
.dash-role-super_admin .admin-rank-row__name { flex: 1; font-size: 14px; font-weight: 600; color: var(--admin-ink); }
.dash-role-admin .admin-rank-row__value,
.dash-role-super_admin .admin-rank-row__value { font-size: 14px; font-weight: 700; color: var(--admin-ink); }

/* Reports & Exports — row list with icon, description, and export actions */
.dash-role-admin .admin-report-list,
.dash-role-super_admin .admin-report-list { display: flex; flex-direction: column; }
.dash-role-admin .admin-report-row,
.dash-role-super_admin .admin-report-row {
    display: flex; align-items: center; gap: 16px; padding: 16px 4px; border-bottom: 1px solid var(--admin-line);
}
.dash-role-admin .admin-report-row:last-child,
.dash-role-super_admin .admin-report-row:last-child { border-bottom: none; }
.dash-role-admin .admin-report-row__icon,
.dash-role-super_admin .admin-report-row__icon {
    width: 42px; height: 42px; border-radius: var(--radius-md); background: #eef1ec; color: var(--admin-forest);
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.dash-role-admin .admin-report-row__icon svg,
.dash-role-super_admin .admin-report-row__icon svg { width: 19px; height: 19px; }
.dash-role-admin .admin-report-row__body,
.dash-role-super_admin .admin-report-row__body { flex: 1; min-width: 0; }
.dash-role-admin .admin-report-row__title,
.dash-role-super_admin .admin-report-row__title { font-size: 15px; font-weight: 700; color: var(--admin-ink); }
.dash-role-admin .admin-report-row__sub,
.dash-role-super_admin .admin-report-row__sub { font-size: 13px; color: var(--admin-muted); margin-top: 2px; }
.dash-role-admin .admin-report-row__actions,
.dash-role-super_admin .admin-report-row__actions { display: flex; gap: 8px; flex-shrink: 0; }
@media (max-width: 640px) {
    .dash-role-admin .admin-report-row, .dash-role-super_admin .admin-report-row { flex-wrap: wrap; }
    .dash-role-admin .admin-report-row__actions, .dash-role-super_admin .admin-report-row__actions { width: 100%; margin-left: 58px; }
}

/* Bug fix (client report, 2026-08-21, real phone screenshot of Admin >
   System) — "the content in the screen is only taking up half the screen
   instead of the entire screen." Confirmed by measuring the real page: at
   a phone width, .dash-wrap's grid-template-columns computed to
   "272px 103px" instead of one full-width "1fr" column — .dash-content
   (which has no explicit grid-column of its own below the 901px desktop
   breakpoint — see .dash-content { grid-column: 2 } further up, which is
   itself inside an @media (min-width: 901px) block) was auto-placing into
   that first, narrow 272px track, leaving the rest of the phone screen an
   empty, unused second track next to it.
   Root cause: .dash-wrap's mobile "collapse to one column" rule
   (.dash-wrap { grid-template-columns: 1fr; }, in the @media
   (max-width: 980px) block above) is a bare, one-class selector —
   specificity (0,1,0). But .dash-role-admin .dash-wrap,
   .dash-role-super_admin .dash-wrap, and .dash-role-business_owner
   .dash-wrap all set their OWN "272px 1fr" / "240px 1fr" column tracks
   completely unconditionally (no media query at all, so they're always in
   effect) at a HIGHER specificity — (0,2,0), two classes. CSS specificity
   always wins a conflict regardless of which rule is inside a media query
   or which one appears later in the file, so those three role-scoped
   rules silently override the mobile collapse at every screen width,
   including phones, for every dashboard role. This is a real, structural
   layout bug, not merely cosmetic — it was very easy to miss by eye alone
   (a phone screenshot of a short line of text can still look plausible at
   272px), which is exactly why it went unnoticed until the client's own
   phone screenshot flagged it; confirmed here by directly measuring
   getComputedStyle(.dash-wrap).gridTemplateColumns in a real headless
   browser rather than guessing from a screenshot alone.
   Fixed by re-declaring the same three role-scoped selectors here, inside
   the mobile media query, at matching specificity and — critically —
   later in the file than all three unconditional role rules above, so
   this wins the cascade and actually collapses every role's dashboard
   back to a single full-width column on a phone, exactly like the
   generic, non-role-scoped .dash-wrap rule was always meant to. */
@media (max-width: 980px) {
    .dash-role-admin .dash-wrap,
    .dash-role-super_admin .dash-wrap,
    .dash-role-business_owner .dash-wrap { grid-template-columns: 1fr; }
}
/* Same root cause, smaller symptom: business_owner's topheader logo block
   was left at its unconditional 272px width (matching the desktop
   sidebar) with no mobile override — admin/super_admin already got this
   exact fix a few hundred lines up (@media (max-width: 900px)
   { .dash-role-admin .dash-topheader__logo, ... { width: auto; } }); this
   is that same fix, just also applied to business_owner, which never got
   it. */
@media (max-width: 900px) {
    .dash-role-business_owner .dash-topheader__logo { width: auto; }
}

/* ---------------------------------------------------------------------
   Sponsored/advertisement banners — ad_banner_html() in helpers.php.
   Client request (2026-08-21): "MOBILE = square/compact sponsored banner,
   DESKTOP = wide horizontal sponsored banner" — a wide landscape banner
   simply scaled down for a phone reads too short and is hard to read, so
   mobile gets its own compact, roughly-square card instead of the desktop
   banner shrunk to fit. Used by five placements: home_hero, home_mid and
   homepage_bottom (full page width, inside .container — see .ad-banner__
   base rules), search_sidebar and listing_footer (narrower column — see
   .ad-banner--narrow below, and .ad-banner--compact for the sidebar's
   tighter vertical spacing). Every placement already sits inside a
   container with real horizontal padding (.container's 24px, or the
   listing/search column's own layout) at every width, so the mobile card
   never needs its own edge margin to avoid touching the screen edge.
   --------------------------------------------------------------------- */
.ad-banner {
    position: relative;
    border-radius: 16px;
    overflow: hidden;
    margin: 32px 0;
    background: var(--cream-200);
}
.ad-banner--compact { margin: 0 0 20px; }
.ad-banner__label {
    position: absolute; top: 10px; left: 10px; z-index: 2;
    background: rgba(16, 31, 24, .65); color: var(--cream-100);
    font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    padding: 3px 9px; border-radius: var(--radius-full);
}
.ad-banner__frame { display: block; }
.ad-banner__img { width: 100%; height: auto; display: block; }

@media (max-width: 640px) {
    /* Only reshape the container when there's a dedicated mobile creative
       to fill it — see the <source media> swap in ad_banner_html(). A
       plain <img> falling back to the desktop image keeps its own natural
       ratio here (no aspect-ratio/object-fit forced onto it), so a wide
       banner is never squeezed or cropped into a shape it wasn't made
       for — it just gets the same card spacing/label as everything else
       on this breakpoint. */
    .ad-banner--has-mobile-creative .ad-banner__frame { aspect-ratio: 1 / 1; }
    .ad-banner--has-mobile-creative.ad-banner--narrow .ad-banner__frame { aspect-ratio: 4 / 3; }
    .ad-banner--has-mobile-creative .ad-banner__img {
        width: 100%; height: 100%; object-fit: cover; object-position: center;
    }
    .ad-banner { margin: 24px 0; border-radius: 14px; }
    .ad-banner--compact { margin: 0 0 16px; }
}
