/* =====================================================
   PHASE K.E — CLIENT PORTAL
   Reuses .client-* tokens from styles.css for consistency.
   These rules add the portal-specific surfaces:
     - portal-dropzone (upload area)
     - portal-queue (in-flight uploads)
     - portal-properties (submitted properties list)
     - portal-toast (non-blocking error/success messages)
   ===================================================== */

.portal-card {
  /* Slightly wider than the doc-upload card — the property list rows
     have more horizontal content (thumb + address + status). */
  max-width: 640px;
}

/* ── DROPZONE ─────────────────────────────────────── */

.portal-dropzone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1.75rem 1rem;
  margin-bottom: 1.25rem;
  background: var(--bg-2);
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-md);
  cursor: pointer;
  text-align: center;
  transition: border-color .15s ease, background .15s ease, transform .15s ease;
}
.portal-dropzone:hover { border-color: var(--accent-strong); }
.portal-dropzone.is-drag-over {
  border-color: var(--accent);
  background: var(--accent-soft);
  transform: scale(1.005);
}

.portal-dropzone-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--avatar-text);
  font-size: 1.5rem; font-weight: 600;
  margin-bottom: 0.25rem;
}
.portal-dropzone-title {
  font-size: 1rem; font-weight: 600;
  color: var(--text-primary);
}
.portal-dropzone-hint {
  font-size: 0.8125rem;
  color: var(--text-secondary);
}
.portal-dropzone-warn {
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: rgba(245, 158, 11, 0.10);
  border: 1px solid rgba(245, 158, 11, 0.32);
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  color: var(--st-amber);
  font-weight: 500;
  line-height: 1.35;
  max-width: 380px;
}
.portal-dropzone input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}

/* ── UPLOAD QUEUE ─────────────────────────────────── */

.portal-queue {
  list-style: none;
  padding: 0;
  margin: 0 0 1.25rem 0;
  display: flex; flex-direction: column;
  gap: 0.5rem;
}
.portal-queue-item {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0.75rem 0.875rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  animation: portal-fade-in .25s ease;
}
.portal-queue-item.is-error { border-color: rgba(239, 68, 68, 0.4); background: var(--danger-soft); }
.portal-queue-item.is-added,
.portal-queue-item.is-duplicate {
  border-color: rgba(16, 185, 129, 0.35);
  background: rgba(16, 185, 129, 0.05);
}

.portal-queue-thumb {
  width: 44px; height: 44px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.portal-queue-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
}
.portal-queue-thumb-placeholder { font-size: 1.25rem; opacity: 0.6; }

.portal-queue-body {
  flex: 1;
  min-width: 0;
  display: flex; flex-direction: column; gap: 0.125rem;
}
.portal-queue-title {
  font-size: 0.875rem; font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.portal-queue-status {
  font-size: 0.8125rem;
  color: var(--text-secondary);
}

.portal-queue-action { flex-shrink: 0; display: flex; align-items: center; }
.portal-queue-check {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  background: var(--success);
  color: white;
  border-radius: 999px;
  font-size: 0.875rem; font-weight: 700;
}
.portal-queue-retry {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  padding: 0.375rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  cursor: pointer;
}
.portal-queue-retry:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ── SECTION DIVIDER + TITLE ──────────────────────── */

.portal-section-divider {
  height: 1px;
  background: var(--border);
  margin: 1.25rem 0 1rem 0;
}
.portal-section-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 0.75rem;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

/* ── EMPTY STATE ──────────────────────────────────── */

.portal-empty {
  text-align: center;
  padding: 1.5rem 0;
  color: var(--text-secondary);
}
.portal-empty p { margin: 0.125rem 0; }

/* ── HOUSED-CLIENT CLOSED PAGE (2026-07) ──────────── */

.portal-closed {
  text-align: center;
  padding: 2.5rem 1rem 3rem;
}
.portal-closed .portal-closed-title {
  margin: 0.75rem 0 0.5rem;
  font-size: 1.35rem;
  line-height: 1.35;
}
.portal-closed p { margin: 0.25rem 0; }

/* ── PROPERTIES LIST ──────────────────────────────── */

.portal-properties {
  list-style: none;
  padding: 0; margin: 0;
  display: flex; flex-direction: column;
  gap: 0.625rem;
}
.portal-property {
  display: flex;
  gap: 0.875rem;
  padding: 0.75rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  transition: background .25s ease, border-color .25s ease, transform .25s ease;
  align-items: stretch;
}
.portal-property:hover { border-color: var(--border-strong); }
.portal-property.is-highlight {
  background: rgba(178, 34, 34, 0.08);
  border-color: var(--accent-strong);
  transform: translateX(2px);
}

.portal-property-thumb {
  /* K.F.3.A.5 — 16:9 thumbnail. OneHome screenshots place the photo
     near the top of the layout, so we cover-fit and pin the crop to
     center-top so the building stays visible. */
  width: 120px;
  aspect-ratio: 16 / 9;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3);
  border-radius: var(--radius-sm);
  overflow: hidden;
  align-self: flex-start;
}
.portal-property-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center top;
  display: block;
}
.portal-property-thumb-placeholder { font-size: 1.5rem; opacity: 0.4; }
.portal-property-specs { margin-top: 0.0625rem; }

.portal-property-body {
  flex: 1;
  min-width: 0;
  display: flex; flex-direction: column; gap: 0.25rem;
  justify-content: center;
}
.portal-property-address {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.3;
}
.portal-property-meta {
  display: flex; flex-wrap: wrap;
  gap: 0.625rem;
  font-size: 0.8125rem;
  color: var(--text-secondary);
  /* RAP-285 — this row holds the price and the wrapping open-house
     badge. Without min-width:0 it keeps its own auto minimum and can be
     pushed wider than .portal-property-body by a long child, which is
     the same overflow one level up. */
  min-width: 0;
}
.portal-property-price { font-weight: 500; color: var(--text-primary); }
.portal-property-notes {
  margin-top: 0.25rem;
  font-size: 0.8125rem;
  color: var(--text-secondary);
  font-style: italic;
}

.portal-property-status {
  flex-shrink: 0;
  display: flex; align-items: flex-start;
}
.portal-status-badge {
  display: inline-flex; align-items: center;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--bg-3);
  border: 1px solid var(--border);
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
}
.portal-status-badge.portal-status-inquired {
  background: rgba(59, 130, 246, 0.10);
  border-color: rgba(59, 130, 246, 0.3);
  color: var(--st-blue);
}
.portal-status-badge.portal-status-outreach_sent,
.portal-status-badge.portal-status-showing_set,
.portal-status-badge.portal-status-submitted {
  background: rgba(245, 158, 11, 0.10);
  border-color: rgba(245, 158, 11, 0.3);
  color: var(--st-amber);
}
.portal-status-badge.portal-status-approved {
  background: rgba(16, 185, 129, 0.10);
  border-color: rgba(16, 185, 129, 0.35);
  color: var(--st-green2);
}
.portal-status-badge.portal-status-denied,
.portal-status-badge.portal-status-withdrawn {
  background: var(--danger-soft);
  border-color: rgba(239, 68, 68, 0.3);
  color: var(--danger);
}

/* ── TOASTS ───────────────────────────────────────── */

.portal-toasts {
  position: fixed;
  bottom: 1rem; left: 1rem; right: 1rem;
  display: flex; flex-direction: column;
  gap: 0.5rem;
  pointer-events: none;
  z-index: 100;
}
.portal-toast {
  pointer-events: auto;
  padding: 0.75rem 1rem;
  background: var(--bg-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: 0.875rem;
  color: var(--text-primary);
  animation: portal-fade-in .25s ease;
  max-width: 480px;
  margin: 0 auto;
  width: calc(100% - 0px);
}
.portal-toast-success { border-color: rgba(16, 185, 129, 0.4); }
.portal-toast-error   { border-color: rgba(239, 68, 68, 0.5); background: var(--danger-soft); }
.portal-toast-info    { border-color: var(--border-strong); }

@keyframes portal-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── MOBILE TIGHTENING ────────────────────────────── */

@media (max-width: 480px) {
  .portal-card { padding: 1.5rem 1.125rem; }
  .portal-dropzone { padding: 1.5rem 0.75rem; }
  .portal-property {
    padding: 0.625rem;
    gap: 0.625rem;
  }
  .portal-property-thumb { width: 96px; }
  .portal-property-address { font-size: 0.875rem; }
  .portal-property-meta { font-size: 0.75rem; gap: 0.5rem; }
  .portal-status-badge { font-size: 0.6875rem; padding: 0.125rem 0.4375rem; }
  .portal-pill { font-size: 0.6875rem; padding: 0.1875rem 0.5rem; }
  .portal-section-title { font-size: 0.875rem; }
}

/* ── K.F.3.B.4 ASYNC UPLOAD HINT ──────────────────── */

.portal-upload-busy {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-top: 0.75rem;
  padding: 0.625rem 0.875rem;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-3);
  color: var(--text-secondary);
  font-size: 0.875rem;
}

/* ── K.F.3.A.8 STATUS PILLS ────────────────────────── */

/* Locked color spec from SPEC §A.8. Background at ~14% alpha, border
   at full alpha, text at full alpha. Mirrors the property header
   "Available" badge style on the admin side. */
.portal-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.1875rem 0.625rem;
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid transparent;
  line-height: 1.4;
}
.portal-pill-gray {
  background: rgba(148, 163, 184, 0.14);
  border-color: rgba(148, 163, 184, 0.55);
  color: rgb(148, 163, 184);
}
.portal-pill-yellow {
  background: rgba(245, 158, 11, 0.14);
  border-color: rgba(245, 158, 11, 0.65);
  color: rgb(245, 158, 11);
}
.portal-pill-blue {
  background: rgba(59, 130, 246, 0.14);
  border-color: rgba(59, 130, 246, 0.65);
  color: rgb(96, 165, 250);
}
.portal-pill-green {
  background: rgba(16, 185, 129, 0.14);
  border-color: rgba(16, 185, 129, 0.65);
  color: rgb(16, 185, 129);
}
.portal-pill-red {
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.65);
  color: rgb(239, 68, 68);
}
.portal-pill-leased {
  background: rgba(71, 85, 105, 0.20);
  border-color: rgba(71, 85, 105, 0.85);
  color: rgb(148, 163, 184);
}
/* UI/UX overhaul — off-market now reads as a "not available" state and
   shares the red treatment with declined / not-pursuing. data-pill-state
   stays "off_market" so the client filter can still bucket it. */
.portal-pill-off-market {
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.65);
  color: rgb(239, 68, 68);
}

/* ── UI/UX overhaul: client status filter + status display ─────────── */

/* Client status filter chips (separate label set from admin's 4-color).
   RAP-125 — the row is [ scrollable front chips ][ More ▾ ]. The front
   chips scroll horizontally on a phone rather than wrapping into a wall
   (Mauricio's Jul 28 review); "More" sits OUTSIDE the scroller so it is
   always reachable AND so its dropdown isn't clipped by the overflow. */
.portal-filter-bar { margin: 0.25rem 0 0.875rem; }
.portal-filter-row {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}
.portal-filter-chips {
  display: flex;
  gap: 0.375rem;
  flex: 1 1 auto;
  min-width: 0;
  /* RAP-285 — the row wraps and is CAPPED at two lines by
     applyChipOverflow(); chips past that move into the Más menu. Two
     lines rather than one because at 375px only "Todas" fits on a single
     line in Spanish, which would bury four everyday filters in a menu;
     and rather than free wrapping because five Spanish chips need four
     lines at 375px, which is the wall the Jul 28 review rejected. */
  flex-wrap: wrap;
  row-gap: 0.375rem;
  /* RAP-285 — the chip row NEVER scrolls sideways on a phone.
     It was `overflow-x: auto` + momentum touch scrolling (the Jul 28
     decision to swipe rather than wrap). Measured with the Spanish
     labels the client actually sees, that put 542px of a 716px chip
     strip off-screen at 320px with no affordance at all: chips ran to
     x=755 on a 320px screen, so content visibly escaped the edge and a
     vertical drag over the row panned it sideways.

     Overflowing chips now move into the "Más" menu instead —
     applyChipOverflow() in views/portal.js measures what fits and hides
     the rest — so nothing is reachable only by swiping.

     `hidden` is the clip-only safety net BEHIND that JS, never the
     mechanism: unlike `auto` it is not user-scrollable on iOS, so if the
     measurement is ever wrong the row still cannot pan. Both axes are
     named (not `overflow-x`) because `overflow-x: hidden` +
     `overflow-y: visible` computes the y axis back to `auto`. */
  overflow: hidden;
  padding-bottom: 1px;
}
.portal-filter-chip {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  flex: 0 0 auto;
  border: 1px solid var(--border-strong);
  background: var(--bg-2);
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.3125rem 0.75rem;
  min-height: 2rem;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.portal-filter-chip:hover { border-color: var(--accent); color: var(--text-primary); }
.portal-filter-chip.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* Set by applyChipOverflow() on the front chips that do not fit the row;
   each one is rendered again as a full-label row inside the Más menu. */
.portal-filter-chip.is-overflowed { display: none; }
/* Counts read as secondary weight inside the chip, full-contrast when
   the chip is the active one. */
.portal-filter-count { opacity: 0.72; font-weight: 500; }
.portal-filter-chip.is-active .portal-filter-count { opacity: 0.85; }

/* ── RAP-125: "More ▾" dropdown for the rare, high-stakes states ───── */
.portal-filter-more { position: relative; flex: 0 0 auto; }
.portal-filter-more-toggle { max-width: 11rem; }
.portal-filter-more-toggle > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
}
.portal-filter-caret { flex: 0 0 auto; font-size: 0.75rem; line-height: 1; }
/* Tap-anywhere-to-dismiss layer. Sits under the menu, over everything
   else; transparent so the list stays visible while choosing. */
.portal-filter-more-backdrop {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: transparent;
}
.portal-filter-more-menu {
  position: absolute;
  top: calc(100% + 0.375rem);
  right: 0;
  z-index: 31;
  min-width: 14rem;
  max-width: min(18rem, calc(100vw - 2.5rem));
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
  background: var(--bg-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.portal-filter-more-item {
  appearance: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  /* Thumb-sized: 44px is the iOS minimum touch target. */
  min-height: 2.75rem;
  padding: 0.5rem 0.6875rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.portal-filter-more-item:hover { background: var(--bg-3); color: var(--text-primary); }
.portal-filter-more-item.is-active { background: var(--accent); color: #fff; }

/* ── RAP-125: "Showing: Ready to view (3)" active-filter indicator ──── */
.portal-filter-showing {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
  font-size: 0.8125rem;
}
.portal-filter-showing-label { color: var(--text-secondary); }
.portal-filter-showing-value {
  font-weight: 700;
  color: var(--text-primary);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.portal-filter-clear {
  appearance: none;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Thumb-sized clear target — the chip row is small, this must not be. */
  width: 2.25rem;
  height: 2.25rem;
  margin-left: auto;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--bg-2);
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.875rem;
  line-height: 1;
  cursor: pointer;
}
.portal-filter-clear:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}

/* Above phone width there is room to wrap into two tidy rows, which
   beats a hidden horizontal scroller for mouse users who get no swipe
   affordance. The 640px card can't fit all five front chips on one line
   even on desktop, so this rule earns its keep. */
@media (min-width: 481px) {
  .portal-filter-chips {
    flex-wrap: wrap;
    /* RAP-285 — both axes, for the same computed-value reason as the base
       rule: `overflow-x: visible` beside a `hidden` y would silently
       become `auto` and reinstate the scroller this issue removed. */
    overflow: visible;
    row-gap: 0.375rem;
  }
}

/* Thumb targets grow on the surface that is actually thumbed. The chip
   row swipes horizontally here (no wrap), so chips stay compact while the
   two things a finger must hit — the More toggle and the clear ✕ — get
   bigger, and the menu rows clear 44px comfortably.

   These MUST stay below the base .portal-filter-* rules above: the
   MOBILE TIGHTENING block earlier in this file sits before them, and at
   equal specificity source order decides. Putting them there silently
   loses the cascade (caught live at 375px — the ✕ measured 36px, not
   40px). */
@media (max-width: 480px) {
  .portal-filter-more-toggle { min-height: 2.25rem; max-width: 9.5rem; }
  .portal-filter-clear { width: 2.5rem; height: 2.5rem; }
  .portal-filter-more-item { min-height: 3rem; }

  /* RAP-285 — give the chips the WHOLE width and let Más wrap beneath
     them. Sharing one line, the toggle takes ~91px of a 320px row, which
     leaves 229px — narrower than a single Spanish chip like
     "Obteniendo acceso (2)" (170px) plus anything else, so every line
     held exactly one chip and three of the five ended up in the menu.
     Full width fits two chips per line and keeps four of five on screen.

     Más stays OUTSIDE .portal-filter-chips (it wraps to its own line
     rather than joining the chip flow) because that container clips, and
     an absolutely-positioned dropdown inside it would be cut off — the
     same reason the RAP-125 markup put it there. */
  .portal-filter-row { flex-wrap: wrap; }
  .portal-filter-chips { flex: 1 1 100%; }

  /* The base rule anchors the dropdown `right: 0`, which was correct
     while Más sat at the RIGHT end of a single-line row. Wrapping puts it
     at the LEFT of its own line, and a right-anchored 14rem menu then
     opened to x=-123 — off the left edge of a 430px screen, with its
     first items unreachable. Anchor it to the edge the toggle is now on. */
  .portal-filter-more-menu { left: 0; right: auto; }
}

/* Not-available rows: dim + strike the address (pill stays solid red). */
.portal-property-unavailable { opacity: 0.62; }
.portal-property-unavailable .portal-property-address { text-decoration: line-through; }

/* "Getting access" subtext + plain-language access-type line. */
.portal-getting-access-sub { margin-top: 0.125rem; font-style: italic; }
.portal-access-hint {
  margin-top: 0.1875rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--st-amber, #f59e0b);
}

/* Amber tour / open-house badge (prominent, NOT red). */
.portal-tour-badge {
  display: inline-flex;
  align-items: center;
  margin-top: 0.25rem;
  padding: 0.1875rem 0.5rem;
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  font-weight: 700;
  background: rgba(245, 158, 11, 0.14);
  border: 1px solid rgba(245, 158, 11, 0.5);
  color: var(--st-amber, #f59e0b);
  /* RAP-285 — this badge WRAPS. It used to be `white-space: nowrap`,
     which made the whole open-house string one unbreakable box:
     "Casa abierta · 18 de agosto de 2026 · 5:00 PM–5:30 PM" measures
     368px, so on a 375px phone it painted to x=524 and dragged the
     whole page 149px wider than the screen. That is the sideways pan
     Mauricio saw — the Spanish date+time string is simply longer than
     any phone is wide, so no amount of room would have saved it.

     `min-width: 0` is the load-bearing half. This badge is a flex item
     of .portal-property-meta, and flex items default to
     `min-width: auto`, i.e. they refuse to shrink below their
     min-content width. With nowrap that min-content WAS the entire
     string, so the parent could not contain it however narrow the
     screen got. Wrapping alone fixes the common case; min-width:0 is
     what makes it impossible to reinstate the failure with a long
     single segment.

     overflow-wrap handles the pathological case of one unbreakable
     token (a date format with no spaces) rather than clipping it.
     All three are plain layout rules with no engine-specific
     behaviour. */
  white-space: normal;
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.portal-tour-badge-done {
  background: rgba(16, 185, 129, 0.12);
  border-color: rgba(16, 185, 129, 0.45);
  color: var(--st-green2, #10b981);
  font-weight: 600;
}

/* Detail status section extras. */
.portal-detail-status-sub { margin-top: 0.25rem; font-style: italic; }
.portal-detail-access-hint {
  margin-top: 0.375rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--st-amber, #f59e0b);
}
.portal-detail-status .portal-tour-badge { margin-top: 0.5rem; }
/* Off-market detail accent (red) — mirrors the recolored pill. */
.portal-detail-status-off-market { border-color: rgba(239, 68, 68, 0.5); background: rgba(239, 68, 68, 0.08); }

/* ── PHASE K.F: STATUS DASHBOARD ───────────────────── */

/* Top bar: logo + language toggle (list view) / back link + language toggle (detail).
   K.F.1 — center the logo. The toggle and (in detail view) the back
   link are positioned absolutely against the topbar, leaving the
   centered child (the logo on list view, empty space on detail view)
   to sit dead-center regardless of toggle width. */
.portal-topbar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
  min-height: 2.25rem;
}
.portal-topbar--brand {
  /* The navigation controls occupy the real top row; the centered wordmark
     starts beneath them so neither control floats beside the logo. */
  padding-top: 2.75rem;
}
.portal-topbar .client-brand {
  margin-bottom: 0;  /* override the 1.75rem default used on the login page */
}
.portal-lang-toggle {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.875rem;
  font-weight: 600;
}
.portal-topbar--brand .portal-lang-toggle {
  top: 0;
  transform: none;
}
.portal-back-host {
  position: absolute;
  left: 0;
  top: 0.25rem;
  max-width: calc(100% - 8.5rem);
}
.portal-back-host:empty { display: none; }
.portal-back-host .client-back-link {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.8125rem;
}
.portal-lang-btn {
  background: none;
  border: 0;
  padding: 0.25rem 0.5rem;
  color: var(--text-secondary);
  cursor: pointer;
  font: inherit;
  letter-spacing: 0.04em;
}
.portal-lang-btn.is-active {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.portal-lang-sep { color: var(--text-muted); }
.portal-back-link {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.875rem;
}
.portal-back-link:hover { color: var(--accent); }

/* Property card status badges */
.portal-property-link {
  display: flex;
  gap: 0.75rem;
  text-decoration: none;
  color: inherit;
  width: 100%;
  align-items: center;
}
.portal-property-mls { margin-top: 0.125rem; }
.portal-property-tour {
  display: inline-block;
  margin-top: 0.25rem;
  font-size: 0.8125rem;
  color: var(--text-secondary);
}
.portal-status-locked       { color: #10b981; border-color: rgba(16, 185, 129, 0.5); }
.portal-status-declined     { color: #ef4444; border-color: rgba(239, 68, 68, 0.5); }
.portal-status-unavailable  { color: #f59e0b; border-color: rgba(245, 158, 11, 0.5); }
.portal-status-pending      { color: var(--text-secondary); border-color: var(--border-strong); }

/* ── RAP-126: phone-width card balance ─────────────────────────────
   The three columns are thumb | body | status, and BOTH outer columns
   are flex-shrink:0 — so at 375px they took 96px + 105px of a 297px
   row and the address was left with ~50px, wrapping one word per line.
   Spanish made it worse: "Solicitud enviada" is a wider pill than
   "App submitted", so the status column demanded even more.

   The fix drops the status out of the horizontal contest: a 2-column
   grid where the badge sits BELOW the address in the same column, so
   the address gets the whole width the photo doesn't use (~167px, up
   from ~50px). Chosen over shrinking the thumbnail because the photo is
   how clients recognise a home in a list, and over letting the status
   column shrink because a wrapped pill reads worse than a wrapped
   address.

   MUST live below .portal-property-link above: the MOBILE TIGHTENING
   block sits earlier in this file, and at equal specificity source
   order decides — putting these there would silently lose (the same
   trap RAP-125 hit with its tap targets). */
@media (max-width: 480px) {
  .portal-property-link {
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
    grid-template-areas:
      "thumb body"
      "thumb status";
    align-content: start;
    align-items: start;
    gap: 0.25rem 0.625rem;
  }
  .portal-property-thumb  { grid-area: thumb; }
  .portal-property-body   { grid-area: body; }
  .portal-property-status { grid-area: status; }
}

/* Detail view */
.portal-detail-address {
  font-size: 1.25rem;
  margin: 0 0 0.25rem;
}
.portal-detail-section {
  margin-top: 1.25rem;
}
.portal-detail-section-title {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin: 0 0 0.5rem;
}
.portal-detail-status {
  padding: 0.875rem 1rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--bg-1);
}
.portal-detail-status-headline {
  font-weight: 600;
  font-size: 1rem;
}
.portal-detail-status-detail {
  margin-top: 0.25rem;
}
.portal-detail-status-locked       { border-color: rgba(16, 185, 129, 0.5); background: rgba(16, 185, 129, 0.08); }
.portal-detail-status-declined     { border-color: rgba(239, 68, 68, 0.5); background: rgba(239, 68, 68, 0.08); }
.portal-detail-status-unavailable  { border-color: rgba(245, 158, 11, 0.5); background: rgba(245, 158, 11, 0.08); }
.portal-detail-status-pending      { }

/* Canonical state styling on the detail page mirrors the list. */
.portal-detail-status-gray   { }
.portal-detail-status-yellow { border-color: rgba(234, 179, 8, 0.5); background: rgba(234, 179, 8, 0.08); }
.portal-detail-status-considering { border-color: rgba(59, 130, 246, 0.5); background: rgba(59, 130, 246, 0.08); }
.portal-detail-status-green  { border-color: rgba(16, 185, 129, 0.5); background: rgba(16, 185, 129, 0.08); }
.portal-detail-status-red    { border-color: rgba(239, 68, 68, 0.5); background: rgba(239, 68, 68, 0.08); }
.portal-detail-status-leased { border-color: rgba(245, 158, 11, 0.5); background: rgba(245, 158, 11, 0.08); }
.portal-detail-status-pill   { font-size: 0.875rem; padding: 0.25rem 0.625rem; }

.portal-detail-tour,
.portal-detail-fields {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.375rem;
  font-size: 0.9375rem;
}

.portal-detail-actions {
  display: flex;
  gap: 0.625rem;
  margin-top: 1.5rem;
  flex-wrap: wrap;
}
.portal-detail-actions button {
  flex: 1 1 160px;
  min-height: 48px;
  font-weight: 600;
}

/* Portal post-tour action buttons. All stack on .primary
   to inherit shape (border-radius, padding, font-size); these
   modifiers override only the background + hover so the choices
   are visually paired and color-coded to their target state. The
   green/red tokens match the K.F.3 status pill spec. */
.portal-action-approve {
  background: rgb(16, 185, 129);
}
.portal-action-approve:hover:not(:disabled) {
  background: rgb(5, 150, 105);
  color: white;
}
.portal-action-considering {
  background: rgb(180, 83, 9);
}
.portal-action-considering:hover:not(:disabled) {
  background: rgb(146, 64, 14);
  color: white;
}
.portal-action-decline {
  background: rgb(239, 68, 68);
}
.portal-action-decline:hover:not(:disabled) {
  background: rgb(220, 38, 38);
  color: white;
}

/* RAP-127 — the single decline control carries a one-line explanation of
   what it will actually do, because the same button means two different
   things depending on the state (end the interest now vs. ask staff to
   remove it). Stating it before the tap, not only in the modal. */
.portal-decline-sub {
  margin: 0.5rem 0 0;
  text-align: center;
  line-height: 1.4;
}
/* The notify-only states render one button alone, so it should not
   stretch the full width the way the paired approve/decline row does. */
.portal-detail-actions-single {
  flex-direction: column;
  align-items: stretch;
}
.portal-detail-actions-single .portal-decline-sub { margin-top: 0.125rem; }

/* ── K.F.5.B — ACCESS DETAILS SECTION ────────────────────────────
   Renders only when admin has opted in AND the interest is yellow
   AND lockbox info is set (server-side gating). Distinct subtle
   green tint draws the eye since this is the only actionable info
   on the page when ready-to-view. */
.portal-detail-access {
  border: 1px solid rgba(16, 185, 129, 0.4);
  background: rgba(16, 185, 129, 0.06);
  border-radius: var(--radius-md);
  padding: 0.875rem 1rem;
}
.portal-detail-access .portal-detail-section-title {
  color: rgb(16, 185, 129);
  margin-bottom: 0.625rem;
}
.portal-detail-access-fields {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 0.875rem;
  row-gap: 0.375rem;
  margin: 0;
  font-size: 0.9375rem;
}
.portal-detail-access-fields dt {
  color: var(--text-secondary);
  font-size: 0.8125rem;
  align-self: center;
}
.portal-detail-access-fields dd {
  margin: 0;
  color: var(--text-primary);
  word-break: break-word;
}
.portal-access-code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 0.0625rem 0.375rem;
  background: rgba(16, 185, 129, 0.16);
  color: rgb(16, 185, 129);
  border-radius: 4px;
}
/* UI/UX overhaul Increment C — free-text notes live inside the green
   "How to view this home" box, with a subtle divider so the free text and the
   structured rows read as one section rather than a jumble.

   RAP-128 flipped the order: "Showing details" (the free text) now comes FIRST
   and the structured rows follow, because the text says what to do and the rows
   are reference data. So the divider had to move to the other side of the pair —
   with only the old `fields + body` rule, the rows crowded straight up against
   the last line of the text with no gap at all. Both directions are declared so
   whichever order renders, the pair stays legible. */
.portal-detail-access .portal-detail-access-fields + .portal-detail-client-notes-body,
.portal-detail-access .portal-detail-client-notes-body + .portal-detail-access-fields {
  margin-top: 0.625rem;
  padding-top: 0.625rem;
  border-top: 1px solid rgba(16, 185, 129, 0.18);
}

@media (max-width: 480px) {
  .portal-detail-access { padding: 0.75rem 0.875rem; }
  .portal-detail-access-fields {
    grid-template-columns: 1fr;
    row-gap: 0.1875rem;
  }
  .portal-detail-access-fields dt { margin-top: 0.375rem; }
  .portal-detail-access-fields dt:first-child { margin-top: 0; }
}

/* Notes thread */
.portal-notes-thread {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.portal-notes-add-btn {
  align-self: flex-start;
}
.portal-notes-composer {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.portal-notes-textarea {
  width: 100%;
  min-height: 84px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-1);
  color: var(--text-primary);
  padding: 0.5rem 0.625rem;
  font: inherit;
  resize: vertical;
}
.portal-notes-composer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.portal-notes-composer-actions {
  display: flex;
  gap: 0.5rem;
}
.portal-notes-counter {
  font-variant-numeric: tabular-nums;
}
.portal-notes-error {
  color: var(--danger);
  font-size: 0.8125rem;
  margin: 0;
}
.portal-notes-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}
.portal-note {
  padding: 0.625rem 0.75rem;
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  border: 1px solid var(--border-strong);
}
.portal-note-client {
  background: rgba(99, 102, 241, 0.08);
  border-color: rgba(99, 102, 241, 0.3);
}
.portal-note-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
}
.portal-note-author {
  font-weight: 600;
  font-size: 0.875rem;
}
.portal-note-body {
  font-size: 0.9375rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.portal-notes-empty { margin: 0; }

/* Modal overlay (used by both lock-in and decline modals) */
.portal-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 200;
  animation: portal-fade-in .15s ease;
}
.portal-modal {
  background: var(--bg-0);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 1.25rem;
  max-width: 480px;
  width: 100%;
}
.portal-modal-title {
  font-size: 1.125rem;
  margin: 0 0 0.75rem;
}
.portal-modal-en-only {
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin: 0 0 0.75rem;
}
.portal-modal-intro {
  margin: 0 0 0.5rem;
}
.portal-modal-bullets {
  margin: 0 0 1rem;
  padding-left: 1.25rem;
  font-size: 0.9375rem;
}
.portal-modal-bullets li + li { margin-top: 0.5rem; }
.portal-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.625rem;
  flex-wrap: wrap;
}
.portal-modal-actions button {
  min-height: 44px;
  padding: 0.5rem 0.875rem;
  /* Overrides the global `button.primary { width: 100% }` — a login-form
     rule that also caught these. It forced the confirm button to fill the
     row, so it ALWAYS wrapped to its own line and left "Cancel" stranded
     above it, at every width. Auto width lets the pair sit side by side;
     the phone block below re-stretches them deliberately. */
  width: auto;
}

/* RAP-127 — at phone width the two buttons don't fit one line, and the
   flex-end + wrap left "Cancel" stranded above a full-width confirm.
   Now an explicit stack. column-reverse puts the destructive confirm on
   top and Cancel nearest the thumb, so the easy tap is the safe one.
   This modal is the decline confirmation for every state since the raw
   window.confirm() was retired, so it has to hold up at 375px. */
@media (max-width: 480px) {
  .portal-modal-actions {
    flex-direction: column-reverse;
    flex-wrap: nowrap;
  }
  .portal-modal-actions button { width: 100%; }
}

/* ─────────────────────────────────────────────────────────────────
   K.F.4.B — sticky failure banners (top of portal card)
   ───────────────────────────────────────────────────────────────── */

.portal-banner-stack {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0;
  margin: 0 0 1rem;
}
.portal-banner-stack:empty { margin: 0; }

.portal-banner {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.75rem 0.875rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  font-size: 0.9375rem;
  line-height: 1.4;
}
.portal-banner-info {
  border-color: rgba(59, 130, 246, 0.45);
  background: rgba(59, 130, 246, 0.10);
}
.portal-banner-warn {
  border-color: rgba(234, 179, 8, 0.5);
  background: rgba(234, 179, 8, 0.10);
}
.portal-banner-icon { font-size: 1.125rem; line-height: 1.2; }
.portal-banner-text { flex: 1; min-width: 0; }
.portal-banner-close {
  background: transparent;
  border: none;
  padding: 0 0.375rem;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 1.25rem;
  line-height: 1;
  flex-shrink: 0;
}
.portal-banner-close:hover { color: var(--text-primary); }
.portal-banner-more {
  text-align: center;
  border: none;
  background: transparent;
  padding: 0.25rem;
}

/* Upload-failure clarity (2026-09-13) — the failed screenshot sits in its
   banner; tap to see it full size. The viewer reuses .portal-modal-overlay. */
.portal-banner-thumb {
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-0);
  cursor: zoom-in;
}
.portal-banner-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.portal-banner-viewer { flex-direction: column; gap: 0.5rem; cursor: zoom-out; }
.portal-banner-viewer-img {
  max-width: min(96vw, 900px);
  max-height: 82vh;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  cursor: default;
}
.portal-banner-viewer-close {
  align-self: flex-end;
  background: transparent;
  border: none;
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.5rem;
}
