/* public/css/app.css
 *
 * Base styling for the DealDuo app frontend (index.html, login.html,
 * signup.html, dashboard.html). Brand colors match the main modelthedeal.com/DealDuo
 * marketing site. Intentionally simple — a real design pass comes later.
 */

:root {
  --navy: #153D64;
  --navy-dark: #0E2840;
  --light-blue: #E6F1FB;
  --bg: #F6F6F6;
  --white: #FFFFFF;
  --border: #D7DEE5;
  /* Matches modelthedeal.com's --grey-border exactly (css/global.css in the
     model-the-deal repo) -- used only on .site-header's border-bottom, to
     match /dealduo/'s header hairline. Distinct from --border above, which
     stays the softer grey this app already uses for cards/inputs. */
  --grey-border: #0E0E0E;
  --text: #1F2A33;
  --text-muted: #5A6B7A;
  --error: #B3261E;
  --error-bg: #FBEAEA;
  /* Amber/warning text token -- for states the user caused on purpose
     (approaching/exceeding an included quota they're paying to extend),
     not a fault. Measured at ~5.32:1 contrast (WCAG AA requires 4.5:1)
     against --bg (#F6F6F6) -- see .usage-indicator.is-attention below for
     why that's the background that matters here, not --navy. */
  --warning: #92590C;
  /* Green confirmation text token -- for a positive, non-blocking note (T12's
     whole-statement reconciliation success line; see .rr-reconcile-success
     below), same "measured contrast" discipline as --warning above. ~6.05:1
     against --bg (#F6F6F6), comfortably above WCAG AA's 4.5:1. */
  --success: #146C2E;
}

* {
  box-sizing: border-box;
}

/* html/body height + flex column below is the standard sticky-footer
   pattern: on short-content pages (e.g. the upload screen before a file
   is processed, or this page's own single-field form) it lets #footer's
   own margin-top:auto (see the Footer section) absorb the leftover
   viewport space, so the footer sits at the true bottom of the screen
   instead of riding up right after short content. min-height (not
   height) on body lets the page still grow taller than the viewport and
   scroll normally once real content (e.g. the review panel) exceeds it.

   Deliberately NOT giving <main> flex:1 here (tried first, reverted):
   flex:1 = flex-basis:0%, which stretches <main> itself to fill the
   leftover space -- and <main> isn't an inert spacer, it already carries
   page-specific layout rules (.auth-wrap-split's align-items:center on
   login.html/signup.html, previously a no-op with no extra height to
   distribute; .tool-panel's very different Rent Roll vs. T12 content
   heights on dashboard.html). Stretching it exposed both as a visible
   re-centered form / exaggerated per-tab reflow. Pushing #footer down
   via its own margin-top:auto instead leaves <main> sized to its natural
   content on every page, unchanged from before this footer work. */
html, body {
  height: 100%;
}

body {
  margin: 0;
  font-family: 'Outfit', -apple-system, sans-serif;
  color: var(--text);
  background: var(--bg);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a {
  color: var(--navy);
}

/* ---------------------------------------------------------------------
   Header -- navy background / white text, matching modelthedeal.com's main
   site header (as opposed to /dealduo/'s own standalone white header,
   which this used to pixel-match -- see git history). Sticky, 64px tall,
   same wordmark/nav/CTA sizing as before; every child rule below is
   recolored for contrast against --navy instead of --white.
--------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 64px;
  padding: 0 32px;
  background: var(--navy);
  border-bottom: 1px solid var(--navy-dark);
}

.brand {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
  min-width: 0;
}

.wordmark {
  font-size: 20px;
  font-weight: 700;
  color: var(--white);
  text-decoration: none;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.wordmark:hover {
  color: var(--light-blue);
}

.wordmark-sub {
  margin-top: 3px;
  font-size: 0.7rem;
  font-weight: 500;
  /* Muted white, not the marketing site's --text-muted grey (#767676) --
     that shade was picked for a white header and is unreadable on navy.
     0.7 opacity keeps it visibly secondary to the white wordmark above it. */
  color: rgba(255, 255, 255, 0.7);
  white-space: nowrap;
}

.wordmark-sub a {
  color: inherit;
  text-decoration: none;
}

.wordmark-sub a:hover {
  color: var(--white);
}

@media (max-width: 480px) {
  .wordmark-sub {
    display: none;
  }
}

.site-header nav {
  display: flex;
  align-items: center;
  gap: 20px;
}

/* Header wrap (primary narrow-width fix) -- below 480px (reusing
   .wordmark-sub's existing breakpoint above; also comfortably under 640px,
   the width where .rr-steps' own "position: sticky; top: 80px" rule
   applies -- see that rule's own responsive override, which flips it to
   position:static below 640px for an unrelated reason and happens to keep
   it inert everywhere this rule is active, so there's no interaction).
   flex-wrap lets nav drop to its own full-width row below .brand instead
   of squeezing into the leftover space beside it -- recovers far more
   width than shrinking anything. height:64px -> height:auto + min-height:
   64px is scoped to this breakpoint only (desktop's height:64px above is
   untouched) -- a fixed height can't grow to fit a wrapped second row;
   min-height keeps the single-brand-row case from ever going shorter than
   64px. flex-basis:100% is the standard flex-wrap idiom for "give this
   item its own row" -- nothing else present can share a row with
   something already claiming the full width. */
@media (max-width: 480px) {
  .site-header {
    flex-wrap: wrap;
    height: auto;
    min-height: 64px;
  }

  .site-header nav {
    flex-basis: 100%;
  }
}

.site-header nav a {
  text-decoration: none;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--white);
}

.site-header nav a:hover {
  color: var(--light-blue);
}

/* .btn-primary's own navy background (see Buttons section) would
   otherwise disappear against this header's matching navy -- flip it to a
   white pill with navy text, the same reversal .btn-secondary already
   uses everywhere else. */
.site-header nav a.btn-primary {
  background: var(--white);
  color: var(--navy);
  border-color: var(--white);
}

.site-header nav a.btn-primary:hover {
  background: var(--light-blue);
  color: var(--navy);
}

/* .btn (not just .btn-primary) so dashboard.html's Log Out (.btn-secondary)
   gets the same header-scaled CTA sizing as signup.html's Sign Up. */
.site-header nav .btn {
  padding: 8px 18px;
  font-size: 0.85rem;
}

.user-greeting {
  /* Muted white, not --text-muted -- see .wordmark-sub above for why. */
  color: rgba(255, 255, 255, 0.7);
  font-size: 14px;
  margin-right: 4px;
}

/* Hides below the width where "Welcome back, <name>" stops fitting
   alongside the rest of nav -- measured against the widest real
   combination (trial state: this greeting + .usage-indicator +
   #header-upgrade-link + #logout-btn), ~678px needed with the greeting
   present; see commit notes for the full measurement. Distinct from (and
   wider than) .usage-indicator's own breakpoint below -- this is the
   first element to go: largest, least functionally important, and the
   only unbounded one (falls back to a full email address when no first
   name is on record). display:none, not visibility/opacity, so its
   layout space and the nav's flex gap around it both collapse. */
@media (max-width: 680px) {
  .user-greeting {
    display: none;
  }
}

.usage-indicator {
  color: var(--text-muted);
  font-size: 12px;
  padding: 4px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  white-space: nowrap;
}

.usage-indicator:empty {
  display: none;
}

/* Attention state (over-quota / at-cap) -- toggled by
   js/usage-display.js's `level` via dashboard.html's refreshUsageStatus().
   Uses --warning, not --error: this state means the subscriber is using or
   extending the plan they bought, not that anything is broken -- --error's
   red would read as a fault. Text color only, against .usage-indicator's
   own background (--bg, #F6F6F6 -- the light pill, not .site-header's navy
   behind it), which is what --warning was contrast-checked against.
   Settings.html never applies this class -- that page stays neutral by
   design. */
.usage-indicator.is-attention {
  color: var(--warning);
}

/* Pill wrapping (safety net) + a modest horizontal padding trim -- same
   480px breakpoint as .site-header's wrap above, so this only ever applies
   once nav already has its own full-width row to work with. white-space:
   normal (not nowrap's absence -- a media query can only override with a
   value, not remove a declaration) lets the pill wrap across 2 lines
   rather than overflow/clip if its text still doesn't fit the row at 1
   line; desktop's nowrap above is untouched, so the pill never wraps on
   wide screens. Padding trimmed horizontally only (10px -> 6px each
   side) -- vertical padding is left alone since it doesn't affect how
   much text fits per line, and font-size is deliberately not touched here:
   12px is already near the floor for a digit-heavy billing indicator ("1"
   vs "7" vs "4" needs to stay unambiguous), so width is recovered by
   giving the pill room to wrap, not by shrinking the text further. */
@media (max-width: 480px) {
  .usage-indicator {
    white-space: normal;
    padding: 4px 6px;
  }
}

/* Header "Upgrade" link -- sits next to the usage indicator, hidden until
   dashboard.html's quota-indicator script unhides it for a trial-state user
   (active or exhausted); hidden for every subscribed tier, which now manage
   their plan via Settings instead. Plain <a> to /upgrade.html, no
   dropdown/menu (the old Investor/Underwriter "Manage Plan" menu that used
   to live here is gone). Text-link styling (not .btn) to stay visually
   secondary to Log Out. */
.header-upgrade-btn {
  color: var(--white);
  font-size: 12px;
  font-weight: 600;
  text-decoration: underline;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.header-upgrade-btn:hover {
  color: var(--light-blue);
}

/* ---------------------------------------------------------------------
   Support CTA -- small, paying-tier-only note directly below the Rent Roll
   upload dropzone (rr-main-col, not a page footer). See dashboard.html's
   quota-indicator script for the show/hide logic (usage.state === 'subscribed').
   text-align: center here centers it within rr-main-col's width, same
   convention as .upload-dropzone/.coming-soon -- max-width/margin: auto are
   inert at that column width but kept in case this is ever reused somewhere
   wider.
--------------------------------------------------------------------- */
.support-cta {
  max-width: 860px;
  margin: 32px auto 0;
  padding: 0 24px;
  font-size: 12px;
  color: var(--text-muted);
  text-align: center;
}

.support-cta a {
  color: var(--text-muted);
  text-decoration: underline;
}

.support-cta a:hover {
  color: var(--navy);
}

/* ---------------------------------------------------------------------
   Buttons
--------------------------------------------------------------------- */
.btn {
  display: inline-block;
  font-size: 0.93rem;
  font-weight: 600;
  padding: 11px 24px;
  border-radius: 4px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
}

.btn-primary {
  background: var(--navy);
  color: var(--white);
  border-color: var(--navy);
}

.btn-primary:hover {
  background: var(--navy-dark);
}

.btn-secondary {
  background: var(--white);
  color: var(--navy);
  border-color: var(--navy);
}

.btn-secondary:hover {
  background: var(--light-blue);
}

.btn-google {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--white);
  color: var(--text);
  border-color: var(--border);
  width: 100%;
}

.google-icon {
  flex-shrink: 0;
}

.btn-google:hover {
  background: var(--bg);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Inline loading spinner for buttons (signup/login submit + Google OAuth
   buttons in login.html/signup.html) -- same ring style and rr-spin animation as the
   rent roll tool's full-size .spinner (below, under "Rent Roll tool"),
   just small enough to sit inline in button text. Default coloring matches
   .btn-google's white background; .btn-primary's navy background needs a
   lighter ring, handled by the override underneath. */
.btn-spinner {
  display: inline-block;
  width: 15px;
  height: 15px;
  margin-right: 8px;
  vertical-align: -3px;
  border: 2px solid var(--border);
  border-top-color: var(--navy);
  border-radius: 50%;
  animation: rr-spin 0.8s linear infinite;
}

.btn-primary .btn-spinner {
  border-color: rgba(255, 255, 255, 0.4);
  border-top-color: var(--white);
}

/* ---------------------------------------------------------------------
   Hero / marketing (index.html)
--------------------------------------------------------------------- */
.hero {
  max-width: 720px;
  margin: 0 auto;
  padding: 80px 24px 48px;
  text-align: center;
}

.hero h1 {
  font-size: 40px;
  line-height: 1.15;
  color: var(--navy);
  margin: 0 0 16px;
}

.hero p {
  font-size: 18px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 0 0 32px;
}

.tools-section {
  max-width: 820px;
  margin: 0 auto;
  padding: 0 24px 96px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

@media (max-width: 640px) {
  .tools-section {
    grid-template-columns: 1fr;
  }
}

.tool-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px;
  text-align: left;
}

.tool-card h3 {
  margin: 0 0 8px;
  color: var(--navy);
  font-size: 18px;
}

.tool-card p {
  margin: 0 0 12px;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.5;
}

.badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
}

.badge-live {
  background: var(--light-blue);
  color: var(--navy);
}

.badge-soon {
  background: var(--bg);
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------
   Auth pages (login.html / signup.html)
--------------------------------------------------------------------- */
.auth-wrap {
  /* Same fix as .dashboard-main above, same reason: this flex item's
     margin:auto centering suppresses body's align-items:stretch, so it
     was shrink-to-fit sizing instead of filling-then-capping. Measured
     363px instead of 400px on reset-password.html's plain .auth-wrap;
     login.html/signup.html's .auth-wrap-split happened to render at the
     correct 960px already (display:grid sizes differently here) but gets
     the same explicit width for consistency/robustness. */
  width: 100%;
  max-width: 400px;
  margin: 56px auto;
  padding: 0 24px;
}

.auth-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 40px;
}

/* Split-screen layout (login.html/signup.html only) -- .auth-wrap's own
   max-width/margin stay as the shared base (reset-password.html uses plain
   .auth-wrap, single column, unaffected by this). ".auth-wrap-split" widens
   the container and lays .auth-card + .auth-visual-panel side by side. */
.auth-wrap-split {
  max-width: 960px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: center;
}

.auth-visual-panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.auth-trust-lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.auth-trust-lines li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 14px;
  line-height: 1.4;
  color: var(--text-muted);
}

.auth-trust-lines li::before {
  content: '\2713';
  flex-shrink: 0;
  color: var(--navy);
  font-weight: 700;
}

/* Matches .tools-section's existing single-column breakpoint convention.
   Form-only on narrow viewports -- no visual panel, not even stacked below. */
@media (max-width: 640px) {
  .auth-wrap-split {
    display: block;
    max-width: 400px;
  }

  .field-row {
    flex-direction: column;
    gap: 14px;
  }

  .auth-visual-panel {
    display: none;
  }
}

.auth-card-title {
  margin: 0 0 24px;
  color: var(--navy);
  font-size: 24px;
  text-align: center;
}

.auth-card-footnote {
  margin: 16px 0 0;
  font-size: 13px;
  text-align: center;
}

/* Showcase panel content (login.html / signup.html's .auth-visual-panel) */
.auth-showcase-headline {
  margin: 0;
  color: var(--navy);
  font-size: 22px;
  font-weight: 700;
  line-height: 1.3;
}

.auth-showcase-logos-label {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.pms-logo-strip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pms-logo-chip {
  padding: 6px 12px;
  background: var(--light-blue);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--navy);
}

.auth-metrics-row {
  display: flex;
  gap: 24px;
}

.auth-metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.auth-metric-value {
  font-size: 22px;
  font-weight: 700;
  color: var(--navy);
}

.auth-metric-label {
  font-size: 12px;
  color: var(--text-muted);
}

.auth-form {
  display: none;
  flex-direction: column;
  gap: 18px;
}

.auth-form.active {
  display: flex;
}

.field-row {
  display: flex;
  gap: 14px;
}

.field-row .field {
  flex: 1;
  min-width: 0;
}

.field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 4px;
  color: var(--text);
}

/* Visually hides an element while keeping it in the accessibility tree --
   standard sr-only pattern (not display:none, which screen readers skip). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Terms/Privacy checkbox (signup.html only) -- overrides .field label's
   block/bold/margin styling above (same specificity, wins on source order)
   since this label wraps an inline checkbox + sentence, not a text input. */
.checkbox-field {
  padding: 10px 0;
}

.checkbox-field label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 400;
  margin-bottom: 0;
  color: var(--text-muted);
  cursor: pointer;
}

.checkbox-field input[type='checkbox'] {
  /* .field input (below) sets width:100%/padding/border/border-radius for
     text inputs -- reset all of those back to native checkbox rendering
     before applying this element's own small set of overrides. */
  width: 16px;
  height: 16px;
  padding: 0;
  border: revert;
  border-radius: 0;
  font-size: inherit;
  flex-shrink: 0;
  margin: 0;
  accent-color: var(--navy);
  cursor: pointer;
}

/* Opts out of .field input:focus's navy outline box below -- appropriate
   for text inputs, but on this small 16px checkbox it renders as an
   oversized box around the checkbox that reads like a rendering glitch.
   The native checked-state fill (accent-color above) is signal enough. */
.checkbox-field input[type='checkbox']:focus {
  outline: none;
}

/* Overage confirmation checkbox (RR/T12 both -- dashboard.html's
   applyOverageConfirmGate()) -- reuses .checkbox-field above (signup.html's
   Terms/Privacy checkbox) for the input/label/focus treatment rather than
   inventing new checkbox styling; this is the one override its longer,
   templated copy needs. align-items:flex-start (not .checkbox-field
   label's own center) so the checkbox aligns with the first line of text,
   not the vertical middle of a wrapped multi-line block -- this copy is
   confirmed multi-line at narrow widths, unlike signup.html's single-line
   use. text-align:left counters .upload-dropzone's own text-align:center,
   which would otherwise center each wrapped line individually -- ragged-
   center reads far worse than ragged-right for multi-line body copy. */
.rr-overage-confirm label {
  align-items: flex-start;
  text-align: left;
}

.field input {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 15px;
}

/* Lighter than var(--text-muted)/#767676 (both used elsewhere for
   lower-emphasis text) -- placeholder copy needs to read as more washed-out
   still. Same blue-grey family as --text-muted (#5A6B7A) and --border
   (#D7DEE5), just lighter, rather than introducing an unrelated grey. */
.field input::placeholder {
  color: #9AA5B1;
}

.field input:focus {
  outline: 2px solid var(--navy);
  outline-offset: 1px;
}

.link-button {
  background: none;
  border: none;
  color: var(--navy);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  text-align: left;
  text-decoration: underline;
}

/* "Forgot password?" reads as a low-emphasis aside rather than a primary
   action, unlike "Back to log in" (still var(--navy) via .link-button
   above) -- scoped to the id so only this one link is affected. */
#forgot-password-link {
  color: #767676;
}

#forgot-password-link:hover {
  color: var(--navy);
}

.divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-muted);
  font-size: 12px;
  margin: 8px 0;
}

.divider::before,
.divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.error-message {
  display: none;
  background: var(--error-bg);
  color: var(--error);
  border: 1px solid var(--error);
  border-radius: 6px;
  padding: 10px 12px;
  font-size: 14px;
}

.error-message.visible {
  display: block;
}

.info-message {
  display: none;
  background: var(--light-blue);
  color: var(--navy);
  border-radius: 6px;
  padding: 10px 12px;
  font-size: 14px;
}

.info-message.visible {
  display: block;
}

/* Vertical gap between stacked .info-message boxes -- dashboard.html's
   buildWarningsHtml() (Rent Roll review screen's Structural Warnings
   section) reuses .info-message as-is for each individual warning rather
   than inventing a new callout style, but .info-message itself was written
   as a single standalone banner (login/signup inline notices) with no
   spacing rule for repeating it in a list. Scoped to this one container so
   it can't affect any of .info-message's other, single-instance uses. */
.rr-warnings-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
}

/* buildWarningsHtml()'s closed-by-default disclosure -- native <details>/
   <summary>, so only cursor affordance is needed here; expand/collapse
   itself is the browser's own behavior, no JS state. */
.rr-warnings-disclosure summary {
  cursor: pointer;
}

/* Body copy inside #signup-pending (signup.html) -- explicit margin:0
   since .auth-form's flex gap (18px) already spaces these paragraphs;
   relying on the browser's default <p> margin would double up against
   that gap. */
#signup-pending p {
  margin: 0;
  font-size: 14px;
  color: var(--text);
  line-height: 1.5;
}

/* Unconfirmed-email recovery state (login.html's #login-unconfirmed-box) --
   reuses .error-message's box styling (background/border/color/padding);
   this id-scoped override switches display:block to a flex column once
   .visible so the paragraph/button/status line get consistent gap
   spacing. No explicit width rule needed on the button/status line --
   they stretch full width from the same align-items:stretch flex default
   .auth-form already relies on elsewhere in this file. */
#login-unconfirmed-box.visible {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

#login-unconfirmed-box p {
  margin: 0;
}

/* #login-resend-status sits inside #login-unconfirmed-box, itself a
   .error-message box -- without this, toggling error-message/info-message
   on the status line (login.html's showLoginResendStatus()) renders a
   second bordered/padded box nested inside the first. Strips box chrome
   only; color still comes from whichever of error-message/info-message is
   currently applied (var(--error) or var(--navy)), so text color stays
   the one visible difference between the two states. */
#login-resend-status {
  background: none;
  border: none;
  padding: 0;
  border-radius: 0;
}

/* welcome.html -- reuses .auth-card/.auth-wrap/.btn-primary/.auth-trust-lines
   as-is; these are the only new rules the page needs. */
.welcome-subline {
  margin: 0 0 24px;
  text-align: center;
  font-size: 15px;
  color: var(--text-muted);
}

.welcome-trial-block {
  margin: 0 0 28px;
  padding: 16px;
  background: var(--light-blue);
  border-radius: 6px;
}

.welcome-trial-heading {
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--navy);
}

.welcome-cta {
  display: block;
  width: 100%;
  text-align: center;
}

/* ---------------------------------------------------------------------
   Dashboard (dashboard.html)
--------------------------------------------------------------------- */
/* .page-shell: <header> (full width, pinned top) -> .app-shell (the
   sidebar/content row, filling whatever vertical space is left) -> #footer
   (full width, pinned bottom) -- a flex column, so the header and footer
   are genuinely OUTSIDE the sidebar/content split rather than scoped to
   (or squeezed by) either column inside it. Collapsing/expanding the
   sidebar only ever changes .app-shell's internal row distribution
   (#ds-sidebar's width vs. .dashboard-main's) -- it can't touch header/
   footer at all now, since they simply aren't part of that row.

   This is a NEW, dashboard.html-local wrapper rather than repurposing
   <body> itself for this role -- <body>'s own flex-column +
   min-height:100vh sticky-footer setup (see the html/body comment near the
   top of this file) is shared by every other page (login/signup/index), so
   it can't be conditionally pinned to 100vh just for this one; .page-shell
   mirrors that exact pattern one level down, scoped to dashboard.html
   alone. min-height (not height) by default so a caller with no sidebar
   renders exactly like a normal page -- <body>'s own sticky-footer setup,
   one level up, still does the real work for that case, unaffected. */
.page-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* Once a sidebar is actually present -- #ds-sidebar's own [hidden]
   attribute (toggled directly by showSidebar() in the sidebar <script>
   block) is the single source of truth here, not a second JS-added class --
   pin this shell to the viewport instead of letting it grow with content,
   so .app-shell below can become the one thing that scrolls internally
   between a header and footer that both stay fixed in place. */
.page-shell:has(#ds-sidebar:not([hidden])) {
  height: 100vh;
  overflow: hidden;
}

/* The sidebar/content row -- exactly two columns, #ds-sidebar and
   .dashboard-main, nothing else (no header/footer inside it anymore).
   Natural (shrink-to-fit) height by default, same as any other block
   sitting between a header and a margin-top:auto footer; only becomes a
   bounded-height scroll container once .page-shell above is pinned to the
   viewport. */
.app-shell {
  display: flex;
  align-items: stretch;
}

.page-shell:has(#ds-sidebar:not([hidden])) .app-shell {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.dashboard-main {
  /* flex:1 + min-width:0 is the standard "fill the remaining row space
     next to a fixed-width sibling" pair -- .app-shell is always
     display:flex (whether or not #ds-sidebar is visible), so without this,
     .dashboard-main would default to shrink-to-fit its own content instead
     of filling the space beside the sidebar. */
  flex: 1;
  min-width: 0;
}

/* Once the shell above is pinned to the viewport, .dashboard-main is the
   one piece that scrolls -- .site-header/#footer, both outside .app-shell
   entirely now, stay fixed at their natural size regardless. */
.page-shell:has(#ds-sidebar:not([hidden])) .dashboard-main {
  overflow-y: auto;
}

.dashboard-content {
  /* Carries the width/max-width/margin/padding .dashboard-main used to own
     directly (see that rule) -- unconditionally, not just when a sidebar is
     present, so a caller who never gets one still gets the exact same
     860px centered column as before. */
  width: 100%;
  max-width: 860px;
  margin: 0 auto;
  padding: 32px 24px 96px;
}

/* A genuine full-height left rail (Claude.ai's chat sidebar), not a card
   sitting in the page: a normal (non-fixed) flex item of .app-shell,
   stretched to that row's full height by align-items:stretch -- no
   position:fixed/z-index needed. No border-radius/box-shadow/outer margin
   -- this is app chrome, not a content card; the single border-right is
   the only separation from .dashboard-main, matching a rail/content
   divider rather than a boxed-card outline. No collapsed state, no
   toggle, no width transition (removed; see dashboard.html's sidebar
   <script> block, which no longer has any collapse/expand state machine
   or localStorage key either).

   width: roughly 20% of the viewport rather than a fixed pixel value, so
   the rail actually makes use of extra room on wider screens instead of
   staying pinned at a phone-era width -- min-width floors it at the old
   fixed value (260px) so it can only ever get WIDER than before this
   pass, never narrower, on any viewport down to the 640px breakpoint
   where it switches to width:100% instead (see that media query below).
   max-width caps it at 380px so it doesn't balloon on very large/ultrawide
   monitors -- .dashboard-main's own flex:1/min-width:0 (Dashboard section
   above) is what keeps .dashboard-content from ever overflowing regardless
   of how much width this rail actually claims. */
.ds-sidebar {
  flex-shrink: 0;
  width: 20vw;
  min-width: 260px;
  max-width: 380px;
  /* A barely-perceptible step off the dashboard's own --bg (the page/
     main-content background) rather than plain white, so the rail reads as
     its own chrome region next to .dashboard-content without becoming a
     visibly distinct gray block -- 1% black mixed in lands at ~#F3F3F3,
     just a hair off --bg's #F6F6F6, even more subtle than the previous
     pass's 2%. Still a step off an existing token, not a new hex value,
     per the same "reuse an existing token" discipline as the rest of this
     file's color choices. */
  background: color-mix(in srgb, var(--bg) 99%, black);
  border-right: 1px solid var(--border);
}

/* height/overflow-y: .ds-sidebar is stretched to .app-shell's own height
   (see that rule in the Dashboard section above) -- which, now that
   .site-header/#footer sit outside .app-shell in .page-shell instead of
   inside it, is 100vh MINUS the header's and footer's heights, not the
   full 100vh. 100% (of .ds-sidebar's own, now-shorter stretched height) is
   what's actually correct here -- a hardcoded 100vh would run taller than
   .ds-sidebar itself and overflow past the rail's real bounds. Without
   this, a property list taller than that available height would otherwise
   just get clipped with no way to reach the rest of it; scoping the scroll
   to this panel instead of the rail keeps that from happening. */
.ds-sidebar-panel {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  /* Top padding used to be 0 -- the collapse toggle (removed) sat here and
     supplied its own visual breathing room above "Saved Properties" via its
     own margin. With the toggle gone, that top edge had nothing left to
     hold it away from the header divider. 16px matches the left/right/
     bottom padding already on this rule (and the same figure
     .ds-sidebar/.ds-new-property-input's own comments already use for this
     component's spacing scale) rather than picking a new value. */
  padding: 16px;
  /* Promotes this to a flex column so .ds-account-link below can pin
     itself to the bottom via margin-top:auto -- .ds-sidebar-header/
     #ds-sidebar-body stay stacked in the same document order as before,
     so switching to flex here doesn't change their existing layout, it
     just adds the flex context the new element needs. */
  display: flex;
  flex-direction: column;
}

.ds-sidebar-header {
  padding-bottom: 8px;
}

.ds-sidebar-header h3 {
  margin: 0;
  font-size: 14px;
  color: var(--navy);
}

.ds-sidebar-body {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ds-empty-note {
  margin: 0;
  font-size: 12px;
  color: var(--text-muted);
}

/* Non-entitled sidebar content (Pay Per Doc/trial/the no-cap fallback --
   see renderUpgradeNote() in dashboard.html) -- replaces the saved-files
   list for those states. Deliberately understated: this is the only place
   in the app a paying customer is told they're missing something, so no
   lock icon, no badge, no background tint, no color beyond what's already
   used for quiet secondary text elsewhere. Box model copied from
   .ds-empty-note directly above (margin:0, font-size:12px,
   color:var(--text-muted)) -- same "quiet note where a list would
   otherwise go" role, just linked. Link styling copied from .support-cta a
   (this file's existing muted-link-with-underline convention, the other
   place a paying tier gets a quiet informational note) rather than
   inventing a new link treatment -- underline is the only affordance this
   is clickable, no color change until hover. */
.ds-upgrade-note {
  margin: 0;
  font-size: 12px;
  color: var(--text-muted);
}

.ds-upgrade-note a {
  color: var(--text-muted);
  text-decoration: underline;
}

.ds-upgrade-note a:hover {
  color: var(--navy);
}

/* "+ New Property" row, above the list -- .ds-sidebar-body's own
   flex-column gap:4px handles the spacing above/below this, and
   .ds-property's base border-top (below) becomes a natural divider under it
   once a real property row exists, since that row is no longer the DOM's
   :first-child with this button ahead of it. */
.ds-new-property-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 4px 0;
  background: none;
  border: none;
  font-size: 13px;
  font-weight: 600;
  color: var(--navy);
  cursor: pointer;
  text-align: left;
}

.ds-new-property-btn:hover {
  color: var(--navy-dark);
}

/* flex-wrap:wrap + .ds-new-property-input's min-width (below) together are
   the "wrap Save/Cancel below the input if they don't fit" fallback --
   .ds-sidebar's own min-width (260px, see that rule) minus
   .ds-sidebar-panel's 16px each side padding is the narrowest this row
   ever gets (228px usable), and it only widens from there since the rail
   itself is never narrower than that. A ~50px "Save" plus a ~65px
   "Cancel" (longer label, same padding/font) plus this 6px gap twice
   still leaves the input comfortably above its own 70px floor at that
   narrowest width, so this fallback only matters if that math changes
   later (wider labels, extra padding) rather than in any of today's
   actual viewport widths. */
.ds-new-property-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 2px 0;
}

.ds-new-property-input {
  flex: 1;
  min-width: 70px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--white);
}

.ds-new-property-input:focus {
  outline: 2px solid var(--navy);
  outline-offset: 1px;
}

/* "+ New Property"'s confirm control -- a text "Save" button, not the
   checkmark icon this used to be, sized for the sidebar's own compact
   scale rather than reused from .btn/.btn-primary (those are sized for
   full-width page buttons: 11px/24px padding alone wouldn't fit this
   228px-wide row next to the input). Colors still match .btn-primary
   (navy/white) rather than inventing a new palette. */
.ds-save-btn {
  flex-shrink: 0;
  padding: 5px 12px;
  border: none;
  border-radius: 6px;
  background: var(--navy);
  color: var(--white);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.ds-save-btn:hover {
  background: var(--navy-dark);
}

.ds-save-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Same override .btn-primary .btn-spinner already applies (app.css's
   Buttons section) -- a light ring for .btn-spinner's default dark-on-light
   coloring to read against this button's own navy background, reused here
   since .ds-save-btn isn't a .btn-primary itself (see that rule's comment
   on why). */
.ds-save-btn .btn-spinner {
  border-color: rgba(255, 255, 255, 0.4);
  border-top-color: var(--white);
}

/* Inline rename input, and inline delete confirm -- property/document rows
   (renameProperty()/deleteProperty()/renameDocument()/deleteDocument() in
   the sidebar <script> block), replacing window.prompt()/window.confirm().
   .ds-inline-input is deliberately its own class rather than sharing
   .ds-new-property-input (visually identical, same rail-compact scale) --
   keeps this change from having to touch the "+ New Property" row's own
   markup/CSS at all. .ds-save-btn (above) IS reused as-is for this row's
   own Save/Yes button, since that's genuinely the same control appearing
   in a second place, not a case of editing one feature to fix another. */
.ds-inline-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 4px 0;
}

.ds-inline-input {
  flex: 1;
  min-width: 70px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--white);
}

.ds-inline-input:focus {
  outline: 2px solid var(--navy);
  outline-offset: 1px;
}

.ds-cancel-btn {
  flex-shrink: 0;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: none;
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.ds-cancel-btn:hover {
  background: var(--bg);
  color: var(--text);
}

.ds-inline-confirm {
  padding: 4px 0;
}

.ds-inline-confirm-text {
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--text);
}

/* Destructive variant of .ds-save-btn for the delete confirm's "Yes" --
   reuses --error (this file's existing error/destructive token, see
   .error-message) rather than a new red. opacity on hover instead of a
   hand-picked darker red, for the same "don't invent a new color value"
   reason -- there's no existing darker-error token to step off of. */
.ds-danger-btn {
  background: var(--error);
}

.ds-danger-btn:hover {
  opacity: 0.85;
}

.ds-property {
  padding: 6px 0;
  border-top: 1px solid var(--border);
}

.ds-property:first-child {
  padding-top: 0;
  border-top: none;
}

.ds-property-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

.ds-property-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  background: none;
  border: none;
  padding: 4px 0;
  cursor: pointer;
  text-align: left;
}

.ds-caret {
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: 11px;
}

.ds-property-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

/* Hidden until the row is hovered (or an action button inside it holds
   keyboard focus, via :focus-within -- so Tab navigation doesn't skip past
   controls a mouse user would see). Same treatment at both the property
   row (.ds-property-header) and document row (.ds-doc-row) level. */
.ds-item-actions {
  flex-shrink: 0;
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 0.1s ease;
}

.ds-property-header:hover .ds-item-actions,
.ds-doc-row:hover .ds-item-actions,
.ds-item-actions:focus-within {
  opacity: 1;
}

/* Inline SVG (pencil/trash) using currentColor, not a new icon font/library
   -- consistent with this file's existing entity-based icons (▾/▸ carets,
   the ◂ sidebar toggle above) rather than introducing a dependency. */
.ds-icon-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: none;
  border: none;
  border-radius: 4px;
  color: var(--text-muted);
  cursor: pointer;
}

.ds-icon-btn:hover {
  background: var(--bg);
  color: var(--navy);
}

.ds-icon-btn svg {
  width: 13px;
  height: 13px;
}

.ds-subfolders {
  margin: 4px 0 6px 17px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ds-subfolder-label {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
}

.ds-doc-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 4px 0;
}

.ds-doc-link {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
}

.ds-doc-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 140px;
  font-size: 12px;
  color: var(--text);
}

.ds-doc-link:hover .ds-doc-name {
  color: var(--navy);
  text-decoration: underline;
}

.ds-doc-date {
  font-size: 11px;
  color: var(--text-muted);
}

/* Account entry point, bottom of the sidebar panel -- see dashboard.html's
   comment at #ds-account-link for why this is a plain link (matches
   settings.html/upgrade.html's own "Back to..." navigation links) rather
   than a button+JS action like "+ New Property" (an in-page action, not
   navigation). margin-top:auto pins it to the bottom of
   .ds-sidebar-panel's flex column regardless of how many properties are
   listed above -- when the list is short there's genuine leftover space to
   push into; when it's long enough to fill/exceed the panel's scrollable
   height, this instead sits at the bottom of the scrollable content
   (reachable by scrolling), not fixed to the viewport edge. At the mobile
   breakpoint .ds-sidebar-panel's height resolves to auto, so there's no
   leftover space there either -- this just sits directly below the list,
   expected on a narrow screen with no real "bottom" to pin against. */
/* Now a <button> (opens the account popover below) rather than a plain
   <a href="/settings.html"> -- no selector here needed rewriting for that
   (nothing in this file ever qualified .ds-account-link/.ds-account-avatar/
   .ds-account-label with `a.`, so every rule below still matches). What DID
   need adding: an anchor has no default background/border/font-family/
   cursor to fight, a <button> does, and this rule was relying on that up to
   now (only text-decoration/color were ever set explicitly, both of which
   an <a> also needs but a <button> doesn't -- kept anyway, harmless).
   background/border/font/cursor/text-align reset the button's UA defaults;
   border-top is re-declared after border:none so only the divider survives;
   width:100% matches .ds-new-property-btn's own reset above for the same
   "full-width sidebar row" reason. */
.ds-account-link {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin: auto 0 0;
  padding: 12px 0 0;
  background: none;
  border: none;
  border-top: 1px solid var(--border);
  font: inherit;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  color: var(--text);
}

.ds-account-link:hover .ds-account-label {
  color: var(--navy);
}

.ds-account-avatar {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--navy);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
}

.ds-account-label {
  font-size: 13px;
  font-weight: 500;
}

/* Account popover -- anchored to #ds-account-link (the sidebar avatar
   button above), positioned via JS (left/bottom set at open-time from the
   trigger's own getBoundingClientRect(), see dashboard.html's account-
   popover IIFE) rather than in CSS, since the trigger's viewport position
   depends on how far the saved-properties list has scrolled. bottom
   (not top) so the panel grows upward from the trigger without needing to
   know its own height in advance -- it sits at the bottom of the sidebar
   panel, so upward is the only direction with reliable room. Sets its own
   display:flex, which -- same as any author rule with equal-or-higher
   specificity than the UA [hidden] { display: none } rule -- would
   otherwise override [hidden] and defeat it; the dedicated
   [hidden] override directly below restores it. No existing modal/
   dropdown/popover component to reuse (the old paywall modal's CSS was
   fully removed, see this file's other comments citing it) or box-shadow
   token to step off of (none defined in :root) -- this is genuinely new
   chrome, built from existing color tokens (--white/--border) only. */
.ds-account-popover {
  position: fixed;
  z-index: 20;
  width: 260px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
}

.ds-account-popover[hidden] {
  display: none;
}

.ds-account-popover-email {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  overflow-wrap: break-word;
}

.ds-account-popover-plan {
  margin: 0;
  font-size: 12px;
  color: var(--text-muted);
}

.ds-account-popover-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 4px;
}

/* .btn/.btn-primary/.btn-secondary reused as-is (see app.css's Buttons
   section) rather than a new button style -- this is just the "stack full-
   width in a narrow column" override every other .btn use in this file
   already gets implicitly from its own layout context (e.g. .btn-google's
   own width:100%, same reasoning). */
.ds-account-popover-actions .btn {
  width: 100%;
}

/* #ds-account-manage-billing-btn is hidden by default (markup) and toggled
   via its [hidden] property/attribute by renderAccountPopover() -- but .btn
   above sets its own `display: inline-block`, an author-origin declaration
   that beats the browser's [hidden] { display: none } UA rule outright
   regardless of specificity (same cascade fact already documented at
   #rr-dropzone-staged/#t12-dropzone-staged's own comment further down this
   file), so [hidden] alone was never enough to actually hide it -- it was
   rendering visible for every usage state, including trial, from the very
   first paint. Scoped to this one id (not a rule on .btn itself, which
   settings.html's own identically-shaped Manage Billing button also
   depends on and is out of scope here) so this can't change anything
   outside this button. */
#ds-account-manage-billing-btn[hidden] {
  display: none;
}

@media (max-width: 640px) {
  /* Below this width the sidebar stacks above the content column instead of
     sitting beside it -- a 260px-wide rail eats too much of a ~375px phone
     screen to coexist with content next to it. flex-direction:column here
     means #ds-sidebar and .dashboard-main naturally stack in DOM order
     (sidebar first, matching the markup) with no extra rule needed for
     that part. The rest of these undo the sidebar-present :has() rules
     above (same selectors, so equal specificity -- winning here on source
     order) back to a plain, normally page-scrolling stacked layout instead
     of the desktop pinned-shell-with-internal-scroll one. .site-header/
     #footer need no override at all here -- they're outside .app-shell
     entirely (see .page-shell above), so this breakpoint was never
     touching them to begin with. */
  .page-shell,
  .page-shell:has(#ds-sidebar:not([hidden])) {
    height: auto;
    overflow: visible;
  }

  .app-shell {
    flex-direction: column;
  }

  .page-shell:has(#ds-sidebar:not([hidden])) .app-shell {
    flex: initial;
    overflow: visible;
  }

  .page-shell:has(#ds-sidebar:not([hidden])) .dashboard-main {
    overflow-y: visible;
  }

  .dashboard-content {
    max-width: none;
    width: 100%;
  }

  .ds-sidebar {
    /* min-width/max-width (see that rule above) need resetting here too,
       not just width -- min-width:260px would otherwise force this wider
       than a narrow phone's actual viewport (horizontal overflow), and
       max-width:380px would stop width:100% from actually reaching full
       width on anything wider than that (e.g. a ~600px tablet still under
       this breakpoint), leaving an unexplained gap next to the rail. */
    width: 100%;
    min-width: 0;
    max-width: none;
  }

  /* No .ds-sidebar-panel override needed here anymore -- its desktop rule
     is width:100%/height:100% (matching .ds-sidebar exactly, now that
     there's no collapsed-width state to stay fixed-width against), and
     percentage heights against an indeterminate-height parent (.ds-sidebar
     is auto-height here, .app-shell no longer being pinned to a bounded
     height at this breakpoint) resolve to auto automatically. */
}

.tool-tabs {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
  margin-bottom: 28px;
}

.tool-tabs button {
  padding: 6px 16px;
  border: none;
  background: transparent;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.tool-tabs button.active {
  background: var(--navy);
  color: var(--white);
}

.tool-tabs button:not(.active):hover {
  color: var(--text);
}

.tool-panel {
  display: none;
}

.tool-panel.active {
  display: block;
}

.tool-panel h2 {
  color: var(--navy);
  margin-top: 0;
}

.upload-placeholder {
  border: 2px dashed var(--border);
  border-radius: 10px;
  padding: 48px 24px;
  text-align: center;
  color: var(--text-muted);
  background: var(--white);
}

.upload-placeholder input[type='file'] {
  margin-top: 16px;
}

.coming-soon {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 48px 24px;
  text-align: center;
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------
   Rent Roll step indicator -- static, always-visible 4-step tracker.
   Desktop: sticky column to the right of the main card (grid auto-placement
   puts .rr-main-col in column 1 and .rr-steps in column 2 since that's DOM
   order and neither sets `order` at this width). Narrow viewports: same
   640px breakpoint .tools-section/.auth-wrap-split already use elsewhere on
   this page, collapsing to a single column with .rr-steps pulled above
   .rr-main-col via `order`.
--------------------------------------------------------------------- */
.rr-layout {
  display: grid;
  grid-template-columns: 1fr 220px;
  align-items: start;
  gap: 24px;
}

.rr-main-col {
  min-width: 0;
}

.rr-steps {
  position: sticky;
  top: 80px;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
}

.rr-steps-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.rr-step {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 13px;
  color: var(--text-muted);
}

.rr-step-marker {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}

.rr-step-label {
  padding-top: 1px;
}

/* Complete: subtle light-blue/navy checkmark -- same accent pairing as
   .badge-live, not a loud color. */
.rr-step.complete .rr-step-marker {
  border-color: var(--light-blue);
  background: var(--light-blue);
}

.rr-step.complete .rr-step-marker::before {
  content: '\2713';
  font-size: 11px;
  line-height: 1;
  color: var(--navy);
}

.rr-step.complete .rr-step-label {
  color: var(--text);
}

/* Current: lightly accented, not bold -- navy outline + a small navy dot,
   label weight 600 (matches .tool-tabs button.active / .badge, not a
   heavier weight). */
.rr-step.current .rr-step-marker {
  border-color: var(--navy);
}

.rr-step.current .rr-step-marker::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--navy);
}

.rr-step.current .rr-step-label {
  color: var(--navy);
  font-weight: 600;
}

@media (max-width: 640px) {
  .rr-layout {
    display: flex;
    flex-direction: column;
  }

  .rr-steps {
    order: -1;
    position: static;
    margin-bottom: 20px;
  }
}

.rr-state {
  display: none;
}

.rr-state.active {
  display: block;
}

/* Locked property-name field in the review screen's title block, once an
   upload-time property selection has been made -- see buildReviewHtml()'s
   property-name field. Same muted/disabled look as :disabled inputs
   elsewhere get implicitly, applied explicitly here since the field itself
   stays `readonly` (still selectable/submittable), not `disabled`. */
.rr-text-input.rr-locked {
  background: var(--bg);
  color: var(--text-muted);
  cursor: default;
}

.upload-dropzone {
  border: 2px dashed var(--border);
  border-radius: 10px;
  padding: 48px 24px;
  text-align: center;
  color: var(--text-muted);
  background: var(--white);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.upload-dropzone:hover {
  border-color: var(--navy);
  background: var(--light-blue);
}

.upload-dropzone.dragover {
  border-color: var(--navy);
  background: var(--light-blue);
}

.upload-dropzone input[type='file'] {
  /* The native file input is triggered programmatically by clicking
     anywhere in the dropzone (see dashboard.html) -- no visible
     "Choose file" control. */
  display: none;
}

/* Trial-exhausted state (dashboard.html's auth-gate script) -- click/drop
   is a silent no-op in this state (no paywall modal left to pop, see
   dashboard.html), just visually inert so it doesn't read as a normal live
   upload target. */
.upload-dropzone.disabled {
  opacity: 0.6;
  background: var(--bg);
}

.upload-dropzone.disabled:hover {
  border-color: var(--border);
  background: var(--bg);
}

/* Confirm-gate staged state (dashboard.html's stageFile()/clearStagedFile(),
   both RR and T12 tools) -- toggled alongside #rr-dropzone-staged/
   #t12-dropzone-staged's own visibility. The dropzone stops being a
   click-to-browse target while a file is staged (see those functions'
   comments), so it shouldn't still visually invite a click either -- same
   "still technically clickable/hoverable but must not look live" shape as
   .upload-dropzone.disabled:hover just above, reverting the hover
   treatment back to the plain base border/background rather than a new
   state-specific pair of colors. */
.upload-dropzone.staged:hover {
  border-color: var(--border);
  background: var(--white);
}

/* Staged-file view (#rr-dropzone-staged/#t12-dropzone-staged) -- file name,
   "Process File", "Cancel Upload" stacked vertically and centered, rather
   than the row of inline-level elements .upload-dropzone's own
   text-align:center alone would produce. :not([hidden]) matters here, not
   just style -- these elements start (and get toggled back to) [hidden]
   via dashboard.html's stageFile()/clearStagedFile(), and the browser's
   own [hidden] { display: none } UA-stylesheet rule loses to ANY author
   rule that sets display on the same element regardless of specificity
   (author beats user-agent origin outright) -- an unscoped
   `#rr-dropzone-staged { display: flex }` here would force it visible even
   while [hidden] is present. Same :not([hidden]) discipline as
   #ds-sidebar's own rules already use elsewhere in this file. */
#rr-dropzone-staged:not([hidden]),
#t12-dropzone-staged:not([hidden]) {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* "Cancel Upload" -- reuses .rr-start-over as its base (same cross-tool
   reuse as that class's other use, the review screen's "New File" button)
   rather than a new class, with an id-scoped override for just the
   underline behavior, same "shared class + id override for one variant"
   pattern #forgot-password-link uses on top of .link-button (login.html).
   Underline only on :hover, not by default -- unlike .rr-start-over's own
   always-underlined look, matching the no-underline-until-hover convention
   .ds-doc-link:hover .ds-doc-name already uses elsewhere in this file
   instead of inventing a new one. */
#rr-staged-remove-btn,
#t12-staged-remove-btn {
  text-decoration: none;
}

#rr-staged-remove-btn:hover,
#t12-staged-remove-btn:hover {
  text-decoration: underline;
}

.rr-processing-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 56px 24px;
  text-align: center;
  color: var(--text);
}

/* Processing-card step checklist -- reuses .rr-step/.rr-step-marker/
   .rr-step-label from the sidebar tracker above rather than inventing new
   styles; complete/upcoming look identical to the sidebar there. Laid out
   as an inline row here instead of the sidebar's vertical list. The one
   difference is the current step's marker: instead of the sidebar's static
   navy dot, it spins -- same ring treatment and rr-spin keyframe as
   .btn-spinner, just sized to this 18px marker -- so motion (not a
   percent) is what signals "alive". Scoped under .rr-processing-steps so
   the sidebar tracker's own .rr-step.current styling is untouched. */
.rr-processing-steps {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}

.rr-processing-steps .rr-step {
  align-items: center;
}

.rr-processing-steps .rr-step.current .rr-step-marker {
  border: 2px solid var(--border);
  border-top-color: var(--navy);
  animation: rr-spin 0.8s linear infinite;
}

.rr-processing-steps .rr-step.current .rr-step-marker::before {
  content: none;
}

@keyframes rr-spin {
  to { transform: rotate(360deg); }
}

.rr-processing-sub {
  margin-top: 8px;
  font-size: 13px;
  color: var(--text-muted);
}

.rr-review-panel {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 36px;
}

.rr-review-section {
  padding: 24px 0;
  border-top: 1px solid var(--border);
}

.rr-review-section:first-child {
  padding-top: 0;
  border-top: none;
}

.rr-review-section:last-child {
  padding-bottom: 0;
}

.rr-review-section h3 {
  margin: 0 0 4px;
  color: var(--navy);
  font-size: 16px;
}

.rr-review-section > p.rr-section-sub {
  margin: 0 0 18px;
  font-size: 13px;
  color: var(--text-muted);
}

@media (max-width: 640px) {
  .rr-review-panel {
    padding: 20px;
  }
}

.rr-item-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 14px;
  padding: 14px 0;
  border-top: 1px solid var(--border);
}

.rr-item-row:first-of-type {
  border-top: none;
}

.rr-item-main {
  flex: 1 1 260px;
  min-width: 220px;
}

.rr-item-original {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 4px;
}

.rr-item-original code {
  background: var(--bg);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 12px;
}

.rr-badge-low {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 2px 7px;
  border-radius: 999px;
  background: #FBEEDB;
  color: #92590C;
  margin-left: 6px;
  vertical-align: middle;
}

.rr-rationale {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--text-muted);
  font-style: italic;
}

/* T12's whole-statement Income/Expense/NOI reconciliation success note (see
   buildT12ReviewHtml() in dashboard.html) -- deliberately plain text, not a
   bordered/filled box like the Anomalies items above it, so it reads as a
   small positive aside rather than another entry in that list. */
.rr-reconcile-success {
  margin: 0 0 18px;
  font-size: 13px;
  color: var(--success);
}

.rr-item-controls {
  flex: 1 1 220px;
  min-width: 200px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rr-select,
.rr-text-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 14px;
  background: var(--white);
  color: var(--text);
}

.rr-select:focus,
.rr-text-input:focus {
  outline: 2px solid var(--navy);
  outline-offset: 1px;
}

.rr-select.unresolved,
.rr-text-input.unresolved {
  border-color: #C98A1F;
  background: #FFFBF2;
}

/* input[type="date"] renders its own calendar-icon control and internal
   mm/dd/yyyy segments via browser/OS chrome that the shared .rr-text-input
   rule above can't reach -- everything else about this field (border,
   radius, padding, focus ring, .unresolved state) already matches the rest
   of the dashboard via that shared class. Scoped to [type='date'] so the
   plain-text property-name field, which shares .rr-text-input, is untouched. */
.rr-text-input[type='date'] {
  cursor: pointer;
}

.rr-text-input[type='date']::-webkit-calendar-picker-indicator {
  cursor: pointer;
  border-radius: 4px;
  padding: 3px;
  margin-right: -2px;
}

.rr-text-input[type='date']::-webkit-calendar-picker-indicator:hover {
  background: var(--light-blue);
}

.rr-ami-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}

.rr-ami-row input[type='text'],
.rr-ami-row input[type='number'] {
  width: 70px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 14px;
}

.rr-ami-row label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-muted);
  cursor: pointer;
}

.rr-field {
  margin-bottom: 16px;
}

.rr-field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 4px;
  color: var(--text);
}

.rr-empty-note {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0;
}

.rr-export-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.rr-export-hint {
  font-size: 13px;
  color: var(--text-muted);
}

.rr-start-over {
  background: none;
  border: none;
  color: var(--navy);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
}

#rr-error-banner,
#t12-error-banner {
  margin-bottom: 16px;
}

/* ---------------------------------------------------------------------
   Footer -- ported from modelthedeal.com's shared footer partial
   (#footer/.footer-inner/.footer-links rules in css/global.css, model-the-deal
   repo). Terms + Privacy links only -- the source footer's email-signup CTA
   (.footer-cta) is intentionally excluded.

   Adaptation: the source wraps .footer-inner in a separate .wrap element
   (max-width: 1100px; padding: 0 32px) that this app doesn't have. Rather
   than add an unused-elsewhere .wrap utility at the site's width, the
   max-width/padding are folded directly into .footer-inner below and sized
   to match .dashboard-main (860px / 24px) so the footer row lines up under
   the dashboard content instead of the wider marketing-site column.
--------------------------------------------------------------------- */
#footer {
  background: var(--navy);
  color: rgba(255, 255, 255, 0.55);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  /* Pushes the footer to the bottom of body's flex column by consuming
     all remaining free space above it, without resizing/re-centering
     header or main -- see the html/body comment near the top of this
     file for why this replaced giving <main> flex:1. */
  margin-top: auto;
}

.footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: 860px;
  margin: 0 auto;
  height: 64px;
  padding: 0 24px;
  font-size: 0.82rem;
  gap: 12px;
}

.footer-links { display: flex; gap: 20px; }

.footer-links a {
  color: rgba(255, 255, 255, 0.55);
  text-decoration: none;
  font-size: 0.82rem;
}
.footer-links a:hover { color: var(--white); }

/* Width override for the same footer on login.html/signup.html: matches
   .auth-wrap-split (960px), those pages' own content width, instead of
   .dashboard-main (860px) which .footer-inner is sized to by default. */
.footer-inner--auth {
  max-width: 960px;
}

/* Width override for the same footer on reset-password.html: matches its
   plain .auth-wrap (400px, single column -- this page doesn't use
   .auth-wrap-split like login.html/signup.html do), narrower than
   .footer-inner--auth (960px) above. */
.footer-inner--auth-narrow {
  max-width: 400px;
}

/* ---------------------------------------------------------------------
   Settings page (settings.html) -- Plan & Billing card's button row
   (Manage Billing / Switch Plan). Both are plain .btn elements with
   nothing else on this file to lay them out side by side -- .paywall-plans
   used to be scoped to the paywall modal specifically (since removed, along
   with the rest of that component -- see git history), so rather than
   repurpose it this follows the same id-scoped "display:flex; gap" precedent
   #rr-dropzone-staged/#t12-dropzone-staged already use for their own
   button pairs, instead of introducing a new named row/group class.
--------------------------------------------------------------------- */
#settings-billing-actions {
  display: flex;
  gap: 8px;
}

/* ---------------------------------------------------------------------
   Pricing page (upgrade.html) -- three-card plan grid + per-card feature
   rows. No existing 3-column grid, hero-price typography, "featured tier"
   highlight, or label-left/value-right row layout in this file to reuse --
   all four flagged and approved before adding. Layout/sizing only, every
   color still an existing --navy/--text/--text-muted/--border token.
--------------------------------------------------------------------- */
.pricing-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin: 32px 0;
}

@media (max-width: 640px) {
  .pricing-grid {
    grid-template-columns: 1fr;
  }
}

/* Centers the page intro (h2 + subhead) and the Enterprise note -- .tool-card's
   own text-align stays left everywhere else (homepage tool tiles), and
   .dashboard-content's h2/p aren't centered by default on settings.html or
   dashboard.html either, so this is scoped to these specific elements
   rather than a blanket .dashboard-content h2/p rule that would also
   recenter those other pages. */
.pricing-intro {
  text-align: center;
}

/* .tool-card's own text-align stays left by default (homepage tool tiles);
   pricing cards read better centered, and buttons pinned to the bottom
   edge regardless of how many feature rows a given card has above them.
   transition + :hover below give each card a slight lift -- shadow color is
   rgba(14, 40, 64, ...), i.e. --navy-dark (#0E2840) at partial opacity
   rather than a new shadow color, matching how every other shadow in this
   file is built off an existing token instead of a bare black. */
.pricing-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Taller than .tool-card's default 24px all around -- extra top/bottom
     only (left/right stay 24px), so the added height comes from breathing
     room around the existing content rather than stretching any one
     element. Grid items stretch to the row's tallest card by default, and
     all three cards share this same rule, so they grow evenly together --
     .pricing-grid's own 20px gap between cards is untouched. */
  padding: 40px 24px;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.pricing-card:hover {
  box-shadow: 0 8px 20px rgba(14, 40, 64, 0.12);
  transform: translateY(-2px);
}

.pricing-card .btn {
  margin-top: auto;
  width: 100%;
}

/* Larger than .tool-card h3's default 18px -- fills more of the extra
   top padding added above, rather than leaving it as bare whitespace above
   the tier name. Still inherits .tool-card h3's navy color/margin, just the
   size overridden. */
.pricing-card h3 {
  font-size: 20px;
}

/* Pay Per Doc / Investor -- a touch darker than .tool-card's default
   1px --border, so they read as slightly more defined next to
   Underwriter's 2px navy "featured" border rather than disappearing next
   to it. Same "step off an existing token" approach as .ds-sidebar's
   background (color-mix against --bg) rather than a new hex value. Scoped
   off Underwriter via :not() so its own navy border (--navy, unrelated to
   --border entirely) is untouched. */
.pricing-card:not(.pricing-card-featured) {
  border-width: 1.5px;
  border-color: color-mix(in srgb, var(--border) 70%, black);
}

/* Underwriter's "Best Value" highlight -- thicker navy border in place of
   .tool-card's default 1px --border, same token, no new color. position:
   relative anchors .pricing-badge below against this card specifically. */
.pricing-card-featured {
  position: relative;
  border: 2px solid var(--navy);
}

/* "Best Value" pill -- straddles the card's top edge (half above, half
   inside) rather than sitting in-flow above the heading, so it doesn't push
   Underwriter's own content down out of alignment with the other two
   cards' content. top:0 + translate(-50%, -50%) centers the badge's own
   midpoint exactly on the card's top border, regardless of the badge's
   height -- more robust than a fixed negative top offset. .badge/.badge-live
   still supply shape (padding/radius/font); background/color are overridden
   here rather than on .badge-live itself, which stays its original light-blue
   "live" treatment for whenever it's next used elsewhere (currently nowhere
   else in the app) -- navy/white is specific to this "featured tier" pill,
   not a redefinition of what .badge-live means generally. Both still
   existing tokens, no new colors. */
.pricing-badge {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--navy);
  color: var(--white);
}

/* "Current Plan" badge (public/upgrade.html) -- unlike .pricing-badge
   above, this stays in normal flow as .pricing-card's first visible child
   (.pricing-card is flex-column/align-items:center, so it centers above h3
   on its own line with no other positioning needed) rather than straddling
   the card's border -- it has to coexist with .pricing-badge on the same
   Underwriter card for a subscriber currently on that tier, and two
   absolutely-positioned pills anchored to the same top edge would collide.
   Only margin added here; shape/color are .badge/.badge-live, unmodified. */
.current-plan-badge {
  margin-bottom: 8px;
}

/* .badge's own `display: inline-block` is an author-origin declaration, so
   it silently beats the browser's UA `[hidden] { display: none }` rule
   regardless of specificity -- same cascade fact already documented and
   fixed at #ds-account-manage-billing-btn[hidden] further up this file (see
   that rule's comment). Without this, both cards' badges rendered visible
   for every caller, trial included, from first paint -- confirmed live,
   not theoretical. Scoped to this one class (not a rule on .badge itself,
   which .pricing-badge/badge-live/badge-soon also use and never combine
   with [hidden]) so this can't change anything outside this badge. */
.current-plan-badge[hidden] {
  display: none;
}

/* Scoped under .pricing-card (not just .pricing-price) so this beats
   .tool-card p's color:var(--text-muted) in specificity (0,2,0 vs
   0,1,1) -- .pricing-price alone is only (0,1,0) and was silently
   losing that cascade, so the color declaration here never actually
   rendered. Matches the "Choose a Plan" h2 (.pricing-intro), which has
   no color rule of its own and so renders in body's default
   var(--text) -- not var(--navy), despite most other headings on this
   page using navy. */
.pricing-card .pricing-price {
  font-size: 20px;
  font-weight: 700;
  color: var(--text);
  margin: 8px 0 4px;
}

.pricing-price span {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-muted);
}

/* Per-card feature/rate rows (Effective Cost/File, Overage Pricing, Save
   Files By Property, etc.) -- label-left/value-right, which .rr-field
   (single value per block) doesn't fit. */
.pricing-rows {
  width: 100%;
  margin: 16px 0;
  padding: 16px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  text-align: left;
}

.pricing-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  padding: 4px 0;
}

.pricing-row dt {
  color: var(--text-muted);
}

.pricing-row dd {
  margin: 0;
  font-weight: 600;
  color: var(--text-muted);
}

.pricing-check {
  color: var(--navy);
  font-weight: 700;
}

/* Enterprise "contact us" note -- was .paywall-enterprise, living in the
   now-deleted paywall modal's CSS section; upgrade.html is its only
   remaining consumer, so it's renamed and relocated here rather than kept
   under a name describing a component that no longer exists. Same rule
   otherwise, unchanged. */
.enterprise-note {
  font-size: 0.9rem;
  color: var(--text-muted);
  margin-bottom: 0;
}

/* Mirrors the ➜ glyph horizontally so it points back/left while keeping
   its rounded-arrow style, rather than swapping to a different (plain)
   left-pointing character. */
.back-arrow {
  display: inline-block;
  transform: scaleX(-1);
}

/* "Back to Dashboard" link (settings.html/upgrade.html) -- underline on
   hover, layered on top of .site-header nav a's shared base style rather
   than added to that rule directly, since it also covers
   #header-upgrade-link on dashboard.html, which stays underline-free on
   hover. */
.back-link:hover {
  text-decoration: underline;
}

/* Pricing card CTA arrow (upgrade.html) -- same ➜ glyph as .back-arrow,
   at the same font-size/weight .back-arrow gets by inheriting from
   .site-header nav a, pinned explicitly here since .pricing-cta's .btn
   context is otherwise a different font-size/weight (0.93rem/600 vs
   0.88rem/500). No scaleX mirror -- unlike .back-arrow, these point
   forward, which is the glyph's own default orientation. */
.cta-arrow {
  display: inline-block;
  font-size: 0.88rem;
  font-weight: 500;
}
