/* ══════════════════════════════════════════════════════════════════════
   FRONTIER PROPERTY CARD (Phase 3)
   Three-zone scoreboard: Position · Demand Pipeline · Rent
   See mockups/property_card_v4.html for the design spec.
   ══════════════════════════════════════════════════════════════════════ */

/* Card shell */
.fr-property-card {
    background: var(--fr-surface);
    border: 1px solid var(--fr-hairline);
    border-radius: var(--fr-r-lg);
    overflow: visible;          /* required so the badge tooltip can escape */
    box-shadow: 0 1px 0 rgba(20, 24, 29, 0.02), 0 1px 2px rgba(20, 24, 29, 0.03);
    margin-bottom: 24px;
    font-family: var(--fr-font-sans);
    color: var(--fr-ink);
}

/* Card header strip (property name + subtitle + updated indicator) */
.fr-pc-header {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 14px;
    padding: 22px 32px 18px;
    border-bottom: 1px solid var(--fr-hairline);
}
.fr-pc-title {
    font-family: var(--fr-font-serif);
    font-size: 30px;
    font-weight: 400;
    letter-spacing: -0.01em;
    color: var(--fr-ink);
    line-height: 1;
    margin: 0;
}
.fr-pc-subtitle {
    color: var(--fr-ink-3);
    font-size: 13px;
    font-weight: 400;
}
.fr-pc-subtitle .fr-sep {
    display: inline-block;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: var(--fr-ink-3);
    margin: 0 8px;
    vertical-align: middle;
    opacity: 0.6;
}
.fr-pc-header-spacer { flex: 1; }
.fr-updated {
    font-family: var(--fr-font-mono);
    font-size: 11px;
    color: var(--fr-ink-3);
    letter-spacing: 0.02em;
}
.fr-live-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--fr-positive);
    margin-right: 6px;
    vertical-align: middle;
    box-shadow: 0 0 0 0 rgba(31, 122, 77, 0.5);
    animation: fr-pulse 2.4s ease-out infinite;
}
@keyframes fr-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(31, 122, 77, 0.45); }
    70%  { box-shadow: 0 0 0 6px rgba(31, 122, 77, 0); }
    100% { box-shadow: 0 0 0 0 rgba(31, 122, 77, 0); }
}

/* Three-zone grid */
.fr-pc-zones {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
}
.fr-pc-zone {
    padding: 26px 32px 30px;
    border-right: 1px solid var(--fr-hairline);
    position: relative;
    opacity: 0;
    transform: translateY(6px);
    animation: fr-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.fr-pc-zone:last-child { border-right: 0; }
.fr-pc-zone:nth-child(1) { animation-delay: 80ms; }
.fr-pc-zone:nth-child(2) { animation-delay: 160ms; }
.fr-pc-zone:nth-child(3) { animation-delay: 240ms; }
@keyframes fr-rise {
    to { opacity: 1; transform: translateY(0); }
}
.fr-zone-label {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--fr-ink-3);
    font-weight: 500;
    margin-bottom: 22px;
    display: flex;
    align-items: center;
    gap: 10px;
}
.fr-zone-label::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--fr-hairline);
}

/* Hero stat block (one per zone) */
.fr-stat-hero {
    margin-bottom: 26px;
}
.fr-stat-hero-value {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 36px;
    font-weight: 500;
    color: var(--fr-ink);
    letter-spacing: -0.025em;
    line-height: 1;
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.fr-stat-hero-value .fr-of,
.fr-stat-hero-value .fr-sub  {
    color: var(--fr-ink-3);
    font-size: 22px;
    font-weight: 400;
}
.fr-stat-hero-value .fr-delta {
    font-size: 13px;
    margin-left: 4px;
    font-weight: 500;
    font-family: var(--fr-font-mono);
}
.fr-stat-hero-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--fr-ink-3);
    margin-top: 8px;
    font-weight: 500;
}
/* Zone 1 hero variant: hero on left, publish badge on right */
.fr-stat-hero--with-badge {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}
.fr-stat-hero--with-badge .fr-hero-text { flex: 0 0 auto; }

/* Stat row (label : value pairs) */
.fr-stat-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 10px 0;
    border-top: 1px dashed var(--fr-hairline);
}
.fr-stat-row:first-of-type {
    border-top: 1px solid var(--fr-hairline);
    padding-top: 12px;
}
.fr-stat-label {
    color: var(--fr-ink-2);
    font-size: 12.5px;
    font-weight: 400;
}
.fr-stat-value {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 14px;
    font-weight: 500;
    color: var(--fr-ink);
}
.fr-stat-value .fr-unit {
    color: var(--fr-ink-3);
    font-size: 11px;
    margin-left: 3px;
    font-weight: 400;
}
.fr-delta {
    font-family: var(--fr-font-mono);
    font-size: 11.5px;
    font-weight: 500;
}
.fr-delta.fr-up   { color: var(--fr-positive); }
.fr-delta.fr-down { color: var(--fr-negative); }

/* ── Adv. vs in-place row (Zone 3 verdict) ──
   Label left (like the Market position row beneath it); the figures sit in a
   compact color-coded pill on the right — green when asking rents lead the
   in-place roll, red when they trail. Small on purpose: a sleek verdict, not
   a banner. The row's first-of-type solid top border doubles as the divider
   between it and the sparkline above. */
.fr-adv-stat-row { align-items: center; }
/* Slightly heavier treatment for the two Zone-3 verdict rows (adv vs
   in-place + market position) so they carry more weight than the plain
   stat rows in the other zones. Scoped to these rows only — the shared
   .fr-stat-label / .fr-stat-value styles drive Zones 1 & 2 too. */
.fr-stat-row.fr-adv-stat-row,
.fr-stat-row.fr-stat-row--with-bar {
    padding-top: 13px;
    padding-bottom: 13px;
}
.fr-adv-stat-row .fr-stat-label,
.fr-stat-row--with-bar .fr-stat-label {
    font-size: 13.5px;
    font-weight: 500;
    color: var(--fr-ink);
}
.fr-stat-row--with-bar .fr-stat-value {
    font-size: 15px;
}
.fr-adv-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 11px;
    border-radius: 999px;
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.01em;
}
.fr-adv-pill.fr-up {
    background: var(--fr-positive-tint-2);
    color: var(--fr-positive-deep);
}
.fr-adv-pill.fr-down {
    background: var(--fr-negative-tint-2);
    color: var(--fr-negative-deep);
}
.fr-adv-pill .fr-adv-arrow { font-size: 9px; }
.fr-adv-pill .fr-adv-sep   { opacity: 0.5; }

/* ── Market position percentile bar (Zone 3) ──
   Used in render_z3_stats. The right side of the row holds a thin
   track with a colored fill from 0 to N% and the numeric value
   beside it, instead of a plain "39th pct" text value. */
.fr-stat-row--with-bar {
    align-items: center;  /* override 'baseline' so the bar lines up vertically */
}
.fr-pct-row-right {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    justify-content: flex-end;
    max-width: 70%;
}
.fr-pct-bar-wrap {
    flex: 1;
    min-width: 90px;
    max-width: 160px;
}
.fr-pct-bar-track {
    position: relative;
    width: 100%;
    height: 11px;
    background: var(--fr-hairline);
    border-radius: 6px;
    overflow: hidden;
}
.fr-pct-bar-fill {
    height: 100%;
    background: var(--fr-accent);
    border-radius: 6px;
    transition: width 0.25s ease-out;
}
/* Quartile segment markers — 1px-wide translucent overlays at 25/50/75
   that paint on top of both the fill and the track. Reads as faint
   joints dividing the bar into quartiles, like a fuel gauge. The
   semi-transparent black darkens whatever sits beneath: amber → darker
   amber over the fill, hairline → darker gray over the unfilled track.
   This sits in front of .fr-pct-bar-fill by source order. */
.fr-pct-bar-track::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: linear-gradient(
        to right,
        transparent calc(25% - 0.75px),
        rgba(0, 0, 0, 0.36) calc(25% - 0.75px),
        rgba(0, 0, 0, 0.36) calc(25% + 0.75px),
        transparent calc(25% + 0.75px),
        transparent calc(50% - 0.75px),
        rgba(0, 0, 0, 0.36) calc(50% - 0.75px),
        rgba(0, 0, 0, 0.36) calc(50% + 0.75px),
        transparent calc(50% + 0.75px),
        transparent calc(75% - 0.75px),
        rgba(0, 0, 0, 0.36) calc(75% - 0.75px),
        rgba(0, 0, 0, 0.36) calc(75% + 0.75px),
        transparent calc(75% + 0.75px)
    );
}

/* ── Zone-level action button (Zone 3 "View Market Charts") ── */
.fr-zone-action-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    margin-top: 16px;
    padding: 9px 14px;
    background: transparent;
    border: 1px solid var(--fr-hairline-strong);
    border-radius: 6px;
    color: var(--fr-ink-2);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition: all 0.15s ease-out;
}
.fr-zone-action-btn:hover {
    background: var(--fr-accent-soft);
    border-color: var(--fr-accent);
    color: var(--fr-accent-strong);
}
.fr-zone-action-btn:active {
    transform: translateY(1px);
}
.fr-zone-action-btn-arrow {
    font-size: 14px;
    line-height: 1;
    transform: translateY(-1px);
}

/* ── Exposure readout (Zone 1) ──
   Sits directly under the "13 / 202 available units" hero and decomposes it:
   a single segmented bar (red vacant → green 60d+) plus one line of counts.
   Deliberately only ~34px tall — this zone also carries the forward-occupancy
   chart and the net-to-rent strip, and the card has to stay skimmable.

   The hero's own 26px bottom margin is too much air between a number and its
   own breakdown, so it tightens when a non-empty exposure block follows it.
   :has() is the right tool here (the gap belongs to the hero, not the bar);
   where it is unsupported the layout simply keeps today's roomier spacing. */
.fr-stat-hero:has(+ .fr-z1-exposure:not(:empty)) {
    margin-bottom: 12px;
}
/* This margin plus .fr-occ-trajectory's own 18px margin-top is the whole gap
   down to the FORWARD OCCUPANCY rule — it stands in for the "Current
   occupancy" row that used to fill that space (and duplicated the chart's own
   NOW point). Together with the 14px reclaimed from the hero above, it keeps
   Zone 1 within ~4px of the height it had before the exposure block existed,
   which is what keeps its baseline aligned with Zone 2. Retune both together. */
.fr-z1-exposure:not(:empty) {
    margin-bottom: 22px;
}
/* Stops short of the zone's right edge. The publish badge sits at the right
   of the hero row directly above, so a full-width bar ran underneath it and
   read as if it were about to collide with it. The reserved 210px covers the
   wider of the two badges ("N units unpublished" + its unit-type list) plus
   breathing room, and tracks the zone as the viewport resizes — a fixed
   percentage would not. min-width keeps it a bar, not a stub, on narrow zones. */
.fr-exposure-bar {
    display: flex;
    width: calc(100% - 210px);
    min-width: 150px;
    height: 10px;
    gap: 3px;
}
/* Each segment is its own rounded pill rather than a slice of one continuous
   track. With four warm-to-cool steps only ~10px tall, the gap between pills
   is doing as much work as the colors: it guarantees a boundary is visible
   even where two adjacent steps are close in tone. */
.fr-exposure-bar .seg {
    height: 100%;
    min-width: 6px;
    border-radius: 5px;
}
.fr-exposure-counts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
    margin-top: 9px;
}
/* Detail link — last item on the count line, so it costs no vertical space
   (Zone 1 has to keep matching Zone 2's height) and sits beside the numbers
   it drills into.

   It flows directly after the final bucket rather than being pinned to the
   zone's right edge: pinned, it stranded itself a couple hundred pixels from
   the counts and read as unrelated furniture. The extra left margin (on top
   of the row's own 12px gap) is what keeps it from reading as a fifth
   bucket — far enough to be an action, near enough to belong to the group.
   On a narrow zone the flex-wrap above drops it to its own line. */
.fr-exposure-link {
    margin-left: 10px;
    padding: 0;
    background: transparent;
    border: 0;
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    font-family: var(--fr-font-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--fr-ink-3);
    cursor: pointer;
    transition: color 0.15s ease-out;
}
.fr-exposure-link:hover {
    color: var(--fr-accent-strong);
}
.fr-exposure-link:focus-visible {
    outline: 1px solid var(--fr-accent);
    outline-offset: 3px;
    border-radius: 2px;
}
.fr-exposure-link-arrow {
    font-size: 13px;
    line-height: 1;
}
/* Zero buckets stay listed but recede, so the four categories remain
   learnable and the block's height doesn't jump as availability changes. */
.fr-exposure-cell.is-zero {
    opacity: 0.38;
}
.fr-exposure-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 2px;
    margin-right: 5px;
    vertical-align: middle;
    position: relative;
    top: -1px;
}
.fr-exposure-count {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--fr-ink);
}
.fr-exposure-lab {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--fr-ink-3);
    margin-left: 4px;
    white-space: nowrap;
}
.fr-exposure-none {
    font-size: 12px;
    color: var(--fr-ink-3);
}

/* Forward-occupancy mini chart (Zone 1) */
.fr-occ-trajectory {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--fr-hairline);
}
.fr-occ-label {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--fr-ink-3);
    font-weight: 500;
    margin-bottom: 12px;
}
.fr-occ-chart-wrap {
    position: relative;  /* anchors the .fr-occ-dot overlay */
    width: 100%;
    height: 124px;       /* Trimmed from 160 to 120 to make room for the
                            net-to-rent strip (incl. its divider) below while
                            keeping Zone 1's total height — and its baseline
                            alignment with Zone 2 — unchanged. Given 12px back
                            once the duplicate "Current occupancy" row came
                            out: the zones are grid siblings stretched to the
                            tallest, so that surplus showed as dead air under
                            the net-to-rent strip. Spent on the chart, which
                            was the thing the original trim had squeezed.
                            THIS IS THE KNOB for Zone 1's total height — raise
                            it if the zone still bottoms out short of Zone 2,
                            lower it if Zone 1 starts driving the card. Traded
                            8px of it for the margin below, which the promoted
                            NOW figure needed as clearance; the two move
                            together, so Zone 1's height is unchanged. */
    margin-bottom: 14px;
}
/* True-circle data points overlaid on the SVG line+area. The path
   inside the SVG stretches non-uniformly with the container, which
   would distort SVG circles into ovals — so we render the dots as
   HTML elements positioned at percentage coordinates instead. */
.fr-occ-dot {
    position: absolute;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: #FFFFFF;
    border: 1.6px solid #B45309;
    transform: translate(-50%, -50%);
    pointer-events: none;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
/* The NOW point is the only measured value on this line; the other three are
   projections. Filling it says so in the chart's own language, which is a
   quieter way to anchor the eye than making its axis figure outsized. */
.fr-occ-dot.is-now {
    background: var(--fr-accent);
}
/* Each cell is absolutely positioned at the same x% as its dot above
   (the percentages are computed in render_z1_occ_chart and applied as
   inline ``left: X%`` styles). ``transform: translateX(-50%)`` centers
   each label horizontally on its dot. */
.fr-occ-axis {
    position: relative;
    height: 30px;
    margin-top: 2px;
}
/* Bottom-anchored, not top-anchored, so the four NOW/30/60/90 captions keep a
   shared baseline even though the NOW figure above them is set larger. With
   top:0 the taller cell pushed its own caption down and broke the row. */
.fr-occ-axis-cell {
    position: absolute;
    bottom: 0;
    transform: translateX(-50%);
    text-align: center;
    font-family: var(--fr-font-mono);
    font-size: 11px;
    color: var(--fr-ink-2);
    font-weight: 500;
    white-space: nowrap;
}
/* Current occupancy. It is the one fact on this axis — the other three are
   projections off it — and it is the number the business is judged on, so it
   outranks them typographically instead of being one of four identical ticks.

   12px, not 13: the emphasis is carried by three quiet signals together —
   weight, full ink, and the solid dot on the line above — rather than by size
   alone. At 13px the figure was tall enough to crowd the chart's baseline and
   left the tick row visibly ragged, which read as a mistake rather than a
   hierarchy. A half-step still ranks it, and the row stays even. */
.fr-occ-axis-cell.is-now {
    font-size: 12px;
    color: var(--fr-ink);
    font-weight: 700;
}
.fr-occ-axis-cell .fr-axis-label {
    display: block;
    color: var(--fr-ink-3);
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin-top: 2px;
    font-family: var(--fr-font-sans);
    font-weight: 500;
}
.fr-occ-axis-cell.is-now .fr-axis-label {
    color: var(--fr-ink-2);
    font-weight: 600;
}

/* Net-to-rent strip (Zone 1): units still to lease, aligned under the
   30d/60d/90d occupancy points. A hairline divides it from the % axis so
   counts and percentages don't read as the same series. The top padding is
   sized to roughly match the zone's own 30px bottom padding, which centers
   the row vertically in the space between the hairline and the zone bottom
   (rather than crowding the hairline). Caption + cells share a bottom edge
   so their text baselines align despite the different font sizes. */
.fr-ntr-strip {
    position: relative;
    height: 18px;
    margin-top: 10px;
    padding-top: 34px;
    border-top: 1px solid var(--fr-hairline);
}
.fr-ntr-caption {
    position: absolute;
    left: 0;
    bottom: 0;
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--fr-ink-3);
    font-weight: 500;
}
.fr-ntr-cell {
    position: absolute;
    bottom: 0;
    transform: translateX(-50%);
    text-align: center;
    white-space: nowrap;
}
.fr-ntr-value {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    font-weight: 600;
    color: var(--fr-ink);
}
.fr-ntr-pct {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    font-weight: 500;
    color: var(--fr-ink-3);
    margin-left: 4px;
}
.fr-ntr-unit {
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--fr-ink-3);
    margin-left: 4px;
}

/* Traffic funnel (Zone 2) */
.fr-funnel-wrap {
    margin-top: 24px;
    padding-top: 22px;
    border-top: 1px solid var(--fr-hairline);
}

/* Funnel header: title (+ info icon) on the left, 7D/30D toggle on the right */
.fr-funnel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}
.fr-funnel-title-group {
    display: inline-flex;
    align-items: center;
}
.fr-funnel-title {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--fr-ink-3);
    font-weight: 500;
}

/* Small info icon (FontAwesome) — hover reveals the YoY tooltip */
.fr-info-icon {
    margin-left: 6px;
    font-size: 11px;
    color: var(--fr-ink-3);
    cursor: help;
    opacity: 0.6;
    transition: opacity 120ms ease, color 120ms ease;
}
.fr-info-icon:hover {
    opacity: 1;
    color: var(--fr-ink-2);
}

/* 7D / 30D segmented toggle */
.fr-funnel-toggle {
    display: inline-flex;
    border-radius: 6px;
    background: var(--fr-bg);
    padding: 2px;
    border: 1px solid var(--fr-hairline);
}
.fr-funnel-toggle-btn {
    padding: 3px 9px;
    border: 0;
    background: transparent;
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-size: 10.5px;
    font-weight: 500;
    color: var(--fr-ink-3);
    cursor: pointer;
    border-radius: 4px;
    letter-spacing: 0.04em;
    transition: background 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.fr-funnel-toggle-btn:hover { color: var(--fr-ink-2); }
/* Default: 30D is active. Toggle the active styling via the wrap class. */
.fr-funnel-wrap .fr-funnel-toggle-btn[data-period="30d"] {
    background: var(--fr-surface);
    color: var(--fr-ink);
    box-shadow: 0 1px 2px rgba(20, 24, 29, 0.06);
}
.fr-funnel-wrap.fr-funnel-show-7d .fr-funnel-toggle-btn[data-period="30d"] {
    background: transparent;
    color: var(--fr-ink-3);
    box-shadow: none;
}
.fr-funnel-wrap.fr-funnel-show-7d .fr-funnel-toggle-btn[data-period="7d"] {
    background: var(--fr-surface);
    color: var(--fr-ink);
    box-shadow: 0 1px 2px rgba(20, 24, 29, 0.06);
}

/* Two row groups, one per period — only one visible at a time */
.fr-funnel-rows--7d  { display: none; }
.fr-funnel-rows--30d { display: block; }
.fr-funnel-wrap.fr-funnel-show-7d .fr-funnel-rows--7d  { display: block; }
.fr-funnel-wrap.fr-funnel-show-7d .fr-funnel-rows--30d { display: none; }

.fr-funnel-row {
    display: grid;
    grid-template-columns: 56px 1fr 36px 56px;
    align-items: center;
    gap: 12px;
    padding: 7px 0;
}
.fr-funnel-label { font-size: 12.5px; color: var(--fr-ink-2); font-weight: 400; }
.fr-funnel-bar-track {
    height: 6px;
    background: var(--fr-hairline);
    border-radius: 99px;
    overflow: hidden;
}
.fr-funnel-bar-fill {
    height: 100%;
    border-radius: 99px;
    background: linear-gradient(90deg, var(--fr-accent) 0%, var(--fr-accent-2) 100%);
    transform-origin: left center;
    animation: fr-bar-grow 800ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
    transform: scaleX(0);
}
@keyframes fr-bar-grow { to { transform: scaleX(1); } }
.fr-funnel-rows .fr-funnel-row:nth-child(1) .fr-funnel-bar-fill { animation-delay: 240ms; }
.fr-funnel-rows .fr-funnel-row:nth-child(2) .fr-funnel-bar-fill { animation-delay: 320ms; }
.fr-funnel-rows .fr-funnel-row:nth-child(3) .fr-funnel-bar-fill { animation-delay: 400ms; }
.fr-funnel-rows .fr-funnel-row:nth-child(4) .fr-funnel-bar-fill { animation-delay: 480ms; }
.fr-funnel-value {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    font-weight: 500;
    color: var(--fr-ink);
    text-align: right;
}

/* YoY delta column: green ▲ / red ▼ / muted — */
.fr-funnel-yoy {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    font-weight: 500;
    text-align: right;
    white-space: nowrap;
}
.fr-funnel-yoy.fr-up        { color: var(--fr-positive); }
.fr-funnel-yoy.fr-down      { color: var(--fr-negative); }
.fr-funnel-yoy--neutral     { color: var(--fr-ink-3); }

/* Sparkline wrapper (Zone 3) */
.fr-sparkline-wrap {
    margin: 14px 0 22px;
    height: 64px;
}

/* Publish-status badge (Zone 1 hero) */
.fr-publish-badge {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 11px 14px;
    border-radius: var(--fr-r-md);
    cursor: pointer;
    transition: background 140ms ease;
    text-decoration: none;
    flex: 1 1 auto;
    max-width: 200px;
    align-self: stretch;
    justify-content: center;
}
.fr-publish-badge--clean {
    background: var(--fr-positive-tint);
}
.fr-publish-badge--clean:hover {
    background: var(--fr-positive-tint-2);
}
.fr-publish-badge--action {
    background: var(--fr-warning-tint);
}
.fr-publish-badge--action:hover {
    background: var(--fr-warning-tint-2);
}
.fr-pb-top {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: -0.005em;
}
.fr-publish-badge--clean .fr-pb-top  { color: var(--fr-positive-deep); }
.fr-publish-badge--action .fr-pb-top { color: var(--fr-warning-deep); }
.fr-pb-icon {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #FFFFFF;
    flex-shrink: 0;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}
.fr-publish-badge--clean .fr-pb-icon  { background: var(--fr-positive); }
.fr-publish-badge--action .fr-pb-icon { background: var(--fr-warning); }
.fr-pb-detail {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-size: 11px;
    color: var(--fr-ink-3);
    letter-spacing: 0.01em;
    padding-left: 24px;
    line-height: 1.3;
}

/* Tooltip for badge hover (action state only — shows per-unit-type breakdown) */
.fr-publish-badge[data-tooltip]::after,
.fr-publish-badge[data-tooltip]::before {
    opacity: 0;
    pointer-events: none;
    transition: opacity 160ms ease, transform 160ms ease;
    z-index: 20;
}
.fr-publish-badge[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    top: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(-4px);
    background: var(--fr-ink);
    color: #FFFFFF;
    padding: 9px 12px;
    border-radius: 8px;
    font-family: var(--fr-font-mono);
    font-size: 11.5px;
    line-height: 1.5;
    white-space: pre;
    box-shadow: 0 8px 24px rgba(20, 24, 29, 0.18);
}
.fr-publish-badge[data-tooltip]::before {
    content: '';
    position: absolute;
    top: calc(100% + 2px);
    left: 50%;
    transform: translateX(-50%) translateY(-4px);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-bottom: 6px solid var(--fr-ink);
}
.fr-publish-badge[data-tooltip]:hover::after,
.fr-publish-badge[data-tooltip]:hover::before {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* Zone 1/2/3 stack on smaller screens */
@media (max-width: 1100px) {
    .fr-pc-zones { grid-template-columns: 1fr; }
    .fr-pc-zone {
        border-right: 0;
        border-bottom: 1px solid var(--fr-hairline);
    }
    .fr-pc-zone:last-child { border-bottom: 0; }
}


/* ──────────────────────────────────────────────────────────────────────
   PROPERTY CARD — slim header mode (Leasing Hub & Settings)
   ----------------------------------------------------------------------
   Leasing agents jump straight to the unit table; settings users don't
   need property metrics either — the demand/rent zones are noise for
   both workflows. The card class is toggled by a callback in
   app/callbacks/navigation.py based on the active page.
   ────────────────────────────────────────────────────────────────────── */

.fr-property-card.fr-property-card-slim {
    background: transparent;
    border: none;
    box-shadow: none;
    margin-bottom: 16px;
}
.fr-property-card.fr-property-card-slim .fr-pc-header {
    border-bottom: none;
    padding: 14px 4px 4px;
}
.fr-property-card.fr-property-card-slim .fr-pc-zones,
.fr-property-card.fr-property-card-slim .fr-updated {
    display: none;
}


/* ──────────────────────────────────────────────────────────────────────
   PROPERTY CARD POPOVERS (Phase 5 / Task #8)
   Hover-revealed math breakdowns for the Leases Needed row and Demand
   Gap hero in Zone 2. Anchored to the new design language: warm white
   surface, hairline border, mono numerics, refined hierarchy.
   ────────────────────────────────────────────────────────────────────── */

.fr-popover.popover {
    border: 1px solid var(--fr-hairline) !important;
    border-radius: var(--fr-r-md) !important;
    background: var(--fr-surface) !important;
    box-shadow: 0 12px 32px rgba(20, 24, 29, 0.10),
                0 2px 4px rgba(20, 24, 29, 0.04) !important;
    font-family: var(--fr-font-sans) !important;
    max-width: 360px !important;
}
.fr-popover .popover-body {
    padding: 14px 16px !important;
    min-width: 280px;
    color: var(--fr-ink);
}
.fr-popover .popover-arrow::after,
.fr-popover .popover-arrow::before {
    border-bottom-color: var(--fr-hairline) !important;
}

.fr-popover-eyebrow {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--fr-ink-3);
    font-weight: 600;
    margin-bottom: 10px;
}
.fr-popover-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 4px 0;
    font-size: 13px;
    line-height: 1.4;
}
.fr-popover-row-label {
    color: var(--fr-ink-2);
}
.fr-popover-row-value {
    font-family: var(--fr-font-mono);
    font-feature-settings: 'tnum';
    font-variant-numeric: tabular-nums;
    color: var(--fr-ink);
    font-weight: 500;
    margin-left: 12px;
    white-space: nowrap;
}
.fr-popover-row--emphasis .fr-popover-row-label {
    color: var(--fr-ink);
    font-weight: 500;
}
.fr-popover-row--emphasis .fr-popover-row-value {
    font-weight: 600;
    font-size: 14px;
}
.fr-popover-divider {
    height: 1px;
    background: var(--fr-hairline);
    margin: 8px 0;
}
/* Muted context line tucked under a math row — annotates the row above
   (e.g. the expiring cohort behind Expected non-renewals) without reading
   as a term in the arithmetic. */
.fr-popover-subrow {
    font-size: 11.5px;
    color: var(--fr-ink-3);
    line-height: 1.35;
    margin: -2px 0 4px;
}

/* The cursor-help affordance lets the user know the element has more
   info available on hover. Applied to the popover anchors. */
.fr-popover-anchor {
    cursor: help;
}


/* ──────────────────────────────────────────────────────────────────────
   SECTION HEADER — page break between property card and unit-type list
   ────────────────────────────────────────────────────────────────────── */

.fr-section-header {
    margin: 8px 4px 18px;
}
.fr-section-title {
    font-family: var(--fr-font-serif);
    font-size: 32px;
    font-weight: 400;
    letter-spacing: -0.005em;
    color: var(--fr-ink);
    line-height: 1;
    margin: 0 0 6px;
}
.fr-section-description {
    font-family: var(--fr-font-serif);
    font-style: italic;
    font-size: 15px;
    color: var(--fr-ink-2);
    margin: 0;
    line-height: 1.4;
    letter-spacing: 0.005em;
}


/* Softer red for availability exposure "urgent" bar */
.bg-exposure-urgent {
  background-color: #e57373 !important;  /* Coral red - urgent but not alarming */
}

/* Renewals UI polish */
.renewals-summary-bar {
  gap: 8px;
}
.renewals-badge {
  display: inline-block;
  padding: 4px 8px;
  margin-right: 8px;
  border-radius: 12px;
  font-size: 12px;
  background: rgba(0,0,0,0.04);
}
.renewals-badge-current { color: #6c757d; }
.renewals-badge-new { color: #0dcaf0; }
.renewals-badge-base { color: #0d6efd; }
.renewals-badge-amenity { color: #20c997; }
.renewals-badge-increase { color: #198754; }
.renewals-badge-total { color: #0d6efd; font-weight: 600; }

.pill-override {
  background: #ffe8a1;
  color: #856404;
  padding: 2px 6px;
  border-radius: 10px;
  font-size: 11px;
}
/* Keep override input inline with badges, avoid wrapping early */
.override-inline { display: inline-block; vertical-align: middle; min-width: 140px; }

/* Renewals table terms collapse row styling */
.terms-collapse-row > td {
  background: #f6f8fb;
  border-top: 1px solid #dfe3e8;
  border-bottom: 1px solid #dfe3e8;
}

.terms-collapse-row .dash-table-container {
  margin: 0;
}

