/*
 * RLEADS design system (SP7) — single static stylesheet, no build step.
 * Scope: the three SaaS layouts only (app console / app auth / public pages),
 * selected via a body class each layout sets:
 *   body.rl-console  — /app agent console
 *   body.rl-auth     — /app login/signup/verify
 *   body.rl-public   — /a/{slug}, /l/{token}, sell, appointment
 * Legacy admin/mvp views never link this file.
 *
 * Tokens are the CANONICAL inHom design-system values (source: Eric's
 * "inHom Prodcut Design System" bundle, colors_and_type.css) — navy #00415A
 * for titles/links/emphasis, steel #52677D for filled buttons, terracotta
 * #E88D67 as accent (the one gradient + attention markers), sage green for
 * won/success, warm-paper neutrals (never cool gray), navy-tinted shadows.
 * Lead-status pill colors are a DS gap filled with tints DERIVED from the
 * canonical hues (navy-tint / terra-tint / green-30) — flag to brand team.
 * Fonts: self-hosted Inter + Noto Sans TC (./fonts/, subsetted woff2).
 */

:root {
    --navy: #00415A;
    --navy-deep: #003346;
    --midnight: #1C2E4A;
    --steel: #52677D;
    --terra: #E88D67;
    --terra-deep: #B05C33;   /* derived: terracotta darkened for text contrast */
    --terra-tint: #FBEEE6;   /* derived: terracotta 10% tint */
    --navy-tint: #E3EDF2;    /* derived: navy 8% tint on paper */
    --sage-30: #EAF0DC;
    --sage-900: #788F3D;
    --paper: #FCFCF9;
    --paper-alt: #F3F3EE;
    --gray-50: #EDEEF3;
    --border: #E8E8E3;
    --border-strong: #D1D9E6;
    --hairline: #EFEFEA;
    --ink: #171923;
    --fg-muted: #2D3748;
    /* RLEADS-87 Batch A: darkened from #718096 (3.906:1 on --paper, below the
       4.5:1 WCAG AA floor for normal text) to #556075 (6.161:1 on --paper —
       verified via the WCAG relative-luminance formula, not just the ticket's
       own ≈4.6:1 estimate, which undershot the actual value). No theme preset
       below ([data-theme="terra"|"forest"|"midnight"]) overrides --muted or
       --fg-muted, and none overrides --paper either, so this single change
       propagates the fix to every theme automatically. */
    --muted: #556075;
    --danger: #B3402E;       /* DS gap: no error token — warm red, flagged derived */
    --danger-bg: #FCEBE7;
    --line-green: #06C755;   /* LINE official — LINE CTA only */
    /* RLEADS-87 Batch A: end-stop darkened from #E88D67 to #B05C33 (the
       existing --terra-deep token, "terracotta darkened for text contrast").
       CORRECTION (review finding): that darkening was verified against
       OPAQUE white (#fff), but .headline actually renders at
       `rgba(255, 255, 255, .92)` — 92%-alpha, not opaque — and is .9rem
       (14.4px) non-bold, so it needs the normal-text 4.5:1 floor, not the
       3:1 large-text exception. The TRUE rendered color is that translucent
       white alpha-composited over the end-stop, which is darker (lower
       contrast) than opaque white: #B05C33 measured 4.746:1 against opaque
       white but only ≈4.284:1 against the actual composited color — a
       real AA fail. Re-darkened to #A75730, ≈4.662:1 composited (small
       safety margin over the 4.5:1 floor, not a knife's-edge pass). Same
       fix covers .landing-sub, which reuses this identical
       rgba(255,255,255,.92)-on-this-end-stop pattern via .hero-band on the
       landing page (see landing.blade.php's `hero-band landing-hero`). */
    --grad-activity: linear-gradient(135deg, #00415A 0%, #A75730 100%);
    --sans: 'Inter', 'Noto Sans TC', -apple-system, BlinkMacSystemFont,
            'PingFang TC', 'Microsoft JhengHei', 'Heiti TC', sans-serif;
    --shadow-xs: 0 1px 2px rgba(17, 40, 83, 0.04);
    --shadow-sm: 0 2px 6px rgba(17, 40, 83, 0.06);
    --shadow-lg: 0 10px 25px rgba(17, 40, 83, 0.08);
    --shadow-xl: 0 15px 35px rgba(17, 40, 83, 0.12);
    --ease: ease-in-out;
    --dur: 200ms;
}

/* ===================== shared primitives ===================== */

.rl-console, .rl-auth, .rl-public {
    margin: 0;
    font-family: var(--sans);
    color: var(--ink);
    -webkit-text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-feature-settings: 'cv11', 'ss01';
}
.rl-console ::selection, .rl-auth ::selection, .rl-public ::selection { background: var(--navy-tint); }

.rl-console a, .rl-auth a, .rl-public a { color: var(--navy); text-underline-offset: 2px; }

.rl-console :focus-visible, .rl-auth :focus-visible, .rl-public :focus-visible {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
}

/* RLEADS-87 Batch C: skip-to-main-content link, the first focusable element
   inside <body> on every public page (see public/layout.blade.php). Standard
   skip-link convention — off-screen by default, slides on-screen on :focus —
   distinct from the Batch B .ptab-radio 1x1px-hidden-but-focusable pattern:
   a skip link must become a normal readable/clickable element once focused,
   not just silently re-enter the tab order. The global :focus-visible rule
   above still supplies the outline; this just adds a readable background so
   the link is legible against the page once it's on-screen. */
.rl-public .skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    z-index: 1000;
    background: var(--navy);
    color: #fff;
    padding: .6rem 1rem;
    text-decoration: none;
}
.rl-public .skip-link:focus {
    top: 0;
}

.rl-console input, .rl-console textarea, .rl-console select,
.rl-auth input,
.rl-public input, .rl-public textarea, .rl-public select {
    box-sizing: border-box;
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: #fff;
    color: var(--ink);
    font-family: var(--sans);
    padding: .55rem .75rem;
    margin-bottom: .85rem;
}
.rl-console input:focus, .rl-console textarea:focus, .rl-console select:focus,
.rl-auth input:focus,
.rl-public input:focus, .rl-public textarea:focus, .rl-public select:focus {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px var(--navy-tint);
    outline: 0;
}

.rl-console label, .rl-auth label, .rl-public label {
    display: block;
    font-size: .85rem;
    font-weight: 600;
    color: var(--fg-muted);
    margin-bottom: .3rem;
}

/* RLEADS-84 Batch B: subtle required-field marker next to a label's text —
   terra-deep (not --danger red) since an unfilled required field isn't yet
   an error state, just a hint of what's needed before submitting. */
.rl-public .required-mark {
    color: var(--terra-deep);
    font-weight: 500;
    font-size: .78rem;
}

.rl-console .error, .rl-auth .error, .rl-public .error {
    color: var(--danger);
    font-size: .85rem;
    margin: -.45rem 0 .8rem;
}

.rl-console .muted, .rl-public .muted { color: var(--muted); font-size: .88rem; }

/* status flash (console) + flash (public) — success = sage family */
.rl-console .status-flash, .rl-public .flash {
    background: var(--sage-30);
    color: #5C6E2E;
    border: 1px solid #DCE5C8;
    padding: .6rem .9rem;
    border-radius: 9px;
    margin-bottom: 1rem;
    font-size: .9rem;
}
.rl-console .status-flash.warn { background: var(--danger-bg); color: var(--danger); border-color: #F2D2CA; }

/* RLEADS-84: public-page validation error summary — sits above the form so a
   #anchor-fragment redirect on a failed submission lands the user right on
   top of it, not scrolled to the (invisible, from the top) inline errors. */
.rl-public .form-error-summary {
    background: var(--danger-bg);
    color: var(--danger);
    border: 1px solid #F2D2CA;
    border-radius: 9px;
    padding: .6rem .9rem;
    margin-bottom: 1rem;
    font-size: .9rem;
}
.rl-public .form-error-summary p { margin: 0 0 .3rem; font-weight: 600; }
.rl-public .form-error-summary ul { margin: 0; padding-left: 1.2rem; }
.rl-public .form-error-summary li { margin: .15rem 0; }

/* badges / pills — existing vocabulary, inHom hues */
.rl-console .badge, .rl-public .badge {
    display: inline-block;
    padding: .16rem .6rem;
    border-radius: 999px;
    font-size: .78rem;
    font-weight: 600;
    white-space: nowrap;
    background: var(--gray-50);
    color: var(--steel);
}
.rl-console .badge.warn, .rl-public .badge.warn { background: var(--danger-bg); color: var(--danger); }
.rl-console .badge.ok, .rl-public .badge.ok { background: var(--sage-30); color: var(--sage-900); }

/* lead-status pill variants (add st-{status} next to .badge where the status is known)
   Grouping: new=navy, working=steel, visit-stage=terracotta, won=sage, dead=warm gray */
.rl-console .badge.st-new, .rl-console .badge.st-assigned { background: var(--navy-tint); color: var(--navy); }
.rl-console .badge.st-contacted, .rl-console .badge.st-qualified, .rl-console .badge.st-follow_up { background: var(--gray-50); color: var(--steel); }
.rl-console .badge.st-appointment_requested, .rl-console .badge.st-appointment_booked,
.rl-console .badge.st-visited, .rl-console .badge.st-negotiating { background: var(--terra-tint); color: var(--terra-deep); }
.rl-console .badge.st-closed { background: var(--sage-30); color: var(--sage-900); }
.rl-console .badge.st-lost, .rl-console .badge.st-spam_or_invalid { background: var(--paper-alt); color: var(--muted); }

/* ===================== console (body.rl-console) ===================== */

.rl-console { background: var(--paper); font-size: 15px; line-height: 1.65; }

.rl-console nav.topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    background: #fff;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 0 20px;
    height: var(--topbar-h);
}
.rl-console .brand {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-weight: 700;
    letter-spacing: .03em;
    color: var(--navy);
    font-size: 15px;
    white-space: nowrap;
    text-decoration: none;
}
.brandmark {
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
}
.brandmark img { width: 100%; height: 100%; display: block; }
.rl-console .nav-links {
    display: flex;
    align-self: stretch;
    overflow-x: auto;
    scrollbar-width: none;
    /* RLEADS-90 Batch A: CSS-only "scroll shadow" cue for the 901-1200px band
       where items overflow with no visible scrollbar. Two edge-color gradients
       scroll WITH the content (background-attachment: local) so they visually
       recede once you've scrolled past that edge; two shadow gradients stay
       fixed relative to the viewport (background-attachment: scroll) so they
       only show while there's actually more content in that direction. Fade
       color matches nav.topbar's real background (#fff), not --paper.
       RLEADS-96 item 3: the original .12-opacity/14px shadows were
       imperceptible in the 901-1200px band (items hard-truncated with no
       cue). Darkened to .22 and widened to 26px so the "more to scroll" edge
       is actually visible; the paired white fades still cover the shadows at
       each true edge, so the desktop (fully-fitting) state shows nothing. */
    background:
        linear-gradient(to right, #fff 30%, rgba(255, 255, 255, 0)),
        linear-gradient(to left, #fff 30%, rgba(255, 255, 255, 0)),
        radial-gradient(farthest-side at 0 50%, rgba(23, 25, 35, .22), rgba(23, 25, 35, 0)),
        radial-gradient(farthest-side at 100% 50%, rgba(23, 25, 35, .22), rgba(23, 25, 35, 0));
    background-repeat: no-repeat;
    background-color: #fff;
    background-size: 40px 100%, 40px 100%, 26px 100%, 26px 100%;
    background-position: left center, right center, left center, right center;
    background-attachment: local, local, scroll, scroll;
}
.rl-console .nav-links::-webkit-scrollbar { display: none; }
.rl-console .nav-links a {
    display: inline-flex;
    align-items: center;
    padding: 0 12px;
    color: var(--steel);
    text-decoration: none;
    font-size: 13.5px;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
}
.rl-console .nav-links a.active {
    color: var(--navy);
    font-weight: 700;
    border-bottom-color: var(--navy);
}
/* (RLEADS-90's `.nav-group-divider` hairline sat here. RLEADS-168 replaced the
   3 unlabelled clusters it separated with 6 labelled `.nav-group` sections;
   RLEADS-171 item 2 deleted the then-dead rules — see the nav-group block near
   the end of this file.) */
.rl-console nav.topbar form { margin-left: auto; }
.rl-console nav.topbar form button {
    background: none;
    border: 0;
    color: var(--muted);
    cursor: pointer;
    font-size: 13px;
    font-family: var(--sans);
    padding: 6px 9px;
    border-radius: 7px;
    white-space: nowrap;
}
.rl-console nav.topbar form button:hover { background: var(--paper-alt); color: var(--navy); }

.rl-console .wrap { max-width: 1080px; margin: 0 auto; padding: 1.35rem 1.35rem 3rem; }

.rl-console h1 { font-size: 1.3rem; font-weight: 700; letter-spacing: -.01em; color: var(--navy); margin: .2rem 0 1rem; }
.rl-console h2 { font-size: .95rem; font-weight: 600; color: var(--navy); margin: 0 0 .8rem; }

.rl-console .page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: .2rem 0 1rem;
}
.rl-console .page-head h1 { margin: 0; }

.rl-console .card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-sm);
    padding: 1.05rem 1.2rem;
    margin-bottom: 1rem;
    overflow-x: auto;
}
/* RLEADS-163: the console's `notice` card modifier was deleted — no view used it. */

/* SP32 (RLEADS-56): AI usage bars — thin navy track, terracotta fill; reuses
   the design-system tokens only, no new colors. Shared by the AI 方案 settings
   card and the 本月 AI 用量 dashboard card. */
.rl-console .ai-usage { list-style: none; margin: .6rem 0 0; padding: 0; }
.rl-console .ai-usage li { margin-bottom: .7rem; }
.rl-console .ai-usage li:last-child { margin-bottom: 0; }
.rl-console .ai-usage-head { display: flex; justify-content: space-between; gap: .5rem; font-size: .88rem; margin-bottom: .3rem; }
.rl-console .ai-usage-bar { height: 6px; border-radius: 3px; background: var(--navy-tint); overflow: hidden; }
.rl-console .ai-usage-bar > span { display: block; height: 100%; border-radius: 3px; background: var(--terra); }

.rl-console table { width: 100%; border-collapse: collapse; font-size: .9rem; font-variant-numeric: tabular-nums; }
.rl-console th {
    text-align: left;
    font-size: .78rem;
    color: var(--muted);
    font-weight: 600;
    padding: .5rem .6rem;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
.rl-console td { padding: .68rem .6rem; border-bottom: 1px solid var(--hairline); vertical-align: middle; }
.rl-console tbody tr:last-child td { border-bottom: 0; }
.rl-console tbody tr:hover td { background: var(--paper); }
.rl-console tr.is-new td:first-child { box-shadow: inset 3px 0 0 var(--terra); }
.rl-console tr.is-new .row-link { font-weight: 700; }
.rl-console td.num { text-align: right; }

.rl-console a.row-link { color: var(--navy); text-decoration: none; font-weight: 600; }
.rl-console a.row-link:hover { text-decoration: underline; }

.rl-console button.primary, .rl-console a.btn-primary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 9px;
    background: var(--steel);
    color: #fff;
    font-size: .9rem;
    font-weight: 600;
    font-family: var(--sans);
    padding: .55rem 1.1rem;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
}
.rl-console button.primary:hover, .rl-console a.btn-primary:hover { background: var(--midnight); }
.rl-console button.small {
    padding: .3rem .75rem;
    font-size: .82rem;
    font-family: var(--sans);
    border-radius: 7px;
    border: 1px solid var(--steel);
    background: #fff;
    color: var(--steel);
    cursor: pointer;
    white-space: nowrap;
}
.rl-console button.small:hover { background: var(--paper-alt); }
.rl-console button.small.danger { border-color: #E5C4BD; color: var(--danger); }
.rl-console button.small.danger:hover { background: var(--danger-bg); }
.rl-console a.small {
    display: inline-block;
    padding: .3rem .75rem;
    font-size: .82rem;
    border-radius: 7px;
    border: 1px solid var(--steel);
    background: #fff;
    color: var(--steel);
    text-decoration: none;
    white-space: nowrap;
}
.rl-console a.small:hover { background: var(--paper-alt); }

/* RLEADS-111: a small button/link in the FILLED (primary) variant. Source order
   let plain `.small` override `.primary`, so the active analytics period selector
   and the 記錄花費 submit both read as secondary. This restores the emphasis:
   filled steel like a primary button, but at the small size.
   (RLEADS-170: the period selector was never a button — it is a filter, and now
   renders as `.filter-chips`. The variant's real users are the 記錄花費 submit,
   the 名單 bulk-action buttons and the dashboard checklist CTAs.) */
.rl-console a.small.primary, .rl-console button.small.primary {
    background: var(--steel);
    color: #fff;
    border-color: var(--steel);
    font-weight: 600;
}
.rl-console a.small.primary:hover, .rl-console button.small.primary:hover {
    background: var(--midnight);
    color: #fff;
    border-color: var(--midnight);
}

.rl-console .filters { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.rl-console .filters select, .rl-console .filters input, .rl-console select { width: auto; margin-bottom: 0; }
.rl-console .filters select { font-size: .85rem; padding: .45rem .65rem; }
.rl-console textarea { width: 100%; }
.rl-console .card form select { margin-bottom: .85rem; }
.rl-console .card .filters select { margin-bottom: 0; }

.rl-console .inline-form { display: inline; }
.rl-console .inline-actions { display: flex; gap: .5rem; align-items: flex-start; }
/* .card form select sets a stacked-form margin at (0,2,2); these opt-outs must outrank it */
.rl-console .card .inline-actions select { margin-bottom: 0; }
.rl-console .copy-input { width: auto; min-width: 220px; margin: 0; font-size: .8rem; color: var(--muted); }

.rl-console .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start; }
.rl-console .grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; align-items: start; }

.rl-console .week-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .6rem; }
@media (max-width: 720px) {
    .rl-console .week-grid { grid-template-columns: 1fr; }
}

.rl-console ul.timeline { list-style: none; padding: 0; margin: 0; }
.rl-console ul.timeline li { padding: .55rem 0; border-bottom: 1px solid var(--hairline); font-size: .88rem; }
.rl-console ul.timeline li:last-child { border-bottom: 0; }

.rl-console p.list-row, .rl-console .list-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem .6rem;
    padding: .55rem 0;
    margin: 0;
    border-bottom: 1px solid var(--hairline);
    font-size: .9rem;
}
.rl-console .list-row:last-of-type { border-bottom: 0; }
.rl-console .list-row form { display: inline-flex; gap: .4rem; align-items: center; margin: 0 0 0 auto; }
.rl-console .card .list-row form select { margin-bottom: 0; font-size: .82rem; padding: .32rem .5rem; }

.rl-console ul.checklist { list-style: none; padding: 0; margin: 0; }
.rl-console ul.checklist li { display: flex; gap: .55rem; padding: .42rem 0; align-items: baseline; }

/* RLEADS-94 Part 3: 今日優先聯絡 rows — same hairline-separated flex row as
   .list-row, with the 撥打 one-tap call link pushed to the trailing edge. */
.rl-console ul.priority-list { list-style: none; padding: 0; margin: 0; }
.rl-console ul.priority-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem .6rem;
    padding: .55rem 0;
    border-bottom: 1px solid var(--hairline);
    font-size: .9rem;
}
.rl-console ul.priority-list li:last-child { border-bottom: 0; }
.rl-console ul.priority-list li a[href^="tel:"] { margin-left: auto; }

/* KPI cards + pure-CSS bars (analytics) */
.rl-console .kpis { display: grid; grid-template-columns: repeat(5, 1fr); gap: .9rem; margin-bottom: 1rem; }
.rl-console .kpi {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-sm);
    padding: .95rem 1.1rem;
}
.rl-console .kpi .n { font-size: 1.7rem; font-weight: 700; color: var(--navy); font-variant-numeric: tabular-nums; letter-spacing: -.01em; line-height: 1.25; }
.rl-console .kpi .n em { font-style: normal; font-size: .9rem; font-weight: 600; color: var(--muted); margin-left: 2px; }
.rl-console .kpi.hot { border-top: 3px solid var(--terra); }
.rl-console .kpi .l { font-size: .8rem; color: var(--muted); margin-top: 2px; }

/* R19 (RLEADS-68): period trend delta + the tighter KPI row used inside a .card.
   Semantic up/down colours are deliberately NOT the terracotta accent — a trend is a
   judgement (better/worse), and the accent is an emphasis, not a verdict. */
.rl-console .kpi .d { font-size: .75rem; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }

/* The same delta also appears OUTSIDE a .kpi card (RLEADS-82 listings index — an inline
   badge next to a table figure, not a block element inside a card). Only the BASE rules
   genuinely differ (block margin-top vs inline margin-left), so they stay separate; the
   .kpi one above is more specific (3 classes vs 2) and keeps winning inside .kpi.
   RLEADS-163: the up/down/flat colours were declared twice with identical values. They
   are now one selector list each — a selector list is exactly equivalent to repeating the
   rule per selector, so both specificities survive and nothing about the cascade moves. */
.rl-console .d { font-size: .75rem; font-weight: 600; font-variant-numeric: tabular-nums; margin-left: 6px; }
.rl-console .kpi .d.up, .rl-console .d.up { color: var(--sage-900); }
.rl-console .kpi .d.down, .rl-console .d.down { color: var(--danger); }
.rl-console .kpi .d.flat, .rl-console .d.flat { color: var(--muted); }
.rl-console .kpis.inline { grid-template-columns: repeat(4, 1fr); gap: .6rem; margin-bottom: .75rem; }
.rl-console .kpis.inline .kpi { padding: .7rem .8rem; box-shadow: none; }
.rl-console .kpi .n.sm { font-size: 1.15rem; }
.rl-console .spend-input { width: 7rem; text-align: right; font-variant-numeric: tabular-nums; }
.rl-console .spend-form { align-items: center; gap: .5rem; margin: .75rem 0; flex-wrap: wrap; }

@media (max-width: 900px) {
    .rl-console .kpis, .rl-console .kpis.inline { grid-template-columns: repeat(2, 1fr); }
}

.rl-console .funnel { display: grid; gap: .55rem; }
.rl-console .funnel-row { display: grid; grid-template-columns: 96px 1fr 52px; align-items: center; gap: .75rem; font-size: .85rem; }
.rl-console .funnel-row .bar { display: block; height: 20px; border-radius: 5px; background: linear-gradient(90deg, var(--steel), var(--navy)); min-width: 2px; }
.rl-console .funnel-row.won .bar { background: linear-gradient(90deg, var(--terra), #D97B52); }
.rl-console .funnel-row .cnt { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }

.rl-console .chanbars { display: grid; gap: .55rem; }
.rl-console .crow { display: grid; grid-template-columns: 96px 1fr 76px; align-items: center; gap: .75rem; font-size: .85rem; }
.rl-console .crow .bar { height: 12px; border-radius: 4px; background: var(--navy-tint); overflow: hidden; }
.rl-console .crow .bar i { display: block; height: 100%; background: var(--navy); border-radius: 4px; }
.rl-console .crow .cnt { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }

/* Laravel default pagination renders Tailwind markup; keep it small and sane */
.rl-console nav[role="navigation"] { margin-top: .8rem; font-size: .85rem; }
.rl-console nav[role="navigation"] svg { width: 18px; height: 18px; }

/* ===================== auth (body.rl-auth) ===================== */

.rl-auth {
    background: linear-gradient(180deg, var(--paper-alt), var(--paper) 320px);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 24px;
    box-sizing: border-box;
    font-size: 15px;
    line-height: 1.65;
}
.rl-auth .auth-brand {
    display: flex;
    align-items: center;
    gap: 9px;
    font-weight: 700;
    letter-spacing: .03em;
    color: var(--navy);
    font-size: 16px;
    margin-bottom: 20px;
}
.rl-auth .auth-brand .brandmark { width: 36px; height: 36px; }
.rl-auth .card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
    padding: 1.9rem 1.8rem;
    width: 100%;
    max-width: 380px;
    box-sizing: border-box;
}
.rl-auth h1 { font-size: 1.2rem; color: var(--navy); margin: 0 0 1.2rem; text-align: center; }
.rl-auth input { font-size: 16px; }
.rl-auth button {
    width: 100%;
    padding: .65rem;
    border: 0;
    border-radius: 10px;
    background: var(--steel);
    color: #fff;
    font-size: 1rem;
    font-weight: 600;
    font-family: var(--sans);
    cursor: pointer;
}
.rl-auth button:hover { background: var(--midnight); }
.rl-auth .status { color: var(--sage-900); font-size: .85rem; margin-bottom: 1rem; }
.rl-auth .links { margin-top: .9rem; font-size: .85rem; text-align: center; color: var(--muted); }
.rl-auth .links a { color: var(--navy); text-decoration: none; }

/* ===================== public storefront (body.rl-public) ===================== */

.rl-public {
    background: var(--paper);
    border-top: 5px solid var(--navy);
    font-size: 16px;
    line-height: 1.7;
}
.rl-public .wrap { max-width: 560px; margin: 0 auto; padding: 1rem 1rem 2rem; }

.rl-public .card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: var(--shadow-sm);
    padding: 1.2rem 1.3rem;
    margin-bottom: .9rem;
}

.rl-public h1 { font-size: 1.3rem; font-weight: 700; letter-spacing: -.01em; color: var(--navy); margin: .2rem 0 .5rem; line-height: 1.5; }
.rl-public h2 {
    font-size: 1rem;
    font-weight: 600;
    color: var(--navy);
    margin: 0 0 .8rem;
    display: flex;
    align-items: center;
    gap: 10px;
}
.rl-public h2::after { content: ""; flex: 1; height: 1px; background: var(--border); }

.rl-public .price { color: var(--navy); font-size: 1.15rem; font-weight: 700; font-variant-numeric: tabular-nums; }

/* brand-page hero band (the one canonical inHom gradient) — SP14 (RLEADS-31)
   restructured this into ONE composition (avatar + identity + badges + CTAs
   inside the gradient itself), replacing the old thin label band + separate
   overlapping .idcard card below it. */
.rl-public .hero-band {
    background: var(--grad-activity);
    color: #fff;
    padding: 28px 22px;
    margin: -1rem -1rem 0;
    position: relative;
    overflow: hidden;
}
.rl-public .hero-band::before,
.rl-public .hero-band::after {
    content: "";
    position: absolute;
    border-radius: 50%;
    background: rgba(255, 255, 255, .1);
}
.rl-public .hero-band::before { width: 120px; height: 120px; right: -40px; top: -48px; }
.rl-public .hero-band::after { width: 72px; height: 72px; left: -26px; bottom: -30px; background: rgba(255, 255, 255, .08); }

.rl-public .hero-flex { display: flex; align-items: center; gap: 1.1rem; text-align: left; position: relative; }
.rl-public .hero-main { flex: 1 1 auto; min-width: 0; }
.rl-public .hero-band h1 { color: #fff; font-size: 1.6rem; margin: 0 0 .15rem; }

.rl-public .hero-photo, .rl-public .avatar-fallback {
    width: 92px;
    height: 92px;
    border-radius: 50%;
    object-fit: cover;
    border: 4px solid #fff;
    box-shadow: 0 4px 14px rgba(17, 40, 83, .22);
    margin: 0;
    display: block;
    flex: none;
    box-sizing: border-box;
}
.rl-public .avatar-fallback {
    background: linear-gradient(135deg, var(--navy-tint), #fff);
    color: var(--navy);
    font-size: 2rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}
.rl-public .headline { font-size: .9rem; color: rgba(255, 255, 255, .92); margin: .25rem 0 .6rem; }

/* identity/compliance badges — 經紀業 + 加盟/直營, 證號, 服務區 (SP14) */
.rl-public .hb-row { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }
.rl-public .hb {
    display: inline-block;
    padding: .25rem .7rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, .16);
    border: 1px solid rgba(255, 255, 255, .35);
    color: #fff;
    font-size: .74rem;
    font-weight: 600;
    white-space: nowrap;
}
/* RLEADS-81 Batch A: the 服務區 badge no longer caps at 2 areas, so it can
   grow well past a single line's width — .hero-band clips overflow, so this
   badge (unlike the short 🏢/證號 ones) needs to wrap instead of staying
   nowrap. */
.rl-public .hb.hb-wrap { white-space: normal; max-width: 100%; }

/* hero CTA column — reuses .btn/.btn.line/.btn.ghost, recolored for the
   on-gradient context the same way .cta-row scopes .btn for the listing hero */
.rl-public .hero-cta { display: flex; flex-direction: column; gap: .5rem; flex: none; width: 172px; }
.rl-public .hero-cta .btn { background: #fff; color: var(--navy); }
.rl-public .hero-cta .btn:hover { background: var(--paper-alt); }
.rl-public .hero-cta .btn.ghost { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, .6); }
.rl-public .hero-cta .btn.ghost:hover { background: rgba(255, 255, 255, .12); }

@media (max-width: 720px) {
    .rl-public .hero-flex { flex-direction: column; text-align: center; }
    .rl-public .hero-main { text-align: center; }
    .rl-public .hb-row { justify-content: center; }
    .rl-public .hero-cta { width: 100%; }
}

/* stats strip (在售物件 / 成交實績 / 頁面瀏覽) directly under the hero band */
.rl-public .stats {
    display: flex;
    justify-content: space-around;
    text-align: center;
    background: #fff;
    padding: .9rem .5rem;
    margin: 0 -1rem .9rem;
    border-bottom: 1px solid var(--border);
}
.rl-public .stat { display: flex; flex-direction: column; gap: .2rem; }
.rl-public .stat b { font-size: 1.25rem; color: var(--navy); font-variant-numeric: tabular-nums; }
.rl-public .stat span { font-size: .74rem; color: var(--muted); }

/* 委託估價 CTA band — the accent-border idiom
   (border-left: 4px solid var(--terra); background: #FFFDFA) */
.rl-public .card.sellband {
    border-left: 4px solid var(--terra);
    background: #FFFDFA;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
}
.rl-public .card.sellband strong { color: var(--navy); }
.rl-public .card.sellband .btn { width: auto; flex: none; }

.rl-public .btn {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: center;
    padding: .75rem .8rem;
    border-radius: 11px;
    background: var(--steel);
    color: #fff;
    text-decoration: none;
    font-size: .95rem;
    font-weight: 600;
}
.rl-public .btn:hover { background: var(--midnight); }
.rl-public .btn.line { background: var(--line-green); }
.rl-public .btn.line:hover { background: #05B34C; }
.rl-public .btn.ghost { background: #fff; color: var(--steel); border: 1px solid var(--steel); }
.rl-public .btn.ghost:hover { background: var(--paper-alt); }
.rl-public .btn.soft { background: var(--paper-alt); color: var(--navy); }
.rl-public .btn.soft:hover { background: var(--gray-50); }

/* chips (service areas / specialties reuse .badge inside .chips) */
.rl-public .chips { display: flex; flex-wrap: wrap; gap: .45rem; }
.rl-public .chips .badge {
    background: var(--paper-alt);
    border: 1px solid var(--border);
    color: var(--fg-muted);
    font-weight: 400;
    font-size: .82rem;
    padding: .22rem .75rem;
}

/* listing rows on the brand page */
.rl-public .listing {
    display: flex;
    gap: .75rem;
    align-items: center;
    padding: .7rem 0;
    border-bottom: 1px solid var(--hairline);
    text-decoration: none;
    color: inherit;
}
.rl-public .listing:last-child { border-bottom: 0; }
.rl-public .listing-title { display: block; font-weight: 600; font-size: .92rem; line-height: 1.45; margin-bottom: .1rem; }
.rl-public .listing .listing-body { flex: 1; min-width: 0; }
.rl-public .listing .price { margin-left: auto; font-size: .98rem; white-space: nowrap; flex: none; }
.rl-public .thumb {
    width: 56px;
    height: 46px;
    border-radius: 8px;
    background: linear-gradient(135deg, var(--navy-tint), var(--paper-alt));
    color: var(--navy);
    font-size: .72rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
}

.rl-public .deal { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; padding: .5rem 0; border-bottom: 1px solid var(--hairline); }
.rl-public .deal:last-of-type { border-bottom: 0; }
.rl-public .deal-title { font-weight: 600; }

/* listing detail page */
.rl-public .lprice { display: flex; align-items: baseline; gap: .7rem; margin: .4rem 0 .2rem; }
.rl-public .lprice .price { font-size: 1.75rem; letter-spacing: -.01em; }
.rl-public .specs { display: flex; gap: .5rem; flex-wrap: wrap; margin: .7rem 0 .2rem; }
.rl-public .spec { background: var(--paper-alt); border-radius: 8px; padding: .4rem .8rem; font-size: .78rem; color: var(--fg-muted); text-align: center; }
.rl-public .spec b { display: block; font-size: .95rem; color: var(--navy); font-variant-numeric: tabular-nums; }

/* forms */
.rl-public input, .rl-public textarea, .rl-public select { font-size: 16px; padding: .6rem .75rem; }
.rl-public button {
    width: 100%;
    padding: .75rem;
    border: 0;
    border-radius: 11px;
    background: var(--steel);
    color: #fff;
    font-size: 1rem;
    font-weight: 600;
    font-family: var(--sans);
    cursor: pointer;
}
.rl-public button:hover { background: var(--midnight); }
.rl-public button.secondary { background: #fff; color: var(--danger); border: 1px solid #E5C4BD; }
.rl-public button.secondary:hover { background: var(--danger-bg); }

.rl-public .type-toggle { display: flex; gap: .5rem; margin-bottom: 1rem; }
.rl-public .type-toggle label {
    flex: 1;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: .6rem;
    text-align: center;
    cursor: pointer;
    font-weight: 400;
    color: var(--muted);
    margin-bottom: 0;
}
/* RLEADS-156: was `display: none`, which strips the radios from the tab order —
   keyboard/AT users could not operate 預約看屋/回電諮詢, 需求, or 委託類型 at
   all. Visually-hidden-but-focusable instead, matching the POI-tab .ptab-radio
   pattern below; the :focus-visible ring lives on the label via :has(), which
   this file already relies on (label:has(input:checked) right below). */
.rl-public .type-toggle input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
.rl-public .type-toggle input:checked + span { color: var(--navy); font-weight: 700; }
.rl-public .type-toggle label:has(input:checked) {
    border-color: var(--navy);
    background: var(--navy-tint);
}
.rl-public .type-toggle label:has(input:focus-visible) {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
}

.rl-public .consent { font-size: .85rem; display: flex; gap: .5rem; align-items: flex-start; margin: .6rem 0 1rem; color: var(--muted); font-weight: 400; }
.rl-public .consent input { width: auto; margin: .2rem 0 0; accent-color: var(--navy); }

.rl-public .disclosure { margin-top: 1rem; font-size: .75rem; text-align: center; color: var(--muted); line-height: 1.7; }

/* ===================== responsive + motion ===================== */

@media (max-width: 720px) {
    .rl-console .grid2, .rl-console .grid3 { grid-template-columns: 1fr; }
    .rl-console .kpis { grid-template-columns: repeat(2, 1fr); }
    .rl-console .wrap { padding: 1rem .9rem 2.5rem; }
    .rl-console nav.topbar { gap: 12px; padding: 0 14px; }
}

/* ===================== SP8: listing photo gallery (RLEADS-23) ===================== */

/* console: photo grid + per-photo controls on the listing edit form */
.rl-console .photo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: .75rem;
    margin-bottom: 1rem;
}
.rl-console .photo-item {
    position: relative;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    background: var(--paper-alt);
}
.rl-console .photo-item img { width: 100%; height: 90px; object-fit: cover; display: block; }
.rl-console .photo-item .badge { position: absolute; top: 6px; left: 6px; }
.rl-console .photo-actions { display: flex; gap: .3rem; padding: .4rem; background: #fff; }
.rl-console .photo-actions form { flex: 1; margin: 0; }
.rl-console .photo-actions button { width: 100%; }

/* console: small cover thumb on the listings index table + thumb/title pairing */
.rl-console .thumb-cell { display: flex; align-items: center; gap: .6rem; }
.rl-console .row-thumb { width: 44px; height: 36px; border-radius: 7px; object-fit: cover; display: block; flex: none; }

/* public: cover photo swapped in for the brand-page `.thumb` placeholder */
.rl-public .thumb-img { width: 56px; height: 46px; border-radius: 8px; object-fit: cover; flex: none; }

/* public: Zillow-style gallery on the listing detail page */
.rl-public .gallery {
    display: grid;
    gap: 6px;
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: .9rem;
    height: 46vw;
    max-height: 320px;
    min-height: 170px;
}
.rl-public .gallery img { display: block; width: 100%; height: 100%; object-fit: cover; }
.rl-public .gallery .cover, .rl-public .gallery .thumb-tile { position: relative; overflow: hidden; }
.rl-public .gallery.single { grid-template-columns: 1fr; }
.rl-public .gallery.row { display: flex; gap: 6px; height: 46vw; max-height: 220px; min-height: 130px; }
.rl-public .gallery.row .cover { flex: 2 1 0; }
.rl-public .gallery.row .thumb-tile { flex: 1 1 0; }
.rl-public .gallery.grid5 { grid-template-columns: 2fr 1fr 1fr; grid-template-rows: 1fr 1fr; }
.rl-public .gallery.grid5 .cover { grid-row: 1 / span 2; }
.rl-public .gallery .more-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 65, 90, .6);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: .95rem;
}

/* ===================== SP9: listing spec depth + hero CTA (RLEADS-24) ===================== */

/* hero CTA row directly under the price — a horizontal sibling of the vertical
   .cta stack used on the brand page; reuses .btn as the base button */
.rl-public .cta-row { display: flex; gap: .55rem; flex-wrap: wrap; margin: .7rem 0 .2rem; }
.rl-public .cta-row .btn { width: auto; flex: 1 1 auto; }

/* LINE outline button (listing hero row) — distinct from the solid .btn.line
   used on the brand-page profile card; #06C755 is LINE's own brand color,
   the one allowed non-token hue in this stylesheet */
.rl-public .btn-line { background: #fff; color: var(--line-green); border: 1px solid var(--line-green); }
.rl-public .btn-line:hover { background: rgba(6, 199, 85, .08); }

/* 特色標籤 chips under the spec grid */
.rl-public .tags { display: flex; flex-wrap: wrap; gap: .4rem; margin: .7rem 0 .2rem; }
.rl-public .tag { background: var(--terra-tint); color: var(--terra-deep); border-radius: 999px; padding: .2rem .7rem; font-size: .78rem; font-weight: 600; }

/* brand-page card meta chips (屋齡 / 型態) — small sibling of .badge. SP15
   (RLEADS-32) moved this from an inline run of text (needed its own left
   margin) into .lcard-meta, a flex row with `gap` — spacing is the
   container's job now, so the chip itself carries none. */
.rl-public .mchip { display: inline-block; background: var(--paper-alt); color: var(--steel); border-radius: 999px; padding: .1rem .55rem; font-size: .68rem; font-weight: 600; }

/* ===================== SP10: LINE login/bind, lead alerts, share links (RLEADS-26) ===================== */

/* error-flash variant for the auth layout — success already has .status (sage) */
.rl-auth .status.warn { color: var(--danger); }

/* "或" divider between the email form and the LINE login button */
.rl-auth .divider { display: flex; align-items: center; gap: .6rem; margin: 1rem 0; color: var(--muted); font-size: .8rem; }
.rl-auth .divider::before, .rl-auth .divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* full-width solid LINE button under the login form (auth scope) */
.rl-auth a.btn-line-solid {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: center;
    padding: .65rem;
    border-radius: 10px;
    background: var(--line-green);
    color: #fff;
    font-size: 1rem;
    font-weight: 600;
    font-family: var(--sans);
    text-decoration: none;
}
.rl-auth a.btn-line-solid:hover { background: #05B34C; }

/* inline solid LINE button — profile card's 綁定 LINE CTA (console scope) */
.rl-console a.btn-line-solid {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 9px;
    background: var(--line-green);
    color: #fff;
    font-size: .9rem;
    font-weight: 600;
    font-family: var(--sans);
    padding: .55rem 1.1rem;
    text-decoration: none;
    white-space: nowrap;
}
.rl-console a.btn-line-solid:hover { background: #05B34C; }

/* small outline LINE button — promote console's per-link 分享到 LINE anchor */
.rl-console a.btn-line {
    display: inline-block;
    padding: .3rem .75rem;
    font-size: .82rem;
    border-radius: 7px;
    border: 1px solid var(--line-green);
    background: #fff;
    color: var(--line-green);
    text-decoration: none;
    white-space: nowrap;
}
/* ===================== SP11: 位置與周邊 POI map (RLEADS-27) ===================== */

.rl-public .poi-wrap { display: grid; grid-template-columns: 1.1fr 1fr; gap: .9rem; align-items: start; }
.rl-public .poi-map { height: 260px; border-radius: 12px; overflow: hidden; background: var(--paper-alt); }

/* CSS-only tabs: visually-hidden (but focusable) radios + label pills, matching
   the .type-toggle pattern used on the inquiry form. `for`/`id` pairs the label
   with its radio; general sibling selectors below key off :checked to show the
   matching pill + rows — no JS involved. */
/* RLEADS-87 Batch B: was `display: none`, which removes an element from the tab
   order entirely — a keyboard user could not Tab to these radios at all, making
   the POI tabs completely inoperable without a mouse. `position:absolute` +
   1x1px + `opacity:0` keeps the element focusable/operable (native radio-group
   Tab/arrow-key behavior) while remaining visually hidden; NOT display:none or
   visibility:hidden, both of which also strip focusability. */
.rl-public .ptab-radio {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
.rl-public .ptabs { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .6rem; }
.rl-public .ptab {
    display: inline-block;
    padding: .3rem .75rem;
    border-radius: 999px;
    background: var(--paper-alt);
    color: var(--steel);
    font-size: .8rem;
    font-weight: 600;
    cursor: pointer;
    margin-bottom: 0;
}
.rl-public .prows { display: none; }
#poi-tab-transit:checked ~ .ptabs label[for="poi-tab-transit"],
#poi-tab-school:checked ~ .ptabs label[for="poi-tab-school"],
#poi-tab-life:checked ~ .ptabs label[for="poi-tab-life"],
#poi-tab-medical:checked ~ .ptabs label[for="poi-tab-medical"],
#poi-tab-leisure:checked ~ .ptabs label[for="poi-tab-leisure"] {
    background: var(--navy);
    color: #fff;
}
/* RLEADS-87 Batch B: visible focus indicator for the currently-focused POI tab.
   The radio and its paired .ptab label are SIBLINGS here (all radios render
   before the .ptabs wrapper in listing.blade.php), not nested — so a bare
   `.ptab:focus-within` rule can never match anything. This mirrors the
   :checked sibling-selector block above, keyed off :focus-visible instead, and
   reuses the site-wide focus-ring convention (2px solid var(--navy), 2px
   offset) rather than inventing a new style. */
#poi-tab-transit:focus-visible ~ .ptabs label[for="poi-tab-transit"],
#poi-tab-school:focus-visible ~ .ptabs label[for="poi-tab-school"],
#poi-tab-life:focus-visible ~ .ptabs label[for="poi-tab-life"],
#poi-tab-medical:focus-visible ~ .ptabs label[for="poi-tab-medical"],
#poi-tab-leisure:focus-visible ~ .ptabs label[for="poi-tab-leisure"] {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
}
#poi-tab-transit:checked ~ #prows-transit,
#poi-tab-school:checked ~ #prows-school,
#poi-tab-life:checked ~ #prows-life,
#poi-tab-medical:checked ~ #prows-medical,
#poi-tab-leisure:checked ~ #prows-leisure {
    display: block;
}

.rl-public .prow {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .6rem;
    padding: .45rem 0;
    border-bottom: 1px solid var(--hairline);
    font-size: .85rem;
}
.rl-public .prow:last-child { border-bottom: 0; }
.rl-public .prow .pname { color: var(--ink); }
.rl-public .prow .pdist { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

.rl-public .poi-disclaimer { font-size: .75rem; color: var(--muted); margin: .7rem 0 0; line-height: 1.6; }

@media (max-width: 720px) {
    .rl-public .poi-wrap { grid-template-columns: 1fr; }
}

/* ===================== RLEADS-81 Batch D: YouTube video embed ===================== */

/* 16:9 responsive embed. aspect-ratio is already relied on elsewhere in this
   file (.lcard-ph), so it's safe to use here too instead of the older
   padding-bottom-percentage hack. */
.rl-public .listing-video {
    aspect-ratio: 16 / 9;
    border-radius: 12px;
    overflow: hidden;
    background: var(--paper-alt);
}
.rl-public .listing-video iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

@media (prefers-reduced-motion: no-preference) {
    .rl-console button, .rl-console a.btn-primary, .rl-console a.small, .rl-console tbody tr td,
    .rl-console a.btn-line, .rl-console a.btn-line-solid,
    .rl-auth button, .rl-auth a.btn-line-solid,
    .rl-public .btn, .rl-public button,
    .rl-console input, .rl-console textarea, .rl-console select,
    .rl-auth input, .rl-public input, .rl-public textarea, .rl-public select {
        transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                    box-shadow var(--dur) var(--ease), color var(--dur) var(--ease);
    }
}

/* ===================== SP12: brand-page theme presets (RLEADS-28) ===================== */

/* Curated public-page theme presets (App\Models\AgentProfile::THEMES), applied via
   [data-theme] on <body class="rl-public"> (see resources/views/public/layout.blade.php).
   Each preset overrides ONLY the custom properties that actually drive rl-public
   visuals — never restates component rules:
     --navy           primary color: headings, links, price, idcard, focus ring
     --steel          filled-button fill (.btn, form buttons, .btn.ghost outline)
     --midnight       filled-button hover/deep state
     --grad-activity  hero-band gradient (brand-page identity band)
   Semantic colors are theme-independent and untouched by design: LINE green
   (--line-green), 成交 sage (--sage-*), 特色標籤/terracotta accent (--terra*).
   'classic' is the stock inHom look and has no override block — its absence
   (or an explicit data-theme="classic") both resolve to the :root defaults.

   WCAG contrast vs #fff (relative-luminance method; AA floor for normal text
   is 4.5:1), for the button-fill and primary/heading colors each preset uses:
     terra    primary/button #B05C33 ≈ 4.75:1   hover #8A4526 ≈ 7.09:1
     forest   primary #3E5622 ≈ 8.21:1   button #5C7A33 ≈ 4.90:1   hover #2E401A ≈ 11.26:1
     midnight primary/button #1C2E4A ≈ 13.64:1   hover #131F33 ≈ 16.52:1
   All four clear the 4.5:1 floor (terra is tightest, ~5% of headroom to spare).

   RLEADS-87 Batch A: --grad-activity's end-stop is where .hero-band's white
   .headline text sits (worst case, per the audit's own methodology). None of
   these presets inherit the :root fix (each fully restates --grad-activity),
   so each end-stop that failed 4.5:1 against white was darkened individually,
   proportionally within its own hue — not copied from another theme.

   CORRECTION (review finding): the numbers below were originally verified
   against OPAQUE white, but .headline (and .landing-sub, which reuses this
   same end-stop via .hero-band on the landing page) actually render at
   `rgba(255, 255, 255, .92)`, and .headline's 14.4px non-bold size needs the
   4.5:1 normal-text floor. Composited (92%-alpha white over the end-stop,
   not raw hex-vs-hex) is measurably darker/lower-contrast than opaque white,
   which flipped classic and terra from "passing" to real AA fails:
     classic  end #E88D67 → #B05C33 → #A75730
              (opaque-white: 2.489:1 → 4.746:1 | composited: 2.489:1 →
              4.284:1 FAIL → 4.662:1 PASS)
     terra    end #E88D67 → #BD4C1D → #B84A1C (#B05C33 was unusable as the
              first darkening target — it's already this theme's
              start-stop, which would collapse the gradient to a flat fill)
              (opaque-white: 2.489:1 → 4.982:1 | composited: 2.489:1 →
              4.465:1 FAIL → 4.657:1 PASS)
     forest   end #ABC270 → #637632, unchanged by this correction
              (opaque-white: 1.966:1 → 5.038:1 | composited: ≈4.554:1 PASS,
              re-verified — the tightest-margin theme, but still clears)
     midnight end #52677D unchanged, unchanged by this correction
              (opaque-white: 5.841:1 | composited: ≈5.246:1 PASS,
              re-verified with the largest margin) */

.rl-public[data-theme="terra"] {
    --navy: #B05C33;
    --steel: #B05C33;
    --midnight: #8A4526;
    --grad-activity: linear-gradient(135deg, #B05C33 0%, #B84A1C 100%);
}

.rl-public[data-theme="forest"] {
    --navy: #3E5622;
    --steel: #5C7A33;
    --midnight: #2E401A;
    --grad-activity: linear-gradient(135deg, #3E5622 0%, #637632 100%);
}

.rl-public[data-theme="midnight"] {
    --navy: #1C2E4A;
    --steel: #1C2E4A;
    --midnight: #131F33;
    --grad-activity: linear-gradient(135deg, #1C2E4A 0%, #52677D 100%);
}

/* profile-editor swatch dots (console scope) — one hardcoded hex per preset,
   these ARE the preset definitions so a literal color is correct here. */
.rl-console .theme-dot {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    margin-right: .4rem;
    vertical-align: -2px;
    border: 1px solid rgba(0, 0, 0, .12);
}
.rl-console .theme-dot-classic { background: #00415A; }
.rl-console .theme-dot-terra { background: #B05C33; }
.rl-console .theme-dot-forest { background: #3E5622; }
.rl-console .theme-dot-midnight { background: #1C2E4A; }

/* RLEADS-112: live brand-theme preview band in the editor. Mirrors each public
   theme's --grad-activity hero gradient so the agent sees the look before
   publishing. data-theme is toggled by a tiny inline script on radio change. */
.rl-console .theme-preview {
    display: flex; align-items: center;
    height: 52px; padding: 0 16px; margin: 0 0 .85rem;
    border-radius: 9px; color: #fff; font-weight: 700; font-size: .95rem;
    background: linear-gradient(135deg, #00415A 0%, #A75730 100%);
}
.rl-console .theme-preview[data-theme="terra"] { background: linear-gradient(135deg, #B05C33 0%, #B84A1C 100%); }
.rl-console .theme-preview[data-theme="forest"] { background: linear-gradient(135deg, #3E5622 0%, #637632 100%); }
.rl-console .theme-preview[data-theme="midnight"] { background: linear-gradient(135deg, #1C2E4A 0%, #52677D 100%); }

/* ===================== SP14: mobile sticky action bar, listing page (RLEADS-31) ===================== */

/* Hidden by default (desktop); the ≤720px media query below turns it into a
   fixed bottom bar. No JS — pure anchor links, #book is the existing booking
   form already on the page. */
.rl-public .sticky-cta { display: none; }

@media (max-width: 720px) {
    .rl-public .sticky-cta {
        display: flex;
        gap: .5rem;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(255, 255, 255, .97);
        border-top: 1px solid var(--border);
        box-shadow: 0 -6px 20px rgba(17, 40, 83, .08);
        padding: .6rem .8rem calc(.6rem + env(safe-area-inset-bottom));
        z-index: 20;
    }
    .rl-public .sticky-cta .sc-icon {
        flex: none;
        width: 46px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 11px;
        border: 1px solid var(--steel);
        color: var(--steel);
        font-size: .68rem;
        font-weight: 600;
        text-decoration: none;
    }
    .rl-public .sticky-cta .sc-primary {
        flex: 1 1 auto;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 11px;
        background: var(--steel);
        color: #fff;
        font-weight: 600;
        text-decoration: none;
        font-size: .92rem;
    }

    /* Room for the fixed bar so it never covers the booking form's submit button.
       RLEADS-84 Batch D: scoped to body.has-sticky-cta (set only on the listing
       page's <body> — see public.layout/listing.blade.php) rather than the
       blanket .rl-public selector, which used to pad every public page's
       bottom (brand/sell pages have no sticky-cta bar to clear, so they were
       left with unnecessary blank space). */
    body.has-sticky-cta .wrap { padding-bottom: 5.5rem; }

    /* RLEADS-84 Batch E, item 8: the AI voice-call widget (public/partials/
       ai-call-widget.blade.php) self-injects a fixed bottom-right bubble at
       runtime via a light-DOM host div (.inhom-voice-host, appended straight
       to <body> by the third-party widget.js) that carries an *open* Shadow
       DOM subtree. Verified directly against the live widget.js
       (voice-assistant-inbound…/widget/widget.js, fetched 2026-07-16): its
       internal <style> hardcodes `.inhom-voice { position:fixed; bottom:24px;
       right:24px; }` with no CSS custom property and no `part` attribute, so
       a normal selector can't reach in and restyle it, and the script exposes
       no offset/position config parameter (only data-project/key/context/
       max-seconds/fallback-*) — a plain descendant-selector or config-param
       fix is genuinely not available.
       What *is* available: styling the host itself, which lives in ordinary
       light DOM. Per the CSS Transforms spec, an ancestor with a `transform`
       other than none establishes a new containing block for position:fixed
       descendants — including ones inside its shadow tree, since layout
       operates on the flattened/composed tree, not the light-DOM tree alone.
       Giving the host its own position:fixed (rather than relying on
       transform alone) keeps the host correctly viewport-anchored so the
       widget keeps tracking scroll instead of scrolling away with the page;
       width/height:0 keep the (otherwise contentless) host from painting or
       hit-testing anything of its own. With that containing block in place,
       the shadow-internal bottom:24px/right:24px (left untouched) resolves
       against this host's 0×0 box instead of the viewport — i.e. lifted
       clear of the sticky-cta bar below while keeping the same right offset.
       Confirmed empirically in a real browser against the live widget script
       (idle/connecting/fallback panel states, plus post-scroll position) —
       this can't be asserted via phpunit, see AiWidgetStickyCtaOffsetTest for
       the markup-presence test that ensures this rule has something to
       hook into. */
    body.has-sticky-cta .inhom-voice-host {
        position: fixed;
        right: 0;
        bottom: 5.5rem;
        width: 0;
        height: 0;
        transform: translateZ(0);
    }
}

/* ===================== SP15: listing two-column layout + brand-page card grid
   (RLEADS-32) — layout parity with the approved storefront mockup. Breakpoints
   use 900px (grid collapse) and 720px (further collapse), matching the file's
   existing 720px convention rather than introducing a new 600px step. ===== */

/* wide wrap variant for the listing page's two-column grid — baseline .wrap is
   the narrow 560px single-column width shared by every other public page */
.rl-public .wrap.wide { max-width: 980px; }

/* listing page topbar — agent identity + quick-contact pills above the gallery */
.rl-public .ptop {
    display: flex;
    align-items: center;
    gap: .8rem;
    padding: 0 0 .9rem;
    margin-bottom: .9rem;
    border-bottom: 1px solid var(--border);
}
.rl-public .ptop-brand { display: flex; align-items: center; gap: .5rem; font-weight: 700; color: var(--navy); font-size: .95rem; }
.rl-public .ptop-brand a { color: inherit; text-decoration: none; }
.rl-public .ptop-mark {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: var(--grad-activity);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .78rem;
    font-weight: 700;
    flex: none;
}
.rl-public .ptop-spacer { flex: 1; }
.rl-public .tbtn {
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: .28rem .8rem;
    font-size: .78rem;
    color: var(--steel);
    font-weight: 600;
    white-space: nowrap;
    background: #fff;
    text-decoration: none;
}
.rl-public .tbtn.line { color: #fff; background: var(--line-green); border-color: var(--line-green); }

/* pill row above the listing title — 售/租, 型態, NEW */
.rl-public .pillrow { display: flex; gap: .4rem; flex-wrap: wrap; margin: 0 0 .5rem; }
.rl-public .pill { font-size: .74rem; font-weight: 700; padding: .18rem .65rem; border-radius: 999px; }
.rl-public .pill-tx { background: var(--navy-tint); color: var(--navy); }
.rl-public .pill-kind { background: var(--paper-alt); color: var(--steel); border: 1px solid var(--border); }
.rl-public .pill-new { background: var(--terra-tint); color: var(--terra-deep); }

/* two-column listing layout — main content + sticky agent sidebar */
.rl-public .lst-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 1.4rem; align-items: start; }
.rl-public .lst-main { min-width: 0; }

.rl-public .agent-card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: var(--shadow-sm);
    padding: 1.2rem 1.3rem;
    position: sticky;
    top: 1rem;
}
.rl-public .agent-avatar {
    width: 76px;
    height: 76px;
    border-radius: 50%;
    object-fit: cover;
    margin: 0 auto .6rem;
    display: block;
}
.rl-public .agent-card .avatar-fallback { width: 76px; height: 76px; font-size: 1.7rem; margin: 0 auto .6rem; }
.rl-public .agent-name { text-align: center; font-weight: 700; color: var(--navy-deep); font-size: 1rem; margin: 0; }
.rl-public .agent-headline { text-align: center; color: var(--muted); font-size: .78rem; margin: .15rem 0 .6rem; }
.rl-public .agent-brokerage { display: block; text-align: center; margin: 0 auto .35rem; width: fit-content; }
.rl-public .agent-license { text-align: center; font-size: .75rem; color: var(--muted); margin: .1rem 0 .8rem; }
.rl-public .agent-stats {
    display: flex;
    justify-content: space-around;
    text-align: center;
    border-top: 1px solid var(--paper-alt);
    border-bottom: 1px solid var(--paper-alt);
    padding: .55rem 0;
    margin-bottom: .8rem;
}
.rl-public .agent-stats b { display: block; color: var(--navy); font-size: .95rem; font-variant-numeric: tabular-nums; }
.rl-public .agent-stats span { font-size: .66rem; color: var(--muted); }
.rl-public .agent-card .btn { margin-bottom: .5rem; }
.rl-public .agent-safe { font-size: .66rem; color: var(--muted); text-align: center; margin: .4rem 0 0; }
.rl-public .agent-more { display: block; text-align: center; font-size: .78rem; margin-top: .6rem; }

@media (max-width: 900px) {
    /* Sidebar hidden rather than reflowed to the bottom: SP14's mobile sticky-cta
       bar plus the inline cta-row buttons already carry mobile conversion, so a
       second, longer agent block below the fold would be pure redundancy. */
    .rl-public .lst-grid { grid-template-columns: 1fr; }
    .rl-public .agent-card { display: none; }
}

/* brand-page photo-card grid — replaces the old plain .listing row list */
.rl-public .lgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.rl-public .lcard {
    display: block;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    color: inherit;
}
.rl-public .lcard-ph {
    height: 130px;
    position: relative;
    background: linear-gradient(160deg, #EADDCB, #D9C4A8);
}
.rl-public .lcard-ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rl-public .lcard-ph .ph-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(0, 51, 70, .55);
    font-weight: 700;
    font-size: .95rem;
}
.rl-public .lcard-body { padding: .7rem .85rem .8rem; }
.rl-public .lcard-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: .85rem;
    font-weight: 700;
    color: var(--navy-deep);
    margin: 0 0 .35rem;
    line-height: 1.4;
}
/* RLEADS-81 Batch A: 行政區/社區/房廳衛 meta line, between the title and the
   existing age/kind .mchip row — sized between .lcard-title (.85rem) and
   .lcard-views (.75rem) to read as secondary info. */
.rl-public .lcard-loc { font-size: .72rem; color: var(--muted); margin: 0 0 .35rem; }
.rl-public .lcard-meta { display: flex; gap: .35rem; flex-wrap: wrap; margin-bottom: .45rem; }
.rl-public .lcard-price-row { display: flex; align-items: baseline; }
.rl-public .lcard-price { color: var(--navy); font-weight: 700; font-size: .98rem; font-variant-numeric: tabular-nums; }
.rl-public .lcard-views { margin-left: auto; font-size: .75rem; color: var(--muted); }

@media (max-width: 900px) { .rl-public .lgrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 720px) { .rl-public .lgrid { grid-template-columns: 1fr; } }

/* RLEADS-81 Batch C: client-side listing filter chips (brand-filters.js).
   Pill buttons follow this system's existing .mchip/.pill-new shape (999px
   radius, small padding); the active state borrows the terracotta accent used
   for .pill-new elsewhere in this file, giving selected chips clear visual
   weight against the muted default state. .filtered-out is toggled purely by
   JS — without it (or without JS at all) every .lcard stays visible. */
.rl-public .filter-panel { display: flex; flex-direction: column; gap: .55rem; }
.rl-public .filter-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.rl-public .filter-chip {
    display: inline-flex;
    /* RLEADS-156: the generic `.rl-public button { width: 100% }` rule was
       stretching every chip into a full-width stacked row (the chips never
       shipped a width of their own, and the panel only renders with >1
       listing, so stage never showed it). */
    width: auto;
    align-items: center;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: #fff;
    color: var(--steel);
    padding: .3rem .8rem;
    font-size: .78rem;
    font-weight: 600;
    cursor: pointer;
    line-height: 1.4;
}
.rl-public .filter-chip:hover { background: var(--paper-alt); }
.rl-public .filter-chip.active {
    background: var(--terra-tint);
    color: var(--terra-deep);
    border-color: var(--terra);
}
.rl-public .lcard.filtered-out { display: none; }

/* ===================== SP18: console mobile navigation (RLEADS-35) ===================== */

/* ≤900px (iPad portrait 768 can't fit 7 nav links either): the inline link row
   collapses into a hamburger dropdown panel; logout stays on the bar. >900px
   desktop is pixel-identical — .nav-toggle/.nav-scrim are hidden there. */
.rl-console .nav-toggle { display: none; }
.rl-console .nav-scrim { display: none; }

@media (max-width: 900px) {
    .rl-console .nav-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 42px;
        height: 42px;
        flex: none;
        border: 1px solid var(--border);
        border-radius: 9px;
        background: #fff;
        color: var(--navy);
        cursor: pointer;
        padding: 0;
    }
    .rl-console .nav-links { display: none; }
    .rl-console nav.topbar.nav-open .nav-links {
        display: flex;
        flex-direction: column;
        position: absolute;
        top: var(--topbar-h);
        left: 0;
        right: 0;
        background: #fff;
        border-bottom: 1px solid var(--border);
        box-shadow: var(--shadow-lg);
        /* RLEADS-164: the drawer previously let its content overflow freely,
           with no height cap.
           With 18-20 nav items it renders ~981px tall, and because it is
           absolutely positioned inside a `position: sticky` topbar it does not
           scroll with the page — so on any phone (largest are ~932px tall) the
           tail of the menu (設定 / 意見回饋 / 帳號設定 / 登出) was simply
           unreachable. Cap it to the space below the bar (--topbar-h, declared
           with --sidebar-w near the end of this file) and let it scroll itself. `dvh` second so it wins where supported: on mobile browsers
           `vh` measures the tallest viewport (URL bar retracted), which would
           still overflow while the bar is showing. */
        max-height: calc(100vh - var(--topbar-h));
        max-height: calc(100dvh - var(--topbar-h));
        overflow-y: auto;
        overscroll-behavior: contain;
        z-index: 31;
    }
    .rl-console nav.topbar.nav-open .nav-links a {
        min-height: 46px;
        padding: 0 20px;
        border-bottom: 1px solid var(--hairline);
        font-size: 14.5px;
    }
    .rl-console nav.topbar.nav-open .nav-links a.active {
        background: var(--navy-tint);
        border-bottom-color: var(--hairline);
        box-shadow: inset 3px 0 0 var(--navy);
    }
    .rl-console nav.topbar.nav-open .nav-scrim {
        display: block;
        position: fixed;
        inset: var(--topbar-h) 0 0 0;
        background: rgba(23, 25, 35, .28);
        z-index: 30;
    }
    /* RLEADS-93: the topbar account dropdown is desktop-only; on mobile the account
       items live inside the hamburger panel (#console-nav). */
    .rl-console nav.topbar .rl-account { display: none; }
    .rl-console nav.topbar.nav-open .nav-links .rl-nav-account { display: block; }
    .rl-console nav.topbar.nav-open .nav-links .rl-nav-account-divider {
        height: 1px; padding: 0; margin: 6px 0; background: var(--border); border: 0;
    }
    .rl-console nav.topbar.nav-open .nav-links form.rl-nav-logout { margin: 0; }
    .rl-console nav.topbar.nav-open .nav-links form.rl-nav-logout button {
        display: block; width: 100%; text-align: left;
        min-height: 46px; padding: 0 20px; border: 0;
        border-bottom: 1px solid var(--hairline);
        background: transparent; color: var(--ink);
        font: inherit; font-size: 14.5px; cursor: pointer;
    }
}

@media (max-width: 400px) {
    .rl-console nav.topbar .nav-name { display: none; }
}

/* RLEADS-93: the mobile account section is hidden on desktop, where the topbar
   account dropdown is used instead. */
.rl-console nav.topbar .nav-links .rl-nav-account { display: none; }

/* ===================== SP19: console tables → card rows + pagination (RLEADS-36) ===================== */

/* Bootstrap pagination markup (Paginator::useBootstrap) has never had styles —
   fix at all widths. */
.rl-console ul.pagination { display: flex; flex-wrap: wrap; gap: .3rem; list-style: none; padding: 0; margin: .9rem 0 0; }
.rl-console .pagination .page-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    height: 34px;
    padding: 0 .55rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
    color: var(--steel);
    text-decoration: none;
    font-size: .85rem;
    font-variant-numeric: tabular-nums;
}
.rl-console .pagination .page-item.active .page-link { background: var(--navy); border-color: var(--navy); color: #fff; }
.rl-console .pagination .page-item.disabled .page-link { color: var(--border-strong); pointer-events: none; }

/* opt-in card layout: only table.rwd-stack transforms on phones; other tables
   (總覽/分析 two-column mini-tables) are untouched. */
@media (max-width: 720px) {
    .rl-console table.rwd-stack thead { display: none; }
    .rl-console table.rwd-stack,
    .rl-console table.rwd-stack tbody,
    .rl-console table.rwd-stack tr,
    .rl-console table.rwd-stack td { display: block; width: 100%; box-sizing: border-box; }
    .rl-console table.rwd-stack tr {
        position: relative;
        border: 1px solid var(--border);
        border-radius: 10px;
        padding: .55rem .8rem;
        margin-bottom: .65rem;
    }
    .rl-console table.rwd-stack tr:hover td { background: transparent; }
    .rl-console table.rwd-stack td { border-bottom: 0; padding: .16rem 0; }
    .rl-console table.rwd-stack td[data-th]::before {
        content: attr(data-th);
        display: inline-block;
        min-width: 4.6em;
        margin-right: .4rem;
        color: var(--muted);
        font-size: .78rem;
        font-weight: 600;
    }
    .rl-console table.rwd-stack td.num { text-align: left; }
    .rl-console table.rwd-stack td.stack-inline { display: inline-block; width: auto; margin-right: .45rem; }
    /* status pill pinned to the card's top-right, level with column 1 (name) */
    .rl-console table.rwd-stack td.stack-status { position: absolute; top: .55rem; right: .8rem; width: auto; padding: 0; }
    .rl-console table.rwd-stack tr:has(td.stack-status) > td:first-child { padding-right: 6.8em; }
    /* the new-lead terracotta cue moves from a first-cell inset shadow to a card left border */
    .rl-console table.rwd-stack tr.is-new { border-left: 3px solid var(--terra); }
    .rl-console table.rwd-stack tr.is-new td:first-child { box-shadow: none; }
    /* action cells (複製/QR/分享/刪除, 公開頁/編輯) become a wrapping button row */
    .rl-console table.rwd-stack td.actions-cell {
        display: flex;
        flex-wrap: wrap;
        gap: .45rem;
        align-items: center;
        padding-top: .35rem;
    }
    .rl-console table.rwd-stack td.actions-cell form { margin: 0; }
    .rl-console table.rwd-stack .copy-input { width: 100%; min-width: 0; flex: 1 1 100%; }

    /* RLEADS-101: leads inbox — buyer name must be the first visible line on
       phones. The table stacks each <td> in DOM order, but the checkbox cell
       comes first in the DOM, so flip the row to a flex column and float the
       name cell to the top with order:-1. Desktop is untouched (this lives
       inside the ≤720px query, so it stays an ordinary table there). */
    .rl-console table.leads-table tr { display: flex; flex-direction: column; }
    .rl-console table.leads-table tr .lead-name-cell { order: -1; padding-right: 6.8em; }
    .rl-console table.leads-table tr:has(td.stack-status) > td:first-child { padding-right: 0; }
}

/* ===================== SP20: console form & touch polish (RLEADS-37) ===================== */

/* Tablet band (iPad portrait 768): 5-col KPI is too tight, drop to 3.
   Range query so this appended rule can't override the existing ≤720 2-col rule. */
@media (min-width: 721px) and (max-width: 900px) {
    .rl-console .kpis { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 720px) {
    /* short numeric fields (房/廳/衛, 坪數, 樓層) opt into staying side-by-side —
       appended after and more specific than the base .grid2/.grid3, so it wins */
    .rl-console .grid2.keep { grid-template-columns: 1fr 1fr; }
    .rl-console .grid3.keep { grid-template-columns: repeat(3, 1fr); }

    /* filter selects two-per-row (half the .6rem gap = .3rem) */
    .rl-console .filters select { flex: 1 1 calc(50% - .3rem); max-width: 100%; }

    /* let the page head and inline actions wrap instead of squeezing */
    .rl-console .page-head { flex-wrap: wrap; }
    .rl-console .inline-actions { flex-wrap: wrap; }
    .rl-console .copy-input { min-width: 0; width: 100%; }
}

/* Touch devices (mouse desktop untouched): comfortable tap heights */
@media (pointer: coarse) {
    .rl-console button.small,
    .rl-console a.small,
    .rl-console a.btn-line { min-height: 38px; display: inline-flex; align-items: center; }
    .rl-console .nav-links a { min-height: 44px; }
    .rl-console nav.topbar form button { min-height: 40px; }
}

/* ===================== SP21: public mobile polish — gallery carousel & touch (RLEADS-38) ===================== */

@media (max-width: 720px) {
    /* gallery: collage → full-bleed snap carousel (DOM unchanged; display:flex
       lets the existing grid props fall away). Each slide 88% wide so the next
       peeks at the edge; negative margins bleed to the screen edge (.wrap pad 1rem). */
    .rl-public .gallery,
    .rl-public .gallery.row,
    .rl-public .gallery.grid5 {
        display: flex;
        gap: 5px;
        height: 60vw;
        max-height: 300px;
        min-height: 200px;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        margin-left: -1rem;
        margin-right: -1rem;
        padding: 0 1rem;
        border-radius: 0;
        scrollbar-width: none;
    }
    .rl-public .gallery::-webkit-scrollbar { display: none; }
    /* Variant-qualified selectors (.gallery.row/.grid5) to match the SP8 base
       rules' specificity — otherwise .gallery.row .cover's flex:2 wins over a
       bare .gallery .cover and the slides never reach 88%. */
    .rl-public .gallery .cover,
    .rl-public .gallery .thumb-tile,
    .rl-public .gallery.row .cover,
    .rl-public .gallery.row .thumb-tile,
    .rl-public .gallery.grid5 .cover,
    .rl-public .gallery.grid5 .thumb-tile {
        flex: 0 0 88%;
        scroll-snap-align: center;
        border-radius: 12px;
        overflow: hidden;
        position: relative;
    }
    .rl-public .gallery.single .cover,
    .rl-public .gallery.row .cover:only-child { flex-basis: 100%; }

    /* brand-page listing-card photos: fixed 130px → ratio-based (no odd crop when full-width) */
    .rl-public .lcard-ph { height: auto; aspect-ratio: 16 / 9; max-height: 240px; }

    /* listing topbar may wrap on very narrow screens rather than clip */
    .rl-public .ptop { flex-wrap: wrap; row-gap: .5rem; }

    /* shorter map so the POI tabs + list come into view sooner */
    .rl-public .poi-map { height: 220px; }
}

/* Touch devices (mouse desktop untouched): pills grow to comfortable tap heights */
@media (pointer: coarse) {
    .rl-public .tbtn { padding: .45rem .95rem; }
    .rl-public .ptab { padding: .42rem .85rem; }
    .rl-public .sticky-cta .sc-icon,
    .rl-public .sticky-cta .sc-primary { min-height: 46px; }
}

/* long strings (a URL pasted into a listing description, etc.) must not force a
   horizontal scrollbar — CJK line-breaking is unaffected */
.rl-public .card { overflow-wrap: anywhere; }

/* ===================== SP22: brand page desktop/tablet two-column (RLEADS-39) ===================== */

/* Brand page uses .wrap.wide (980px) like the listing page. Below the full-width
   hero + stats, content splits into a main column + a sticky inquiry-form
   sidebar on desktop; collapses to today's single column ≤900px (mobile
   untouched — same DOM order). */
.rl-public .brand-grid { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 1.4rem; align-items: start; }
.rl-public .brand-main { min-width: 0; }
.rl-public .brand-aside { position: sticky; top: 1rem; }
.rl-public .brand-aside .card { margin-bottom: 0; }

/* on desktop the listings grid sits in the ~600px main column — 2 larger cards
   per row read better there than the base 3-col */
@media (min-width: 901px) {
    .rl-public .brand-main .lgrid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 900px) {
    .rl-public .brand-grid { grid-template-columns: 1fr; }
    .rl-public .brand-aside { position: static; }
    .rl-public .brand-aside .card { margin-bottom: .9rem; }
}

/* ===================== SP23: sell page desktop/tablet two-column (RLEADS-40) ===================== */

/* Form is the primary column; a sticky trust panel sits beside it on desktop.
   ≤900px collapses to a centered ~560px form (today's width — forms shouldn't
   stretch) with the trust card stacked below. */
.rl-public .sell-grid { display: grid; grid-template-columns: minmax(0, 540px) 300px; gap: 1.4rem; justify-content: center; align-items: start; }
.rl-public .sell-aside { position: sticky; top: 1rem; }
.rl-public .sell-trust { text-align: center; }
.rl-public .sell-why { text-align: left; border-top: 1px solid var(--paper-alt); margin-top: .9rem; padding-top: .9rem; }
.rl-public .sell-why h3 { font-size: .9rem; color: var(--navy); margin: 0 0 .5rem; }
.rl-public .sell-why ul { list-style: none; padding: 0; margin: 0; }
.rl-public .sell-why li { position: relative; padding: .3rem 0 .3rem 1.5rem; font-size: .9rem; color: var(--fg-muted); }
.rl-public .sell-why li::before { content: "✓"; position: absolute; left: 0; color: var(--sage-900); font-weight: 700; }
.rl-public .sell-help { margin: .9rem 0 .2rem; font-size: .85rem; }
.rl-public .sell-trust .btn { margin-top: .5rem; }

@media (max-width: 900px) {
    .rl-public .sell-grid { display: block; }
    .rl-public .sell-grid > .card { max-width: 560px; margin-left: auto; margin-right: auto; }
    .rl-public .sell-aside { position: static; max-width: 560px; margin: 0 auto; }
}

/* ===================== SP32: internal staff console (RLEADS-56) ===================== */

/* Utilitarian internal tool — the operator-facing subscription console. Not
   scoped to a body class (the staff layout deliberately ships no rl-console
   chrome); inHom tokens keep it on-brand (navy on warm paper, terracotta CTAs). */
.staff-bar { display: flex; align-items: baseline; gap: .75rem; padding: .7rem 1.25rem; background: var(--navy); color: var(--paper); }
.staff-bar strong { font-size: 1rem; }
.staff-bar .muted { color: var(--navy-tint); font-size: .85rem; }
.staff-main { max-width: 1100px; margin: 1.5rem auto; padding: 0 1.25rem; color: var(--ink); }
.staff-main h1 { color: var(--navy); font-size: 1.35rem; margin: 0 0 1rem; }
.staff-table { width: 100%; border-collapse: collapse; background: var(--paper); }
.staff-table th, .staff-table td { text-align: left; padding: .55rem .7rem; border-bottom: 1px solid var(--border); vertical-align: top; font-size: .9rem; }
.staff-table th { color: var(--steel); font-weight: 600; background: var(--paper-alt); }
.staff-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.staff-actions form { display: inline-flex; gap: .3rem; align-items: center; margin: 0; }
.staff-actions .small { padding: .3rem .6rem; font-size: .8rem; border: 1px solid var(--navy); background: var(--navy); color: var(--paper); border-radius: 4px; cursor: pointer; }
.staff-actions .small.danger { border-color: var(--danger); background: var(--paper); color: var(--danger); }
.flash.ok { padding: .6rem .9rem; border-radius: 4px; background: var(--navy-tint); color: var(--navy); border: 1px solid var(--border-strong); }
.small.danger { color: var(--danger); }

/* R14 (RLEADS-60): notification bell + dropdown panel (console topbar only). */
.rl-console nav.topbar .rl-notif { position: relative; margin-left: auto; display: flex; align-items: center; }
/* When the bell owns the auto-margin, the logout form no longer needs it. */
.rl-console nav.topbar .rl-notif + form { margin-left: 8px; }
.rl-bell {
    position: relative; display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0; border: 1px solid transparent; border-radius: 8px;
    background: transparent; color: var(--steel); cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.rl-bell:hover { background: var(--paper-alt); color: var(--navy); }
.rl-badge {
    position: absolute; top: 3px; right: 3px; min-width: 16px; height: 16px; padding: 0 4px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 10px; font-weight: 700; line-height: 1; color: var(--paper);
    background: var(--terra); border: 1.5px solid var(--paper); border-radius: 999px;
}
.rl-notif-panel {
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
    width: 320px; max-width: calc(100vw - 24px); max-height: 70vh; overflow-y: auto;
    background: var(--paper); border: 1px solid var(--border-strong); border-radius: 10px;
    box-shadow: var(--shadow-lg);
}
.rl-notif-panel[hidden] { display: none; }
.rl-notif-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 10px 14px; border-bottom: 1px solid var(--hairline);
    font-size: 13px; font-weight: 700; color: var(--navy);
}
.rl-notif-readall {
    padding: 2px 6px; border: 0; background: transparent; color: var(--steel);
    font-size: 12px; font-weight: 600; cursor: pointer; border-radius: 4px;
}
.rl-notif-readall:hover { color: var(--navy); background: var(--paper-alt); }
.rl-notif-list { padding: 4px 0; }
.rl-notif-empty { margin: 0; padding: 22px 14px; text-align: center; color: var(--muted); font-size: 13px; }
.rl-notif-error {
    display: block; width: 100%; padding: 22px 14px; border: 0; background: none;
    text-align: center; color: var(--terra); font-size: 13px; cursor: pointer; font: inherit;
}
.rl-notif-error:hover { text-decoration: underline; }
.rl-notif-item {
    display: block; padding: 10px 14px; border-bottom: 1px solid var(--hairline);
    color: var(--ink); text-decoration: none; transition: background var(--dur) var(--ease);
}
.rl-notif-item:last-child { border-bottom: 0; }
.rl-notif-item:hover { background: var(--paper-alt); }
.rl-notif-item.is-unread { background: var(--navy-tint); }
.rl-notif-item.is-unread:hover { background: var(--terra-tint); }
.rl-notif-title { display: block; font-size: 13px; font-weight: 600; color: var(--navy); }
.rl-notif-body { display: block; margin-top: 2px; font-size: 12px; color: var(--muted); line-height: 1.4; }

/* RLEADS-93: console topbar account dropdown (replaces the name-as-logout button).
   Mirrors the notification panel's absolutely-positioned dropdown conventions. */
.rl-console nav.topbar .rl-account { position: relative; margin-left: 4px; }
.rl-account-trigger {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 6px 10px; border: 1px solid transparent; border-radius: 8px;
    background: transparent; color: var(--steel); cursor: pointer;
    font: inherit; line-height: 1;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.rl-account-trigger:hover { background: var(--paper-alt); color: var(--navy); }
.rl-account-trigger .nav-name { font-weight: 600; }
.rl-account-caret { flex: none; }
.rl-account-menu {
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
    min-width: 180px; max-width: calc(100vw - 24px);
    background: var(--paper); border: 1px solid var(--border-strong); border-radius: 10px;
    box-shadow: var(--shadow-lg); overflow: hidden;
}
.rl-account-menu[hidden] { display: none; }
.rl-account-menu form { margin: 0; }
.rl-account-item {
    display: block; width: 100%; text-align: left;
    padding: 10px 14px; border: 0; border-bottom: 1px solid var(--hairline);
    background: transparent; color: var(--ink); text-decoration: none;
    font: inherit; font-size: 13px; cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.rl-account-item:last-child { border-bottom: 0; }
.rl-account-item:hover { background: var(--paper-alt); color: var(--navy); }

/* ===================== R8 (RLEADS-62): client reviews 客戶見證 ===================== */
/* Star rating — shared filled/empty glyph across public 客戶見證 + console moderation. */
.rl-public .rv-stars, .rl-console .rv-stars { display: inline-flex; gap: 1px; line-height: 1; white-space: nowrap; }
.rl-public .rv-stars .star, .rl-console .rv-stars .star { font-size: .95rem; }
.rl-public .rv-stars .star.on, .rl-console .rv-stars .star.on { color: var(--terra); }
.rl-public .rv-stars .star.off, .rl-console .rv-stars .star.off { color: var(--hairline); }

/* Public 客戶見證 card — one review per block, stars over quote over attribution. */
.rl-public .card.reviews .review-card { padding: .65rem 0; border-bottom: 1px solid var(--hairline); }
.rl-public .card.reviews .review-card:last-child { border-bottom: 0; }
.rl-public .review-card .rv-body { margin: .4rem 0 .35rem; color: var(--ink); line-height: 1.6; }
.rl-public .review-card .rv-meta { display: flex; flex-wrap: wrap; gap: .5rem; align-items: baseline; margin: 0; font-size: .82rem; }
.rl-public .review-card .rv-name { font-weight: 600; color: var(--navy); }

/* Console moderation row — quoted body sits inline, muted, and can wrap. */
.rl-console .list-row .rv-body { color: var(--muted); font-size: .85rem; flex-basis: 100%; margin: 0; }

/* ===================== R17 (RLEADS-63): marketing landing at / ===================== */
/* Reuses the canonical .hero-band gradient; the landing hero is a centered,
   single-column composition (no avatar) rather than the brand page's
   avatar+CTA flex. CSS-only — no image assets exist for this page. */
.rl-public .landing-hero { text-align: center; padding: 46px 22px 40px; }
.rl-public .landing-hero-inner { position: relative; max-width: 640px; margin: 0 auto; }
.rl-public .landing-hero h1 { color: #fff; font-size: 2rem; line-height: 1.3; margin: 0 0 .7rem; }
.rl-public .landing-sub { color: rgba(255, 255, 255, .92); font-size: 1.02rem; line-height: 1.65; margin: 0 auto 1.4rem; max-width: 520px; }
.rl-public .landing-cta { display: flex; flex-wrap: wrap; gap: .6rem; justify-content: center; }
.rl-public .landing-cta .btn { width: auto; flex: 0 1 auto; min-width: 172px; }
/* Hero email CTA: white outline on the gradient (mirrors .hero-cta .btn.ghost). */
.rl-public .landing-cta .btn.ghost { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, .6); }
.rl-public .landing-cta .btn.ghost:hover { background: rgba(255, 255, 255, .12); }
.rl-public .landing-demo { display: inline-block; margin-top: 1rem; color: #fff; font-size: .92rem; font-weight: 600; opacity: .92; }
.rl-public .landing-demo:hover { opacity: 1; text-decoration: underline; }

/* section rhythm */
.rl-public .landing-section { margin: 2.2rem 0; }
.rl-public .landing-section > h2 { font-size: 1.25rem; }
.rl-public .landing-lead { margin: -.4rem 0 1.1rem; }

/* free-core feature grid — 3-up on desktop, collapses on mobile */
.rl-public .feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.rl-public .card.feature { margin-bottom: 0; }
.rl-public .card.feature h3 { font-size: 1rem; font-weight: 600; color: var(--navy); margin: 0 0 .45rem; }
.rl-public .card.feature p { margin: 0; color: var(--ink); font-size: .92rem; line-height: 1.6; }
.rl-public .card.feature code { background: var(--paper-alt); border: 1px solid var(--border); border-radius: 6px; padding: .05rem .35rem; font-size: .85em; color: var(--navy); }

/* AI plan card — sage-tinted so it reads as the premium add-on */
.rl-public .card.landing-ai { border-left: 4px solid var(--sage-900); background: var(--sage-30); }
.rl-public .landing-ai-list { margin: 0 0 .8rem; padding-left: 1.2rem; }
.rl-public .landing-ai-list li { margin: .3rem 0; color: var(--ink); font-weight: 600; }
.rl-public .landing-ai-more { margin: 0 0 .9rem; color: var(--ink); font-size: .92rem; line-height: 1.6; }
.rl-public .landing-price { margin: .2rem 0 .3rem; }
.rl-public .landing-price .price { font-size: 1.5rem; }
.rl-public .landing-boundary { margin: 0 0 1rem; color: var(--sage-900); font-weight: 600; font-size: .9rem; }
.rl-public .card.landing-ai .btn { width: auto; }

/* FAQ — plain <details>/<summary>, no JS */
.rl-public .faq details { background: #fff; border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-sm); padding: .9rem 1.1rem; margin-bottom: .6rem; }
.rl-public .faq summary { font-weight: 600; color: var(--navy); cursor: pointer; list-style: none; position: relative; padding-right: 1.5rem; }
.rl-public .faq summary::-webkit-details-marker { display: none; }
.rl-public .faq summary::after { content: "＋"; position: absolute; right: 0; top: 0; color: var(--steel); font-weight: 400; }
.rl-public .faq details[open] summary::after { content: "－"; }
.rl-public .faq details p { margin: .6rem 0 0; color: var(--ink); font-size: .92rem; line-height: 1.65; }

@media (max-width: 720px) {
    .rl-public .landing-hero h1 { font-size: 1.55rem; }
    .rl-public .landing-sub { font-size: .96rem; }
    .rl-public .landing-cta .btn { width: 100%; flex: 1 1 100%; }
    .rl-public .feature-grid { grid-template-columns: 1fr; }
}

/* ===================== R7 (RLEADS-65): 行情 comps report ===================== */
.rl-public .report { max-width: 720px; margin: 0 auto; display: grid; gap: 1rem; }
.rl-public .report-hd { text-align: center; }
.rl-public .report-hd h1 { font-size: 1.5rem; color: var(--navy); margin-top: .5rem; }
.rl-public .report-agent { display: inline-flex; align-items: center; gap: .6rem; }
.rl-public .ra-photo { border-radius: 50%; object-fit: cover; }
.rl-public .ra-name { font-weight: 700; color: var(--navy); text-align: left; }
.rl-public .ra-meta { font-size: .8rem; text-align: left; }
.rl-public .sparkline { margin-top: .8rem; }
.rl-public .sparkline svg { width: 100%; height: 64px; display: block; }
.rl-public .sl-axis { display: flex; justify-content: space-between; font-size: .72rem; margin-top: 4px; }
.rl-public .report .empty { text-align: center; }
.rl-public .report-cta { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: .6rem; }
.rl-public .report-cta .btn {
    flex: 1 1 8rem; display: flex; align-items: center; justify-content: center;
    min-height: 46px; border-radius: 10px; font-weight: 600; text-decoration: none;
    background: var(--steel); color: #fff;
}
.rl-public .report-cta .btn.line { background: var(--line-green); }
.rl-public .report-cta .btn.ghost { background: transparent; color: var(--navy); border: 1.5px solid var(--border); }
.rl-public .report-legal { font-size: .75rem; line-height: 1.6; border-top: 1px solid var(--border); padding-top: .8rem; }
.rl-console .report-actions { display: flex; align-items: center; gap: .5rem; margin-top: .6rem; flex-wrap: wrap; }
/* R22 (RLEADS-69): LINE OA guide */
.rl-console .steps { margin: .5rem 0 0 1.2rem; display: grid; gap: .4rem; }
.rl-console .script { border-top: 1px solid var(--border); padding-top: .7rem; margin-top: .7rem; }
.rl-console .script h3 { font-size: .95rem; color: var(--navy); margin-bottom: .4rem; }
.rl-console .script-row { display: flex; align-items: flex-start; gap: .5rem; margin-bottom: .4rem; }
.rl-console .script-row > .muted { min-width: 4.5rem; padding-top: .35rem; font-size: .8rem; }
.rl-console .script-row .copy-input { flex: 1; }

/* ============================================================
   RLEADS-79: 物件照片體驗 — public lightbox + gallery affordances
   ============================================================ */
.rl-public .gallery a.cover,
.rl-public .gallery a.thumb-tile { display: block; color: inherit; }
.rl-public .gallery [data-index] { cursor: zoom-in; }

.rl-public .lightbox {
    position: fixed; inset: 0; z-index: 2000; display: none;
    background: rgba(10, 20, 26, 0.94);
}
.rl-public .lightbox.open { display: block; }
.rl-public .lightbox-track {
    display: flex; height: 100%; overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
}
.rl-public .lightbox-track::-webkit-scrollbar { display: none; }
.rl-public .lightbox-slide {
    flex: 0 0 100%; scroll-snap-align: center;
    display: flex; align-items: center; justify-content: center;
    padding: 24px; box-sizing: border-box;
}
.rl-public .lightbox-slide img {
    max-width: 100%; max-height: 100%; object-fit: contain;
}
.rl-public .lightbox-counter {
    position: absolute; top: 16px; left: 50%; transform: translateX(-50%);
    color: #fff; font-size: 14px; letter-spacing: .05em;
}
.rl-public .lightbox-close {
    position: absolute; top: 8px; right: 12px; width: 44px; height: 44px;
    border: 0; background: none; color: #fff; font-size: 32px; line-height: 1;
    cursor: pointer;
}
body.rl-public.lightbox-open { overflow: hidden; }

/* RLEADS-79: console photo management (drag reorder + set cover) */
.rl-console .photo-item { position: relative; }
.rl-console .drag-handle,
.rl-console .js-cover { display: none; }
.rl-console .photo-grid.js-drag .drag-handle {
    display: inline-flex; cursor: grab; touch-action: none; user-select: none;
    padding: 2px 8px;
}
.rl-console .photo-grid.js-drag .js-cover { display: inline-block; }
/* RLEADS-79 a11y fix: 上移/下移 stay visible alongside the drag handle even once
   JS boots — the drag handle is aria-hidden and has no keyboard handler, so it
   cannot be the only reorder control or keyboard/screen-reader users lose the
   ability to reorder entirely. */

/* ============================================================
   RLEADS-90 Batch B: listings form sticky save bar
   ============================================================
   Mirrors .rl-public .sticky-cta's exact mechanic (RLEADS-31/RLEADS-84):
   fixed to the viewport bottom, translucent white, top border + shadow,
   safe-area padding, gated behind a body.has-* marker class so only the
   listings create/edit page pays the .wrap bottom-padding cost. z-index:20
   — matches .sticky-cta's own z-index and stays safely BELOW the console
   topbar's z-index:30 (and its 31/30 mobile-nav-open dropdown/scrim) so
   they never fight; the only other z-index in the 20-30 band is the RLEADS-168
   desktop sidebar (29) at the very bottom of this file — which sits between
   this bar and the topbar, and never overlaps this bar anyway because the same
   ticket offsets `left` by --sidebar-w at ≥901px.
   Unlike .sticky-cta (mobile-only, gated by a max-width media query), this
   bar shows at ALL viewport widths — the ~20-field form's save button is a
   long scroll away on desktop too, not just mobile. */
.rl-console .sticky-save-bar { display: none; }

body.has-sticky-save .sticky-save-bar {
    display: flex;
    justify-content: flex-end;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(255, 255, 255, .97);
    border-top: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
    padding: .7rem 1.35rem calc(.7rem + env(safe-area-inset-bottom));
    z-index: 20;
}

/* Room for the fixed bar so it never covers the form's last field. */
body.has-sticky-save .wrap { padding-bottom: 5.5rem; }
.rl-console .photo-item.dragging { opacity: .6; outline: 2px dashed #E88D67; }

/* ============================================================
   RLEADS-81 Batch E: 成交實績 drag-reorder (mirrors RLEADS-79's photo-grid)
   ============================================================ */
.rl-console .deals-list .deal-item {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}
.rl-console .deals-list .deal-info { flex: 1 1 auto; }
.rl-console .deals-list .deal-reorder-btns { display: flex; gap: .3rem; flex: none; }
.rl-console .deals-list .drag-handle {
    display: none;
    cursor: grab; touch-action: none; user-select: none;
    padding: 2px 8px; flex: none;
}
.rl-console .deals-list.js-drag .drag-handle { display: inline-flex; }
.rl-console .deals-list .deal-item.dragging { opacity: .6; outline: 2px dashed #E88D67; }

/* ============================================================
   RLEADS-90 Batch C: section anchor nav (profile-edit, settings)
   ============================================================
   A small in-page table-of-contents strip, one pill per card — deliberately
   NOT styled like .nav-links (the primary top nav) so it doesn't read as a
   second global nav bar. Max 10 short labels (settings/index.blade.php);
   rather than reproducing .nav-links' overflow-x + scroll-fade-gradient
   trick (built because that nav's items must all stay individually
   reachable behind a scrollbar-hidden strip), this wraps to multiple lines
   instead — every anchor stays visible with no hidden/scrolled-off content
   to cue in the first place, which is simpler and more robust for a
   secondary, lower-stakes nav aid. Plain #fragment links, no JS, no
   scroll-spy active state (explicitly out of scope). */
.rl-console .section-anchors {
    display: flex;
    flex-wrap: wrap;
    gap: .45rem .5rem;
    margin: 0 0 1rem;
}
.rl-console .section-anchors a {
    display: inline-flex;
    align-items: center;
    padding: .3rem .75rem;
    border-radius: 999px;
    background: var(--gray-50);
    color: var(--steel);
    font-size: .78rem;
    font-weight: 600;
    text-decoration: none;
    border: 1px solid var(--border);
    white-space: nowrap;
}
.rl-console .section-anchors a:hover {
    background: var(--navy-tint);
    color: var(--navy);
    border-color: var(--navy-tint);
}

/* ============================================================
   RLEADS-96: console RWD fixes
   ============================================================
   A bundle of layout/responsive fixes to console surfaces. All rules
   reference existing :root tokens only (no new hex literals). Items are
   numbered to match the ticket. */

/* Item 1: checkbox/radio rows. The global `.rl-console input { width:100% }`
   base rule (used for text/number/select fields) also caught bare
   checkboxes/radios, blowing them to full width so the input floated on its
   own line and the label text dropped below it. `.check-row` is a <label>
   that wraps its input inline — flex keeps the box and its text on one line,
   and forcing the input back to width:auto (and margin:0, dropping the base
   margin-bottom) restores a normal checkbox/radio row. Wrapping the input in
   the label also associates them for a11y with no `for=`/`id=` plumbing. */
.rl-console .check-row {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-weight: 400;
    color: var(--ink);
    margin-bottom: .5rem;
}
.rl-console .check-row input { width: auto; margin: 0; flex: none; }

/* (Item 2 restyled the RLEADS-90 vertical hairline as a full-width rule inside
   the ≤900px hamburger panel, where a 1px×20px stub rendered as a stray float
   on its own row. RLEADS-168 replaced the hairline with labelled `.nav-group`
   headings and carried that fix over to them — the drawer band near the end of
   this file. RLEADS-171 item 2 deleted the leftover rule.) */

/* Item 4: the per-photo action buttons (設為封面/上移/下移/刪除) overflowed and
   were clipped by the tile at narrower widths. Let the row wrap and shrink
   the buttons so they always fit within the photo tile. */
.rl-console .photo-actions {
    flex-wrap: wrap;
    row-gap: .3rem;
}
.rl-console .photo-actions button,
.rl-console .photo-actions .small {
    padding: .25rem .4rem;
    font-size: .74rem;
}

/* Item 5: on phones the sticky save bar (taller than its desktop height once
   the touch tap-target min-heights kick in) covered the form's last field.
   Give the .wrap extra bottom room in the phone band to clear it. */
@media (max-width: 720px) {
    body.has-sticky-save .wrap { padding-bottom: 7rem; }
}

/* Item 6: listings-index rows with no cover photo left the title column
   starting at a different x than rows WITH a thumbnail. Render a same-size
   grey placeholder box so every row's title aligns. Inherits .row-thumb's
   dimensions/radius; just supplies the muted fill + border. */
.rl-console .row-thumb-empty {
    background: var(--paper-alt);
    border: 1px solid var(--border);
}

/* Item 7: in-page #anchor jumps (the settings/profile section-anchor chips)
   landed under the position:sticky topbar (54px + z-index:30). Give the
   anchor-target cards/headings a scroll-margin-top a little taller than the
   topbar so they land clearly below it. */
.rl-console .card[id],
.rl-console h2[id] {
    scroll-margin-top: 5rem;
}

/* RLEADS-100: version footer (app name + deploy short-SHA). Small, muted and
   centered; shared by the console and auth layouts. The padding keeps it clear
   of content down to 390px without crowding. */
.rl-footer {
    text-align: center;
    color: var(--muted);
    font-size: .72rem;
    padding: 1.6rem 1rem .8rem;
    letter-spacing: .02em;
}

/* RLEADS-102: calendar 今日 highlight.
   (The RLEADS-103 `.task-filters .chip` rules and the RLEADS-102 `.cal-tabs`
   view-switcher rules that used to sit here were superseded by the RLEADS-170
   `.filter-chips` / `.view-tabs` primitives at the end of this file, and were
   deleted rather than left orphaned — nothing referenced them once the five
   views moved. This highlight shipped in the same block but is unrelated to
   tabs, so it stays where it is.) */
.rl-console .week-grid .card.is-today {
    border-color: var(--navy);
    box-shadow: 0 0 0 1px var(--navy);
    background: var(--navy-tint);
}

/* RLEADS-127: shared public 法遵 footer (service-terms + privacy links + copyright).
   Scoped to .rl-public so the console (.rl-console) and auth (.rl-auth) surfaces are
   untouched; the digital card never extends public.layout so it never gets this.
   Tokens only — no new colours. */
.rl-public .rl-public-footer {
    margin-top: 48px;
    padding: 28px 20px 40px;
    border-top: 1px solid var(--paper-alt);
    text-align: center;
    color: var(--muted);
    font-size: 14px;
    line-height: 1.6;
}
.rl-public .rl-public-footer__links { margin-bottom: 6px; }
.rl-public .rl-public-footer__links a { color: var(--navy); margin: 0 4px; }
.rl-public .rl-public-footer__links span { color: var(--muted); }
.rl-public .rl-public-footer__copy { margin: 0; }
/* On listing pages the fixed .sticky-cta bar (≤720px) pins to the viewport bottom;
   the .wrap padding-bottom clears content INSIDE .wrap but the footer is its sibling,
   so it needs its own clearance to not render behind the bar. */
body.has-sticky-cta .rl-public-footer { padding-bottom: calc(40px + 5.5rem); }

/* RLEADS-127: legal document pages (/terms, /privacy) — comfortable reading measure
   and list spacing, reusing the .report/.card shells. */
.rl-public .legal-doc { max-width: 760px; }
.rl-public .legal-doc .card h2 { margin-top: 0; }
.rl-public .legal-doc .card ul { margin: 8px 0 0; padding-left: 20px; }
.rl-public .legal-doc .card li { margin-bottom: 6px; }

/* ===================== RLEADS-156: public UX wave 1 ===================== */
/* SP30's slot picker (RLEADS-54) shipped .slot-day/.slot-chip markup on the
   brand, listing, and appointment-reschedule pages but never got styles — it
   rendered as a raw, thousands-of-pixels radio list. Chips below follow the
   filter-chip pill anatomy; the radio stays visually hidden BUT focusable
   (same rationale as .ptab-radio / the RLEADS-156 .type-toggle fix). */

.rl-public fieldset {
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: .75rem .9rem .9rem;
    margin: 0 0 1rem;
}
.rl-public legend {
    font-size: .85rem;
    color: var(--fg-muted);
    font-weight: 600;
    padding: 0 .35rem;
}

/* The .slot-day day groups this section introduced were replaced by RLEADS-159's
   day tabs (.slot-days / .sday / .slot-part, further down); the chip anatomy
   below is still the picker's building block. */
.rl-public .slot-chip { display: inline-block; margin: 0; cursor: pointer; }
.rl-public .slot-chip input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
.rl-public .slot-chip span {
    display: inline-block;
    padding: .35rem .8rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: #fff;
    color: var(--steel);
    font-size: .85rem;
    font-variant-numeric: tabular-nums;
}
.rl-public .slot-chip span:hover { border-color: var(--navy); color: var(--navy); }
.rl-public .slot-chip input:checked + span {
    background: var(--navy);
    border-color: var(--navy);
    color: #fff;
    font-weight: 600;
}
.rl-public .slot-chip input:focus-visible + span {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
}

/* 其他時間 fold (free-text time fallback) summary anatomy. RLEADS-159 replaced
   the slot picker's own 更多日期 fold with day tabs, so only .alt-time remains. */
.rl-public .alt-time summary {
    cursor: pointer;
    color: var(--steel);
    font-size: .85rem;
    font-weight: 600;
    padding: .3rem 0;
}
.rl-public .alt-time summary:hover { color: var(--navy); }
.rl-public .alt-time { margin: 0 0 .8rem; }
.rl-public .alt-time[open] summary { margin-bottom: .4rem; }

/* Zero-photo listing placeholder — neutral navy-tint gradient, same shape the
   gallery would occupy, so the page never opens on a bare pill row. */
.rl-public .gallery-empty {
    height: 200px;
    border-radius: 14px;
    background: linear-gradient(135deg, var(--navy-tint), var(--paper-alt));
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--navy);
    font-size: .85rem;
    font-weight: 600;
    margin-bottom: 1rem;
}

/* Brand-page zero-listing empty state + filter zero-result message. */
.rl-public .listings-empty { text-align: center; }
.rl-public .listings-empty .btn { display: inline-block; margin-top: .6rem; width: auto; padding: .55rem 1.4rem; }
.rl-public .filter-empty { color: var(--muted); font-size: .88rem; margin: .6rem 0 0; }

/* ===================== RLEADS-157: public UX wave 2 ===================== */
/* Sell-form optional property group — same summary anatomy as
   .alt-time (wave 1). */
.rl-public .sell-optional { margin: 0 0 1rem; }
.rl-public .sell-optional summary {
    cursor: pointer;
    color: var(--steel);
    font-size: .9rem;
    font-weight: 600;
    padding: .45rem 0;
}
.rl-public .sell-optional summary:hover { color: var(--navy); }
.rl-public .sell-optional[open] summary { margin-bottom: .5rem; }

/* ===================== RLEADS-158: public UX wave 3 ===================== */
/* Brand-grid scale: skip layout/paint for off-screen cards. The intrinsic
   size approximates a rendered card (130px photo + body) so scrollbar math
   stays stable; images inside already carry loading="lazy". */
.rl-public .lcard {
    content-visibility: auto;
    contain-intrinsic-size: 0 260px;
}

/* Hero badge hierarchy: the brokerage badge is the §21 trust anchor — solid
   fill so it leads the row; license/service-area badges keep the glass look. */
.rl-public .hb-primary {
    background: #fff;
    border-color: #fff;
    color: var(--navy);
}

/* Stats strip rhythm: equal columns with hairline separators instead of
   free-floating space-around numbers. */
.rl-public .stats .stat { flex: 1; }
.rl-public .stat + .stat { border-left: 1px solid var(--hairline); }

/* ===================== RLEADS-159 (SP31): booking picker v2 ===================== */
/* Day tabs, zero JS. Each .sday is `display: contents`, so its radio/tab/panel
   become direct flex children of .slot-days while remaining DOM siblings — which
   is what lets the `:not(:checked) ~ .sday-panel` rule below hide the other days.
   flex `order` then lays every tab out on the row above every panel, and the
   panels' flex-basis:100% forces each onto its own line. The radio group also
   gives day switching real keyboard support for free (arrow keys), which is why
   the radios are visually hidden but never display:none (same rationale as
   .ptab-radio and the RLEADS-156 .type-toggle fix). */

.rl-public .slot-days { display: flex; flex-wrap: wrap; gap: .4rem; }
.rl-public .sday { display: contents; }
.rl-public .sday-radio {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
.rl-public .sday-tab {
    order: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .05rem;
    margin: 0;
    padding: .3rem .6rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #fff;
    color: var(--steel);
    cursor: pointer;
    line-height: 1.2;
}
.rl-public .sday-tab b { font-size: .85rem; font-variant-numeric: tabular-nums; }
.rl-public .sday-tab small { font-size: .68rem; opacity: .75; }
.rl-public .sday-tab:hover { border-color: var(--navy); color: var(--navy); }
.rl-public .sday-radio:checked + .sday-tab {
    background: var(--navy);
    border-color: var(--navy);
    color: #fff;
}
.rl-public .sday-radio:focus-visible + .sday-tab {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
}
/* Switching days hides the panel holding the current pick, so mark the day that
   carries it. :has() is baseline; where unsupported the dot is simply absent. */
.rl-public .sday:has(input[name="slot_at"]:checked) .sday-tab::after {
    content: "・";
    position: absolute;
    margin-top: -.15rem;
}
.rl-public .sday-panel {
    order: 2;
    flex-basis: 100%;
    width: 100%;
    margin-top: .5rem;
}
.rl-public .sday-radio:not(:checked) ~ .sday-panel { display: none; }

/* 上午 / 下午 / 晚上 segments within the open day. */
.rl-public .slot-part {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    margin: 0 0 .5rem;
}
.rl-public .slot-part:last-child { margin-bottom: 0; }
.rl-public .slot-part-label {
    flex-basis: 100%;
    font-size: .78rem;
    font-weight: 600;
    color: var(--navy);
}
/* Slots already taken: visible but unpickable, so a sparse day reads as
   "popular" rather than "broken". Disabled inputs leave the tab order by
   design — the visually-hidden （已預約） text carries the state to AT. */
.rl-public .slot-chip.is-booked { cursor: not-allowed; }
.rl-public .slot-chip.is-booked span {
    background: var(--paper, #f4f2ee);
    border-style: dashed;
    color: var(--muted, #9aa3ad);
    text-decoration: line-through;
}
.rl-public .slot-chip.is-booked span:hover {
    border-color: var(--border);
    color: var(--muted, #9aa3ad);
}

.rl-public .booking-mode-hint {
    margin: 0 0 .6rem;
    font-size: .8rem;
    color: var(--steel);
}

/* Visually-hidden utility (state text for AT only). */
.rl-public .vh {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ===================== RLEADS-160 (SP32): booking calendar ===================== */
/* The SP31 tab strip above is the no-JS baseline; slot-calendar.js adds
   .is-calendar and sets explicit grid-column/grid-row on the SAME labels, so
   everything below is a layout swap — no second set of controls, and every
   SP31 `:checked`/`~` rule keeps matching. */

.rl-public .slot-days.is-calendar {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: .3rem;
}
/* Column headers: decoration only (the radios carry a full date aria-label). */
.rl-public .cal-wd {
    align-self: center;
    text-align: center;
    font-size: .7rem;
    font-weight: 600;
    color: var(--steel);
}
/* Dates inside the horizon with nothing open, and the pre-today padding of the
   first week — both inert, present so the weekday columns stay aligned. */
.rl-public .cal-off,
.rl-public .cal-blank {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.3rem;
    font-size: .8rem;
    font-variant-numeric: tabular-nums;
    color: var(--muted, #9aa3ad);
    opacity: .45;
}
.rl-public .slot-days.is-calendar .sday-tab {
    justify-content: center;
    min-height: 2.3rem;
    padding: .25rem .1rem;
    border-radius: 8px;
}
.rl-public .slot-days.is-calendar .sday-tab b { font-size: .82rem; }
/* The weekday is the column now. */
.rl-public .slot-days.is-calendar .sday-tab small { display: none; }
.rl-public .slot-days.is-calendar .sday-tab.is-today { border-color: var(--terracotta, #E88D67); }
.rl-public .slot-days.is-calendar .sday-panel {
    grid-column: 1 / -1;
    margin-top: .7rem;
    padding-top: .7rem;
    border-top: 1px solid var(--border);
}

/* RLEADS-161: long-horizon fold. A display:none grid item creates no row, so
   hiding the weeks past the cap collapses them with no leftover gaps and
   expanding is a single class removal (see slot-calendar.js). */
.rl-public .slot-days.is-clipped .is-beyond { display: none; }
.rl-public .cal-more {
    margin-top: .5rem;
    padding: .45rem;
    border: 1px dashed var(--border);
    border-radius: 8px;
    background: transparent;
    color: var(--navy);
    font-size: .8rem;
    font-family: inherit;
    cursor: pointer;
}
.rl-public .cal-more:hover { border-color: var(--navy); border-style: solid; }
.rl-public .cal-more:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

/* RLEADS-162: month caption above each month's weeks. Bare day numbers across a
   multi-month horizon gave the buyer no anchor at all. */
.rl-public .cal-month {
    margin: .35rem 0 .1rem;
    font-size: .78rem;
    font-weight: 700;
    color: var(--navy);
    letter-spacing: .02em;
}
.rl-public .slot-days.is-calendar .cal-month:first-of-type { margin-top: 0; }

/* ============================================================
   RLEADS-163: console button hygiene.
   Defensive base for any future UNCLASSED <button> — i.e. one with
   no class attribute at all — so it lands on the button.small look
   instead of browser-default. Buttons that carry a class (even an
   empty or non-button one) are out of its reach by design; the
   ConsoleButtonsTest guard is what keeps those honest.
   ============================================================ */
.rl-console button:not([class]) {
    padding: .3rem .75rem;
    font-size: .82rem;
    font-family: var(--sans);
    border-radius: 7px;
    border: 1px solid var(--steel);
    background: #fff;
    color: var(--steel);
    cursor: pointer;
    white-space: nowrap;
}
.rl-console button:not([class]):hover { background: var(--paper-alt); }

/* ============================================================
   RLEADS-165: copy-field — the readonly-URL + copy-button pair
   as one flex unit, previously hand-rolled at every call site.
   ============================================================ */
.rl-console .copy-field { display: inline-flex; gap: .4rem; align-items: center; flex-wrap: wrap; }

/* The two contexts that used to stretch the bare .copy-input now have to
   stretch the wrapper instead — the input's own flex/width rules still apply,
   one level down, so the rendered widths stay what they were. */
.rl-console .script-row .copy-field { flex: 1; }
@media (max-width: 720px) {
    .rl-console table.rwd-stack .copy-field { flex: 1 1 100%; }
}

/* RLEADS-165: page-head actions cluster (was an inline style on listings). */
.rl-console .page-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* RLEADS-165: empty-state. The standalone <p class="muted empty-state"> keeps the
   paragraph metrics it already had, so no rule is needed for it. The two table
   empty rows, however, used to be bare text in a .muted <td>; the component wraps
   them in a <p>, whose UA margin would push the row open. Zero it there only. */
.rl-console td > .empty-state { margin: 0; }

/* ============================================================
   RLEADS-165: .check-row sweep
   ============================================================
   The base `.rl-console input { width: 100% }` (top of this file) also catches
   bare checkboxes/radios, blowing them to the full row width and dropping the
   label text below. `.check-row` (RLEADS-96 item 1) is the remedy, and every
   console checkbox/radio now goes through it. Three additions finished the
   sweep; each replaces markup that had been working around the base rule by
   hand. Pinned by ConsoleComponentsTest. */

/* Long labels. `.check-row` uses `align-items: center`, which centres the box
   against the WHOLE label — fine on one line, but a consent sentence that wraps
   to 2-3 lines leaves the box floating at the paragraph's midpoint. Kept as an
   opt-in modifier rather than flipping the base rule, because centre and
   flex-start are NOT interchangeable on single-line rows either.

   The arithmetic, corrected (RLEADS-165 review): `.check-row` is always a
   <label>, and `.rl-console label` (rleads.css:139) sets font-size: .85rem —
   `.rl-console .check-row` declares no font-size of its own, and nothing else in
   this file sets one on a label. There is no `html` rule here either, so 1rem is
   the UA's 16px. The line box is therefore .85rem x 1.65 = 13.6 x 1.65 = 22.4px,
   NOT the 15px x 1.65 = 24.75px this comment used to claim (15px is
   `.rl-console`'s base, which the label rule overrides). Against a ~13-16px UA
   checkbox that is still a ~3-5px gap, so the conclusion is unchanged: plain
   flex-start would lift every existing box off its optical centre. The nudge
   restores that half-leading against the first line only.

   Kept as .3rem rather than the tidier-looking calc((1.65em - 1em) / 2): `em` on
   this declaration resolves against the INPUT's font-size, and `.rl-console input`
   (rleads.css:118) sets font-family but no font-size, so a checkbox keeps the UA's
   own form-control size (~13.3px in Chrome) rather than inheriting the label's
   13.6px. The calc would therefore track the browser's checkbox default, not the
   label — the opposite of what it looks like it does. */
.rl-console .check-row.is-multiline { align-items: flex-start; }
.rl-console .check-row.is-multiline input { margin-top: .3rem; }

/* Horizontal variant. The 版面主題 swatch picker lays its radios out as a
   wrapping row rather than a vertical stack, and the group owns the spacing
   (this rule is what two inline `style=` attributes on the editor became). */
.rl-console .check-row-group { display: flex; flex-wrap: wrap; gap: .4rem 1.2rem; margin-bottom: .85rem; }
.rl-console .check-row-group .check-row { margin-bottom: 0; }

/* The one kind of checkbox that cannot be a `.check-row`: the 名單 table's
   select-all and per-row boxes sit bare in <th>/<td> carrying an aria-label and
   no visible text, so wrapping them in a text-less label would only add the
   component's flex row and bottom margin inside a table cell. They take the
   reset directly, via a purely presentational class — deliberately NOT the
   existing `.lead-select-box`, which is the bulk-action JS's handle on the
   *row* boxes only (putting the select-all box in that set would double-count
   「已選 N 筆」). ConsoleComponentsTest exempts exactly this class and asserts
   this rule, so the exemption cannot become a hole. */
.rl-console .table-select-box { width: auto; margin: 0; }

/* RLEADS-165: `.list-row` trailing action CLUSTER. The existing
   `.list-row form { margin: 0 0 0 auto }` pushes a row's single trailing form
   to the edge; 跟進序列 rows end in a link + a form instead, so the cluster
   takes the auto margin and the form inside it gives its own back — otherwise
   the toggle button would drift away from 編輯 within the cluster. */
.rl-console .list-row > .inline-actions { margin-left: auto; align-items: center; }
.rl-console .list-row > .inline-actions form { margin: 0; }

/* =====================================================================
   RLEADS-165: classes used by console views that had no rule at all
   =====================================================================
   Every literal class token in resources/views/app + resources/views/components
   was diffed against the selectors in this file. 33 had no rule anywhere. Most
   of those are correct as-is and are declared, one by one, with their reason in
   ConsoleClassCoverageTest::STYLE_FREE — JS handles (`.studio-share-*`,
   `.seq-remove`, `.move-form`, `.js-move-*`, `.lead-select-box`,
   `.media-picker`), semantic hooks on an element a sibling class already styles
   (`.ai-summary-card`/`.studio-post` ride `.card`; `.checklist-progress`/
   `.checklist-next` ride `.muted`), fieldsets that spell out their border inline
   (`.ai-bio-fields`, `.ai-call-fields`, `.seq-step`), and `.frow`, which
   RLEADS-163 emptied on purpose. Adding rules for those would be noise.

   The ten below are the ones that actually rendered unstyled. */

/* 媒體庫 and the studio's media picker. Same idiom as SP8's `.photo-grid` /
   `.photo-item` (auto-fill minmax track, bordered tile, cover-fit image), on a
   smaller track: the library holds up to MAX_PER_AGENT assets, not one
   listing's 12 photos. `.media-tile` is a <figure> on 媒體庫 (hence margin: 0 —
   the UA default is 1em 40px) and a <label> in the picker. */
.rl-console .media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: .75rem;
    margin-bottom: 1rem;
}
.rl-console .media-tile {
    position: relative;
    margin: 0;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    background: var(--paper-alt);
}
.rl-console .media-tile img { width: 100%; height: 96px; object-fit: cover; display: block; }
.rl-console .media-tile form { margin: 0; }
.rl-console .media-tile button { width: 100%; border-radius: 0; }

/* The picker's tile is a <label> that also carries `.check-row` — needed, since
   the base `.rl-console input { width: 100% }` would otherwise stretch its
   checkbox across the tile. But `.check-row`'s `display: flex` sits that box
   BESIDE the thumbnail and squeezes it inside a grid track, so `.media-pick`
   takes the box out of flow and floats it over the image instead. The checked
   outline is the only cue telling the agent which images the draft will use.
   (`:has()` is already the file's idiom — see `tr:has(td.stack-status)`.) */
.rl-console .media-picker-grid { grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: .5rem; }
.rl-console .media-picker-grid .media-tile img { height: 72px; }
.rl-console .media-pick {
    display: block;
    position: relative;
    cursor: pointer;
    margin-bottom: 0;
}
.rl-console .media-pick input[type="checkbox"] { position: absolute; top: 6px; left: 6px; }
.rl-console .media-pick:has(input:checked) { outline: 2px solid var(--navy); outline-offset: 1px; }

/* 內容工作室 → 歷史草稿: a bare <ul> inside a card, so it drew disc bullets at the
   UA's 40px indent. Reuses `ul.checklist`'s list reset, `.list-row`'s hairline
   separation and `.ai-usage-head`'s space-between head row. */
.rl-console .studio-history { list-style: none; padding: 0; margin: 0; }
.rl-console .studio-history > li { padding: .7rem 0; border-bottom: 1px solid var(--hairline); }
.rl-console .studio-history > li:last-child { border-bottom: 0; }
.rl-console .studio-history-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .5rem;
    margin-bottom: .25rem;
}
.rl-console .studio-history-body {
    margin: 0 0 .4rem;
    color: var(--fg-muted);
    font-size: .88rem;
    white-space: pre-wrap;
}
/* 複製/分享/發佈 row — both on a history entry and in the generated-draft
   <template>, where four controls otherwise butt together with no gap. Same
   shape as `table.rwd-stack td.actions-cell`. */
.rl-console .studio-post-actions { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center; }
.rl-console .studio-post-actions form { margin: 0; }

/* 選擇平台 is the only console <fieldset> that does not spell out a border
   inline, so it drew the browser-default inset ridge. Same shape the two
   profile-editor fieldsets carry inline, expressed once with tokens. */
.rl-console .studio-platforms {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: .85rem 1rem;
    margin-bottom: .85rem;
}
.rl-console .studio-platforms legend { font-size: .9rem; padding: 0 .35rem; }

/* 選擇圖片（可選） names the picker as a whole, not one input, so it cannot be a
   <label> — without this it rendered as plain body text among styled labels.
   Deliberately identical to `.rl-console label` (rleads.css:139). */
.rl-console .field-label {
    display: block;
    font-size: .85rem;
    font-weight: 600;
    color: var(--fg-muted);
    margin-bottom: .3rem;
}

/* ============================================================
   RLEADS-168 (Console UX Wave 2, PR 1): desktop left sidebar
   ============================================================
   The top nav carried 17-19 flat items in one row: at 1440px 意見回饋 was
   already clipped and overlapped the bell/account cluster, and between
   901-1200px items disappeared into an invisible horizontal scroller whose
   only cue was the RLEADS-96 gradient.

   THE ONE ARCHITECTURAL RULE: the SAME `#console-nav` element becomes the
   sidebar at ≥901px and stays the hamburger drawer at ≤900px. There is no DOM
   restructuring, so `rlNavToggle` (console.js, called from two inline onclick=
   attributes in app/layout.blade.php), the RLEADS-164 drawer height cap and
   every ≤900px rule above keep working with zero changes. Everything here is
   scoped inside `@media (min-width: 901px)` and therefore cannot reach them.

   The topbar itself is unchanged — brand, bell, account dropdown, hamburger and
   scrim all stay put. `.rl-notif` already carries `margin-left: auto`
   (rleads.css:1851), so pulling `.nav-links` out of the topbar's flex flow
   leaves the right-hand cluster exactly where it was.

   The rail's `top` is the topbar height, which the ≤900px drawer `top`, the
   drawer `max-height` and the scrim `inset` also need. RLEADS-168 hardcoded
   54px in all four rather than make an append-only section rewrite three
   shipped rules in two other ticket sections; RLEADS-171 item 3 did that
   rewrite and they now share --topbar-h below. */

/* Console layout tokens. Both are consumed by rules in EARLIER ticket sections
   of this file — custom properties resolve at computed-value time, not in
   source order, so declaring them here is fine. */
:root {
    /* Wide enough for 內容工作室 / 匯入/匯出 (the longest labels) at 13.5px
       without wrapping, narrow enough to leave ≥1080px of content at 1300px. */
    --sidebar-w: 216px;
    /* `.rl-console nav.topbar`'s CONTENT height. The bar also has a 1px
       `border-bottom` and this stylesheet has no global border-box, so it
       OCCUPIES 55px. Every consumer below is an offset (`top` / `inset`) or a
       viewport subtraction, never a reserved width, so each deliberately tucks
       its top edge 1px under that border — which the topbar (z-index 30) paints
       over anyway. That is the shipped behaviour and the token preserves it
       exactly; unlike --sidebar-w there is no off-by-one to correct here. */
    --topbar-h: 54px;
}

@media (min-width: 901px) {
    .rl-console nav.topbar .nav-links {
        position: fixed;
        top: var(--topbar-h);
        left: 0;
        bottom: 0;
        width: var(--sidebar-w);
        /* This stylesheet has no global `* { box-sizing: border-box }`, so the
           1px border-right below would otherwise make the rail 217px wide while
           `.wrap`'s padding-left, `.rl-footer`'s margin-left and the sticky save
           bar's `left` all offset by exactly --sidebar-w (216px) — leaving the
           save bar tucked 1px under the border and the content gutters 1px
           unequal. Border-box makes the rendered width match the token. */
        box-sizing: border-box;
        flex-direction: column;
        align-items: stretch;
        /* The RLEADS-90/96 horizontal scroll cue is a top-row affordance; a
           vertical rail neither overflows sideways nor needs the gradients.
           The declarations themselves stay in the base rule for ≤900px. */
        overflow-x: hidden;
        overflow-y: auto;
        overscroll-behavior: contain;
        scrollbar-width: none;
        /* Drop only the four gradients — the base rule's `background-color`
           still paints the rail opaque, so nothing scrolls through behind it
           and this section needs no hard-coded color of its own. */
        background-image: none;
        border-right: 1px solid var(--border);
        padding: .5rem 0 1.2rem;
        /* Below the topbar (30) and its nav-open dropdown/scrim (31/30), above
           the listings sticky save bar (20) — which is offset below anyway. */
        z-index: 29;
    }
    .rl-console nav.topbar .nav-links a {
        display: flex;
        align-items: center;
        min-height: 38px;
        padding: .35rem 18px;
        border-bottom: 0;
        line-height: 1.35;
    }
    /* Same active mechanic the ≤900px drawer already uses (rleads.css:1578):
       an inset left bar, so nothing shifts by a border width on activation. */
    .rl-console nav.topbar .nav-links a.active {
        border-bottom-color: transparent;
        background: var(--navy-tint);
        box-shadow: inset 3px 0 0 var(--navy);
    }
    .rl-console nav.topbar .nav-links a:hover { background: var(--paper-alt); }

    /* --- make room for the rail -------------------------------------------
       `.wrap` is `max-width: 1080px; margin: 0 auto` (content-box — this
       stylesheet has NO global `* { box-sizing: border-box }`, only inputs).
       A `margin-left` would kill the auto-centring and pin the content against
       the rail with a huge empty right margin at 1920px. Adding the rail width
       to `padding-left` instead keeps BOTH margins auto, and the arithmetic
       works out to exact centring inside the remaining region at every width:
         ours   = (W - C - 259.2)/2 + 237.6  = (W - C)/2 + 108
         target = 216 + (W - 216 - C)/2      = (W - C)/2 + 108
       (C = content width, 1.35rem = 21.6px). Verified at 1000/1440/1920. */
    .rl-console .wrap { padding-left: calc(1.35rem + var(--sidebar-w)); }

    /* `.rl-footer` is shared with the auth layout, which has no sidebar — scope
       the offset to the console. margin-left (not padding) so the centred text
       centres inside the remaining region. */
    .rl-console .rl-footer { margin-left: var(--sidebar-w); }

    /* The listings create/edit save bar is `position: fixed; left: 0; right: 0`
       at ALL widths, so without this it runs underneath the rail. */
    body.has-sticky-save .sticky-save-bar { left: var(--sidebar-w); }
}

/* --- RLEADS-168 part 2: six labelled nav groups ---------------------------
   The RLEADS-90 clusters (two bare hairlines) are replaced by
   經營 / 品牌 / 通路 / 洞察 / 團隊 / 系統 headings. The wrapper must be a flex
   column at EVERY width: `.nav-links a` is `display: inline-flex`, so inside a
   plain block <div> the ≤900px drawer's 46px rows would line up side by side.
   (RLEADS-171 item 2 deleted the superseded hairline rules, which no view had
   emitted since this ticket and which only two string-matching stylesheet
   tests still held in place; both now pin the rules below instead.) */
.rl-console .nav-links .nav-group {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.rl-console .nav-links .nav-group-label {
    display: block;
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .1em;
    line-height: 1.4;
    white-space: nowrap;
}

@media (min-width: 901px) {
    /* Rail: a quiet eyebrow. The first group sits directly under 工作台, so the
       top margin does the separating and no rule/hairline is needed. */
    .rl-console nav.topbar .nav-links .nav-group-label {
        padding: .95rem 18px .3rem;
    }
}

@media (max-width: 900px) {
    /* Drawer: the same eyebrow would vanish between 46px rows, so it becomes a
       filled section band — the visual successor to the RLEADS-96 full-width
       divider it replaces. The drawer gains 6 heading rows (~5 x 30px) on top
       of 工作台's row; it still scrolls inside the RLEADS-164 height cap. */
    .rl-console nav.topbar.nav-open .nav-links .nav-group-label {
        padding: .45rem 20px;
        background: var(--paper-alt);
        border-bottom: 1px solid var(--hairline);
        font-size: 11.5px;
    }
}

/* ============================================================
   RLEADS-170 (Console UX Wave 2, PR 3): two secondary-nav primitives
   ============================================================
   The console expressed ONE concept — "switch which subset of this page you
   are looking at" — four different ways, with the meanings crossed:

     .cal-tabs (underline)      行事曆 view mode + 客戶評價 status + 總覽 period
                                — three different meanings on one component
     .task-filters .chip        追蹤任務 status filter
     .filters + a.small.primary 成效分析 period — a BUTTON class used as a tab

   The sharpest symptom: AnalyticsPeriod::ALLOWED_DAYS drove both the 總覽 and
   the 成效分析 period selector, and they rendered differently on the two pages.

   Converged onto two primitives, split by MEANING rather than by page:

     .view-tabs     underline — genuine view-mode switching. Only 行事曆's
                    議程／週檢視 qualifies: the same data, shown two ways.
     .filter-chips  pill      — status and period filters, i.e. "show me a
                    SUBSET": 客戶評價 status, 追蹤任務 filter, 總覽 period,
                    成效分析 period.

   Both take their anchor from <x-tab-link>, so the active state (`.active` +
   aria-current="page") is written in exactly one place.

   `.section-anchors` (settings, profile/edit) is deliberately NOT folded in:
   it is an in-page #fragment table of contents with no active state and no
   query params — a different concept that merely happens to look like pills.

   The superseded `.cal-tabs` / `.task-filters` / `.chip` rules are deleted
   rather than left orphaned; nothing referenced them once the views moved.
   (`.week-grid .card.is-today` shipped inside the same RLEADS-102 block but is
   unrelated to tabs, and stays.)

   NOT the same thing as `.rl-public .filter-chips` (RLEADS-156, brand-page
   listing filters) further up: that strip is <button aria-pressed> toggled by
   JS with no navigation, and it is terracotta-accented like the rest of the
   public surface. The name is shared on purpose — same visual idea, same
   vocabulary across the two surfaces — and the `.rl-`prefix scoping keeps them
   from ever meeting. Console = <a aria-current>, navigates, navy. */
.rl-console .view-tabs {
    display: flex;
    align-items: center;
    gap: 1.1rem;
    border-bottom: 1px solid var(--border);
    margin: 0 0 1rem;
}
.rl-console .view-tabs a {
    display: inline-flex;
    align-items: center;
    padding: .4rem .1rem;
    color: var(--steel);
    text-decoration: none;
    font-size: .9rem;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.rl-console .view-tabs a:hover { color: var(--navy); }
.rl-console .view-tabs a.active {
    color: var(--navy);
    font-weight: 700;
    border-bottom-color: var(--navy);
}

/* Pill strip: "show me a SUBSET of this page". Carries its own flex+gap so a
   view never needs to pair it with the generic `.filters` form row the way
   `.task-filters` did. Wraps rather than scrolls — the longest strip is 4
   short labels (客戶評價), and it also sits inside a half-width 總覽 card. */
.rl-console .filter-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .45rem .5rem;
    margin: 0 0 1rem;
}
.rl-console .filter-chips a {
    display: inline-flex;
    align-items: center;
    padding: .35rem .8rem;
    border-radius: 999px;
    border: 1px solid var(--hairline);
    background: var(--paper-alt);
    color: var(--steel);
    font-size: .85rem;
    font-weight: 600;
    text-decoration: none;
    line-height: 1.2;
}
.rl-console .filter-chips a:hover { border-color: var(--navy-tint); }
.rl-console .filter-chips a.active {
    background: var(--navy-tint);
    border-color: var(--navy);
    color: var(--navy);
}

/* ===================== RLEADS-172: 評價表單星等選項 ===================== */
/* The 客戶評價 form (public/review-form.blade.php) shipped `.star-group`/`.star`
   markup with NO rule behind it. The only `.star` selector in this file is
   `.rl-public .rv-stars .star` (testimonial-card display stars, further up) —
   it needs an `.rv-stars` ancestor, so it never matched the form. The five
   radios therefore inherited only the global `.rl-public input { width: 100% }`,
   rendering as five full-width bordered slabs with their star text detached
   from the control they belong to. Same class of defect as the ones RLEADS-87 /
   -156 fixed on .ptab-radio, .type-toggle and .slot-chip; PublicInputCoverageTest
   now fails the build on the next one.

   LAYOUT — why a vertical stack and not a chip row.
   These labels are deliberately ragged: the glyph run IS the content, so
   「★★★★★ 5 星」 is ~4em wider than 「★ 1 星」. A horizontal strip has to pick a
   poison. Equal-width chips (the .type-toggle `flex: 1` anatomy) size all five
   to the widest, so the 1-星 chip wastes over half its box — and on a 320px card
   one fifth of the row cannot hold 「★★★★★ 5 星」 at all. Ragged chips that wrap
   (the .slot-chip anatomy) break the star run across lines at an unpredictable
   point. Stacking instead makes ragged width a non-property: each option is its
   own block row, every ★ run starts at the same left edge, and the group reads
   as one descending scale. Five rows fit at every width. So the widths are NOT
   normalised — the layout is changed so that they no longer need to be.

   The row is a LABEL, so the whole thing is the hit target; the radio is
   visually hidden but focusable (position/1x1px/opacity, never display:none —
   see the RLEADS-156 note on .type-toggle for what that costs keyboard users).
   Unlike its three siblings this one sets `position: relative`, so the hidden
   control stays inside its own row: absolutely positioned with no positioned
   ancestor it would resolve against the initial containing block, and focusing
   it could scroll the page away from the group.

   Also unlike its siblings the unchecked row has NO background: `.slot-chip
   span` paints literal white, but everything appended after the RLEADS-165
   banner is token-only (ConsoleComponentsTest::test_the_new_rules_introduce_no_
   raw_colors — which scans comments too, so do not write a hex literal even in
   prose here) and this file has no white token. The row sits on
   `.rl-public .card`, which is white under every theme preset, so transparent
   renders identically — and stays right if a preset ever tints the card. */

.rl-public .star-group {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    margin-bottom: .2rem;
}
.rl-public label.star {
    position: relative;
    display: block;
    margin: 0;
    padding: .5rem .8rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    color: var(--steel);
    font-size: .95rem;
    font-weight: 400;
    line-height: 1.4;
    cursor: pointer;
}
.rl-public label.star input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
.rl-public label.star:hover { border-color: var(--navy); color: var(--navy); }
.rl-public label.star:has(input:checked) {
    border-color: var(--navy);
    background: var(--navy-tint);
    color: var(--navy);
    font-weight: 700;
}
.rl-public label.star:has(input:focus-visible) {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
}

/* ===================== RLEADS-174: 工作台 KPI strip ===================== */

/* The dashboard shows THREE KPIs, not the analytics page's five, so it opts into
   a 3-column grid. Specificity is 3 classes, which beats the base `.kpis` rule at
   every width — including inside the ≤720 media query, whose `.kpis` selector is
   only 2 classes. A media query does not add specificity, so the phone override
   below has to be restated at the same 3-class weight to win there. */
.rl-console .kpis.cols-3 { grid-template-columns: repeat(3, 1fr); }

@media (max-width: 720px) {
    .rl-console .kpis.cols-3 { grid-template-columns: repeat(2, 1fr); }
}

/* A KPI card is a link to the page that explains its number. `.kpi` already draws
   the card; this only undoes the global anchor colour/underline so the number keeps
   its navy and the label its muted grey. */
.rl-console a.kpi { display: block; color: inherit; text-decoration: none; }
.rl-console a.kpi:hover { border-color: var(--navy); }
.rl-console a.kpi:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
