@layer pfa-app {
/* ===========================================================================
 * UNIFIED MESSAGING MODEL + Overview close  (consumer-first redesign)
 *
 * One voice, three homes. Every way the app speaks to the person now routes
 * through one of three tiers with a single shared look, dismiss gesture and
 * accessibility behaviour, instead of the previous scatter of one-off strips
 * (the sticky "Welcome back" band, the install prompt, the backup reminder,
 * the confirmation toast, the first-run and drop hints), each of which lived
 * wherever was convenient and looked unlike the others.
 *
 *   Tier 1  .status-line  - ambient status. Quiet, informational, no forced
 *                           action. Belongs to the CONTENT column (placed as
 *                           the first child of the content wrap), so it aligns
 *                           with the cards and never sits in the sticky nav
 *                           seam and never shoves the page down.
 *   Tier 2  .offer         - a genuine take-it-or-leave-it offer (install,
 *                           back up). One floating card, low and thumb-reachable,
 *                           clearly dismissible, shown ONE AT A TIME.
 *   Tier 3  Sonner toast   - immediate feedback after an action (+ undo). Already
 *                           lived at the bottom centre; now shares the voice.
 *
 * All three use .msg-dismiss for a single, comfortably sized close control, and
 * inherit the reduced-motion / reduced-transparency contracts at the foot of
 * the file and in glass.css. Solid-first: this block is fully legible with
 * glass.css stripped out.
 * ------------------------------------------------------------------------- */

/* ---- Overview: the story's closing line, folded INTO the story card ----
   Previously "Open Right Now" / "Open Ahead" were two loose buttons appended
   to the page background beneath the card. They are the natural next step of
   the narrative, so they now close the story card itself, under a quiet
   lead-in, separated by a hairline so they read as "here is where to go next"
   rather than orphaned controls. */
/* ---- Shared dismiss control (all tiers) ----
   One clear, comfortably sized close gesture the person learns once. Replaces
   the tiny bespoke circle the old greeting used. */
.msg-dismiss {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  min-height: 32px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  transition:
    background 0.15s ease,
    color 0.15s ease;
}
.msg-dismiss:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
}
.msg-dismiss:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.msg-dismiss svg {
  width: 16px;
  height: 16px;
}

/* ---- Tier 1: ambient status line (in the content column) ----
   Placed by the app as the first child of the content wrap (#app.wrap /
   .accounts-wrap), so it aligns to the same width as the cards, sits in the
   flow (never over the sticky bars), and simply is not rendered when there is
   nothing worth saying - the person then lands straight on their figures. */
.status-line {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--panel-2);
  border: 1px solid var(--edge);
  color: var(--fg);
  font-size: 13.5px;
  line-height: 1.45;
}
.status-line-ic {
  flex: none;
  display: inline-flex;
  margin-top: 1px;
  color: var(--accent-text);
}
.status-line.tone-good .status-line-ic {
  color: var(--good-ink);
}
.status-line.tone-watch .status-line-ic {
  color: var(--warn-ink);
}
.status-line-body {
  flex: 1;
  min-width: 0;
}
.status-line-body .strong {
  font-weight: 650;
}
.status-line-body .status-line-sub {
  color: var(--dim);
  font-size: 12.5px;
  margin-top: 2px;
}
.status-line .msg-dismiss {
  margin: -4px -4px -4px 0;
}

/* ---- Tier 2: the offer card (one floating surface, one at a time) ----
   Bottom-centre, thumb-reachable, safe-area aware. The app shows at most one
   offer at a time (install OR back up, never stacked). Shares the toast's dock
   variables so the two never collide. */
.offer {
  position: fixed;
  left: 50%;
  bottom: calc(var(--dock-edge) + env(safe-area-inset-bottom));
  transform: translate(-50%, 24px);
  z-index: 65;
  display: none;
  align-items: center;
  gap: 12px;
  max-width: min(92vw, 560px);
  padding: 12px 12px 12px 16px;
  border-radius: 16px;
  background: var(--panel);
  border: 1px solid var(--edge);
  box-shadow: var(--shadow-lg);
  font-size: 13.5px;
  line-height: 1.45;
  opacity: 0;
  transition:
    opacity 0.22s ease,
    transform 0.22s ease;
}
.offer.show {
  display: flex;
  opacity: 1;
  transform: translate(-50%, 0);
}
.offer-ic {
  flex: none;
  display: inline-flex;
  color: var(--accent-text);
}
.offer-body {
  flex: 1;
  min-width: 0;
}
.offer-actions {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
/* When a toast is on screen with an offer, lift the offer clear of it. */
body:has([data-sonner-toast]) .offer.show {
  bottom: calc(var(--dock-toast) + env(safe-area-inset-bottom));
}
@media (max-width: 639px) {
  .offer {
    max-width: calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right));
  }
  .offer.show {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: 12px;
    row-gap: 10px;
  }
  .offer-actions {
    grid-column: 1 / -1;
    justify-content: flex-end;
  }
}

/* ---- Retrofit the surfaces the app still emits today onto the shared voice,
   so unification is visible immediately even before the emit-side is routed
   through the new tiers. The install banner IS an offer; give it the offer's
   entrance so the two are indistinguishable. */
/* ---- Accessibility + motion contracts for the messaging model ----
   Reduced motion: keep the state change, drop the slide. */
@media (max-width: 1099px) {
  body.has-bottom-nav .offer.show {
    bottom: calc(var(--dock-bottom, 0px) + var(--s3));
  }
  body.has-bottom-nav:has([data-sonner-toast]) .offer.show {
    bottom: calc(var(--dock-bottom, 0px) + var(--dock-toast));
  }
}

@media (prefers-reduced-motion: reduce) {
  .offer {
    transition: none !important;
  }
  .offer.show {
    transform: translate(-50%, 0) !important;
  }
}

/* ===========================================================================
 * v19 - consumer-first finance-workspace refinement
 *
 * Implements the reviewed plan. CSS only: no markup, wording, calculation,
 * stored data, print output or behaviour changes. Appended last so it settles
 * ties by source order; higher-specificity selectors are used only where a
 * base rule must be beaten deliberately. Surface elevation for the lead vs
 * supporting cards and the chart marks live in glass.css (which loads last);
 * everything here is layout, navigation, typography, lists, tables, the
 * greeting, mobile and light-mode - none of which glass.css contests.
 * ------------------------------------------------------------------------- */

/* Local tokens (additive) */
:root {
  --workspace-max: 1080px;
  --section-gap: var(--s4);
  /* The space under the sticky chrome. #app's top padding and a notice's top
     margin both read it, so a notice sits with this space on either side. */
  --page-top: var(--s6);
  --hairline: color-mix(in srgb, var(--fg) 8%, transparent);
  --hairline-soft: color-mix(in srgb, var(--fg) 5%, transparent);
  --soft-hover: color-mix(in srgb, var(--accent) 7%, var(--panel-2));
}
[data-theme='dark'] {
  --hairline: color-mix(in srgb, var(--fg) 10%, transparent);
  --hairline-soft: color-mix(in srgb, var(--fg) 6%, transparent);
  --soft-hover: color-mix(in srgb, var(--accent) 9%, var(--panel-2));
}
@media (prefers-color-scheme: dark) {
  [data-theme='auto'] {
    --hairline: color-mix(in srgb, var(--fg) 10%, transparent);
    --hairline-soft: color-mix(in srgb, var(--fg) 6%, transparent);
    --soft-hover: color-mix(in srgb, var(--accent) 9%, var(--panel-2));
  }
}

/* --- Plan 3: workspace and reading measure --- */
.wrap,
.footer {
  max-width: var(--workspace-max);
}
.wrap {
  gap: var(--section-gap);
  padding-top: var(--page-top);
  padding-bottom: 56px;
  padding-inline: calc(var(--s5) + env(safe-area-inset-left)) calc(var(--s5) + env(safe-area-inset-right));
}
.overview-story {
  max-width: 76ch;
}

/* --- Plan 2: the two sticky bars read as one calm layer. Only their own
   borders/shadows are softened here; the original blur, stacking and the
   in-flow greeting inside .topbar-stack are left exactly as shipped, so the
   greeting keeps pushing the page down on show and never overlaps the tabs. --- */
.topbar {
  border-bottom: 0;
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 10%, transparent);
}
.period-bar {
  border-bottom: 1px solid var(--hairline-soft);
}
.period-select,
.mini,
.filters input,
.filters select,
.name-field,
.pass,
.picker-filter {
  border-color: var(--hairline);
}
.btn:not(.primary):not(.danger) {
  background: color-mix(in srgb, var(--panel) 78%, transparent);
}
.btn:focus-visible,
.period-select:focus-visible,
.mini:focus-visible,
.filters input:focus-visible,
.filters select:focus-visible,
.name-field:focus-visible,
.pass:focus-visible,
.picker-filter:focus-visible,
.tx-filters .f-search:focus {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- Plan 6: financial typography --- */
body {
  letter-spacing: -0.005em;
}
.vm-number {
  font-variant-numeric: tabular-nums lining-nums;
  text-wrap: balance;
}
.recent-item {
  border-color: transparent;
  box-shadow: none;
}
.insight + .insight,
.recent-item + .recent-item {
  border-top: 1px solid var(--hairline-soft);
}
/* --- Plan 12: tables - clearer header, quieter rules --- */
table.grid th {
  padding-top: 8px;
  padding-bottom: var(--s2);
}

/* --- Plan 11: Data & settings truly recedes and never leads --- */
/* Matches glass.css's .card.secondary tone (panel-2 55%) so the Data & settings
   drawer reads ONE tone whether the glass layer is active or a reduced-
   transparency / no-backdrop-filter user is on the solid fallback. Previously
   this solid path was 48% while the glass path was 55%, so the same drawer
   rendered two different tones by transparency mode. */
.card.secondary {
  background: color-mix(in srgb, var(--panel-2) 55%, var(--panel));
  border-color: var(--hairline-soft);
  box-shadow: none;
}
.card.secondary > summary {
  min-height: 44px;
  color: color-mix(in srgb, var(--dim) 92%, var(--fg));
}

.card.secondary:hover {
  border-color: var(--hairline);
}
.secondary[open] > summary {
  border-bottom-color: var(--hairline-soft);
}

/* --- Plan 14: page ending --- */
.footer {
  margin-top: 4px;
  padding-bottom: 24px;
  color: color-mix(in srgb, var(--dim) 82%, transparent);
  font-size: var(--t-label);
}

/* --- Plan 10: the greeting is intentionally left to the original in-flow
   treatment (mounted inside .topbar-stack, pushing the page down on show and
   collapsing on dismiss). That approach never overlaps the navigation, so it
   is respected here rather than overridden. --- */

@media (min-width: 1000px) {
  #app.wrap,
  .accounts-wrap.accounts-grid {
    --card-gap: var(--s4);
    gap: var(--card-gap);
  }
}

@media (max-width: 639px) {
  :root {
    --section-gap: 12px;
    --page-top: var(--s3);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ledger-tab,
  .insight-go {
    transition: none !important;
  }
}

/* --- Plan 14: light mode calibrated on its own, not dark values on white.
   Softer background pools, near-neutral hairlines, gentler control fills. --- */
[data-theme='light'] {
  --hairline: color-mix(in srgb, var(--fg) 11%, transparent);
  --hairline-soft: color-mix(in srgb, var(--fg) 7%, transparent);
}
@media (prefers-color-scheme: light) {
  [data-theme='auto'] {
    --hairline: color-mix(in srgb, var(--fg) 11%, transparent);
    --hairline-soft: color-mix(in srgb, var(--fg) 7%, transparent);
  }
}

[data-theme='light'] .btn.primary,
[data-theme='light'] .brand-mark {
  background: var(--accent);
}

@media (prefers-color-scheme: light) {
  [data-theme='auto'] .btn.primary,
  [data-theme='auto'] .brand-mark {
    background: var(--accent);
  }
}



.privacy-slash {
  opacity: 0;
  transition: opacity 0.15s ease;
}
:root[data-privacy="on"] .privacy-slash {
  opacity: 1;
}
:root[data-privacy="on"] #privacy-btn {
  color: var(--accent-text);
  border-color: color-mix(in srgb, var(--accent) 48%, var(--edge));
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
}
.app-loading {
  min-height: min(54vh, 460px);
  flex-direction: column;
  text-align: center;
}
.app-loading-mark {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--accent) 11%, var(--panel-2));
}
.app-loading-copy,
.busy-copy {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.app-loading-copy strong,
.busy-copy strong {
  color: var(--fg);
}
.busy-card {
  max-width: min(420px, calc(100vw - 32px));
  padding: 18px 20px;
}
.busy-sub {
  max-width: 34ch;
}
.empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}
#app.view-enter {
  animation: view-enter 0.2s ease-out;
}
@keyframes view-enter {
  from {
    opacity: 0.72;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* No longer a card: a single quiet line under the headline, with its caveat
   behind the (i) that follows it. */
.coverage-note {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: -4px 0 0;
  padding-inline: 2px;
}
.coverage-note > .chart-info > summary {
  line-height: 0;
}
/* The fact itself is the way into the coverage card, so it wears the app's
   one inline-link affordance - at the line's own size, with the button
   padding removed so the row still reads as one quiet sentence. */
.coverage-note > .linkbtn {
  padding: 0;
  font-size: inherit;
  font-weight: 600;
  text-align: left;
}
@media (max-width: 639px) {
  .coverage-note > .linkbtn {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-lg);
  }
}
.overview-attention {
  min-height: 0;
}
.activity-tabs {
  width: 100%;
}
.activity-view > .activity-tabs {
  margin-bottom: var(--s4);
}
.activity-tabs .ledger-tabs {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  max-width: 100%;
  gap: var(--s1);
  padding: var(--s1);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface-inset);
  box-shadow: var(--lift-1);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.activity-tabs .ledger-tabs .ledger-tab {
  display: flex;
  flex: 1 1 0;
  align-items: center;
  justify-content: center;
  min-width: max-content;
  min-height: var(--tap-lg);
  padding: var(--s2);
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  text-align: center;
  font-weight: 620;
  box-shadow: none;
}
.activity-tabs .activity-tab-title {
  color: var(--fg);
  font-size: var(--t-body);
  font-weight: inherit;
}
.activity-tabs .ledger-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (forced-colors: active) {
  .activity-tabs .ledger-tab.active:not(:focus-visible) {
    outline: 1px solid Highlight;
    outline-offset: -1px;
  }
}
.activity-analysis > .activity-primary {
  order: -2;
}
.activity-primary .dh-secondary-metric {
  padding-inline: 0;
  border-color: transparent;
  background: transparent;
}
.activity-primary .dh-secondary-metric strong {
  font-size: var(--metric-minor);
}
.activity-primary .dh-support .metric-open {
  width: fit-content;
  max-width: 100%;
}
@media screen and (min-width: 1000px) {
  .activity-primary .dh-aside {
    width: 280px;
  }
  .activity-primary .dh-aside .donut {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s2);
    margin: 0;
  }
  .activity-primary .dh-aside .donut-figure {
    width: 150px;
    align-self: center;
  }
  .activity-primary .dh-aside .donut-legend {
    flex: none;
    max-width: none;
  }
  .activity-primary .dh-aside .donut-legend-label {
    white-space: normal;
  }
}
.activity-analysis > .card.insights,
.activity-analysis > .ds-tiles-section {
  order: -1;
}
.tx-filters {
  flex-direction: row;
  align-items: end;
  gap: 8px;
}
/* A label is never smaller than the field it labels.

   Every input in the app is 16px - deliberately, because anything smaller makes
   iOS zoom the page on focus. The labels were 12px, so "Search transactions"
   rendered visibly smaller than its own placeholder "Name, category, amount, or
   custom label" directly beneath it, and the same inversion applied to Type,
   Name and Amount on Position. Thirteen label/field pairs app-wide read
   backwards.

   14px keeps the label quieter than the value a person types (weight and colour
   still do that) without printing it smaller than the field's own placeholder.
   Helper captions under a field are NOT labels and keep .muted.small. */
.field-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  color: var(--dim);
  font-size: 14px;
  font-weight: 650;
}
.tx-search-field {
  flex: 1;
}
.tx-search-clear {
  flex: none;
}
.position-add-fields {
  display: grid;
  grid-template-columns: minmax(150px, 1fr) minmax(130px, 0.8fr) auto;
  align-items: end;
  gap: var(--s2);
}
.position-add-fields.has-currency {
  grid-template-columns: minmax(150px, 1fr) minmax(130px, 0.8fr) minmax(96px, 0.4fr) auto;
}
.position-add-fields .name-field {
  width: 100%;
  min-height: 40px;
}
.position-remove {
  min-width: 72px;
  justify-content: center;
}
.goal-draft-step {
  align-items: end;
  gap: 8px;
  margin: 0;
  padding-top: var(--s3);
  border-top: 1px solid var(--hairline-soft);
}
.goal-draft-step .field-label {
  flex: 1 1 180px;
}
.goal-draft-step .name-field {
  width: 100%;
}
.view-forecast .card.empty,
.view-position .card.empty {
  grid-column: 1 / -1;
}
@media (max-width: 639px) {
  .activity-tabs {
    width: 100%;
  }
  .tx-filters {
    align-items: end;
  }
  .position-add-fields,
  .position-add-fields.has-currency {
    grid-template-columns: 1fr;
  }
  .position-add-fields .btn,
  .goal-draft-step .btn {
    width: 100%;
    min-height: 44px;
    justify-content: center;
  }
  .position-remove {
    min-height: 44px;
  }
  .position-line-edit .btn { min-height: 44px; }
  .goal-draft-step {
    align-items: stretch;
  }
}
@media (prefers-reduced-motion: reduce) {
  #app.view-enter,
  .privacy-slash {
    animation: none;
    transition: none;
  }
}

.field-label .name-field,
.field-label .mini {
  flex: none;
  width: 100%;
  max-width: none;
  min-height: 40px;
}
@media (max-width: 639px) {
  .field-label .name-field,
  .field-label .mini {
    min-height: 44px;
  }
}
}
