/* ==========================================================================
   App shell
   ========================================================================== */

/* The ambient wash lives on the shell, not on <body>: it has to scroll with
   nothing and cover everything, and #app is the only element that is always
   exactly the viewport. `fixed` attachment keeps it still while views scroll
   underneath, so it reads as light in the room rather than as a texture
   glued to the page. */
#app {
  background-image: var(--app-ambient);
  background-attachment: fixed;
  display: grid;
  /* One column: navigation is the top menu on a computer (#topnav) and the
     bottom bar on a phone. */
  grid-template-columns: minmax(0, 1fr);
  height: 100vh;
  height: 100dvh; /* mobile browsers: accounts for the collapsing address/tab bar so content never ends up under it */
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-standard);
}

#app.is-ready {
  opacity: 1;
}

/* ---- Sidebar: not shown at any width since the v12 bottom bar ----
   Only the parking place for the account list, the theme switch (and, on a
   computer, whatever js/app.js moves out of it); see "Phone shell" below. */

.sidebar {
  grid-column: 1;
  height: 100vh;
  height: 100dvh;
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  background: var(--color-bg-elevated);
  border-right: 1px solid var(--color-border);
  z-index: var(--z-sidebar);
}

/* The parking place's foot: light/dark (the top menu has it on a computer -
   js/app.js placeShellParts). */
.sidebar__footer {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}

/* Sync indicator (audit Etap 5.3), in the top menu on a computer and in the
   top bar on a phone. Status colours as in Settings -> Data. */
.sidebar__footer {
  flex-wrap: wrap;
}

.sync-status {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 32px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  text-align: left;
  transition: background-color var(--dur-fast) var(--ease-standard);
}

.sync-status[hidden] {
  display: none;
}

.sync-status:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

.sync-status__dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-text-tertiary);
}

.sync-status--profit .sync-status__dot { background: var(--color-profit); }
.sync-status--accent .sync-status__dot { background: var(--color-accent); }
.sync-status--warning .sync-status__dot { background: var(--color-warning); }
.sync-status--loss .sync-status__dot { background: var(--color-loss); }

.sync-status__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* DEMO DATA in the top bar (app.js updateDemoTag): a filled capsule in the
   warning colour, no outline (the new look, 2026-10-17). */
.demo-tag {
  display: inline-flex;
  align-items: center;
  /* 28px, the read-only pill's height, so the two sit level (2026-09-27). */
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--text-2);
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
  white-space: nowrap;
  cursor: pointer;
}

/* A note, not an alarm (the operator 6.11): plain words in the bar. */
.demo-tag:hover {
  color: var(--text-1);
}

.demo-tag[hidden] {
  display: none;
}

/* READ-ONLY DEMO in the top bar while this copy is not the hosted service
   (app.js updateReadOnlyTag): a quiet pill that only says so. */
.readonly-tag {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--tool-bg);
  box-shadow: var(--tool-edge);
  color: var(--color-text-secondary);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}

.readonly-tag[hidden] {
  display: none;
}

/* The public demo's way to an account (#demo-signup, js/app.js; /demo only):
   a link in the primary fill, the height and shape of the tags beside it. */
.demo-signup {
  height: 28px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption);
  text-decoration: none;
}

.demo-signup[hidden] {
  display: none;
}

/* While the Demo data tag shows, "Read-only demo" beside it would say the same
   twice (the demo's tooltip says the copy is read-only); a copy that is
   read-only for another reason says so in the access pill, never here. */
.topnav__tools:has(.demo-tag:not([hidden])) .readonly-tag,
.topbar__actions:has(.demo-tag:not([hidden])) .readonly-tag {
  display: none;
}

/* On a phone, and in the computer's menu from 1380px down, only "Read-only":
   the page's title and the other tags need the room. The full words stay in
   the tooltip. */
@media (max-width: 560px) {
  .readonly-tag__more {
    display: none;
  }
}

@media (max-width: 400px) {
  .demo-tag__more {
    display: none;
  }
}

/* The computer's menu below 1120px: "Demo" too, so the "?" fits beside it
   (measured 900-1120px with sync and Demo data). */
@media (max-width: 1120px) {
  .topnav__tools .demo-tag__more {
    display: none;
  }
}

@media (max-width: 1380px) {
  .topnav__tools .readonly-tag__more {
    display: none;
  }
}

/* The public demo's menu carries Create account as well, which the widths
   below were not measured with: up to about 1470px "Settings" ran under the
   tools (the operator 21.10, measured 900-1500px). There the button says
   what the tag would (a demo, no account), so the tag gives way first, then
   the brand's name. */
@media (max-width: 1500px) {
  .topnav__tools:has(.demo-signup:not([hidden])) .readonly-tag {
    display: none;
  }
}

@media (max-width: 1380px) {
  .topnav:has(.demo-signup:not([hidden])) .topnav__name {
    display: none;
  }

  .topnav:has(.demo-signup:not([hidden])) .topnav__brand {
    margin-right: 0;
  }
}

.brand-signet {
  display: block;
  height: 30px;
  width: auto;
  color: var(--color-text-primary);
}

/* ---- Account list (js/components/account-nav.js, audit Etap 6.3) ----
   One link per active account under the nav. --acct is the account's colour
   token (identity, never direction). Scrolls on its own when there are more
   accounts than the column has room for, so the footer never moves off. */

.acct-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-height: 0;
  margin-top: calc(-1 * var(--space-2));
}

/* A button: it folds the section (account-nav.js). */
.acct-nav__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 28px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--color-text-tertiary);
}

.acct-nav__head:hover {
  color: var(--color-text-secondary);
}

.acct-nav__head:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.acct-nav__title {
  flex: 1;
}

.acct-nav__chevron {
  display: inline-flex;
  margin-left: calc(-1 * var(--space-1));
  transition: transform var(--dur-fast) var(--ease-standard);
}

.acct-nav.is-folded .acct-nav__chevron {
  transform: rotate(-90deg);
}

.acct-nav.is-folded .acct-nav__list,
.acct-nav.is-folded .acct-nav__add {
  display: none;
}

.acct-nav__list {
  position: relative; /* offsetParent for the rows: keeps them in view */
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  list-style: none;
  margin: 0;
  padding: 0;
}

.acct-nav__row {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text-secondary);
  font-size: var(--fs-footnote);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-standard),
    background-color var(--dur-fast) var(--ease-standard);
}

.acct-nav__row:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

/* The account Journal and Analytics are showing right now. */
.acct-nav__row.is-context {
  color: var(--color-text-primary);
}

/* Its own page is open - same treatment as an active nav item. */
.acct-nav__row.is-active {
  color: var(--color-text-primary);
  background: var(--color-surface-raised);
  box-shadow: var(--shadow-sm);
  font-weight: var(--fw-semibold);
}

.acct-nav__dot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--acct);
}

.acct-nav__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.acct-nav__bal {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--color-text-tertiary);
}

.acct-nav__add {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--fs-footnote);
  color: var(--color-text-tertiary);
  text-align: left;
  transition: color var(--dur-fast) var(--ease-standard),
    background-color var(--dur-fast) var(--ease-standard);
}

.acct-nav__add:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

/* ==========================================================================
   The top menu (redesign v12, dev/mockups/redesign/v12.html ".nav";
   docs/REDESIGN-PLAN.md decisions 3, 4 and 8). From 881px it is the only
   navigation: brand, Portfolio, Accounts (a popover with the account list),
   Journal, Analytics, Planner, Settings, and on the right the state pills,
   light/dark and New Trade (js/app.js moves those in). It is the first
   thing in the scrolling column and scrolls away with the page.
   On a summary screen - a view whose first block is the desk (.desk:
   Portfolio, an account, Analytics, Planner) - it is the top of the desk:
   the desk's own colour, the same side padding, no seam. On Journal and
   Settings it is a plain bar on the page.
   ========================================================================== */

.topnav {
  position: relative;
  z-index: var(--z-topbar);
  display: flex;
  align-items: center;
  gap: 16px;
  max-width: var(--content-max-width);
  min-height: var(--topbar-height);
  margin: 0 auto;
  padding: 22px var(--section-gap) 0;
  color: var(--text-1);
}

/* The desk under it: ONE card (the new look). The bar is the card's top half,
   inset --frame from the viewport like /auth/, rounded at the top, in the
   desk's colour with its lit edge; the desk below it (css/components/desk.css)
   is the bottom half. The card is never wider than the content column, and
   the edge has no line along the bottom: the desk continues it. */
.view-scroll:has(> #view-root .desk) > .topnav {
  width: calc(min(var(--content-max-width), 100%) - 2 * var(--frame));
  max-width: none;
  margin: var(--frame) auto 0;
  padding: 10px 40px;
  border-radius: var(--r-desk) var(--r-desk) 0 0;
  background: var(--desk);
  box-shadow: var(--bar-edge);
}

/* A plain bar (Journal, Settings): the page's own colour, a hairline under it,
   and it stays at the top of the scroller while the page scrolls under it (it
   is opaque, so nothing shows through, and it takes its own row, so it covers
   nothing at the top of the page). */
.view-scroll:not(:has(> #view-root .desk)) > .topnav {
  position: sticky;
  top: 0;
  /* Its content sits exactly where the card's does (the card's frame plus its
     40px padding), so the menu does not jump sideways between Planner and
     Journal or Settings (the operator 21.10: "the page changes scale"). */
  padding-right: calc(var(--frame) + 40px);
  padding-bottom: 14px;
  padding-left: calc(var(--frame) + 40px);
  border-bottom: 1px solid var(--line);
  background: var(--color-bg);
}

.topnav__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  margin-right: 8px;
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
}

.topnav__mark {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--tile);
  display: grid;
  place-items: center;
  flex: none;
}

.topnav__mark .brand-signet {
  height: 22px;
}

.topnav__mark:has(.brand-signet) > :not(.brand-signet) {
  display: none;
}

.topnav__mark img {
  width: 22px;
  height: 22px;
}

.topnav__name {
  font-weight: 600;
  letter-spacing: -0.01em;
}

.topnav__links {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.topnav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 42px;
  padding: 0 12px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--nav-rest);
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-spring);
}

/* Under the pointer the grey eases up and the word rises a pixel on the app's
   spring; a press sets it down at once. */
@media (hover: hover) and (pointer: fine) {
  .topnav__link:hover {
    color: var(--nav-on);
    transform: translateY(-1px);
  }

  .topnav__link:active {
    transform: none;
    transition-duration: var(--dur-instant);
  }
}

@media (prefers-reduced-motion: reduce) {
  .topnav__link:hover {
    transform: none;
  }
}

/* No capsule behind the current page (the operator 6.11): the words light up. */
.topnav__link.is-active,
.topnav__menu.is-open > .topnav__menu-btn {
  color: var(--nav-on);
}

.topnav__link:focus-visible {
  outline: 2px solid var(--lead);
  outline-offset: 2px;
}

/* ---- Accounts: a button and the account list under it ---- */
.topnav__menu {
  position: relative;
}

.topnav__count {
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  color: var(--text-3);
}

.topnav__chevron {
  display: inline-flex;
  margin-right: -4px;
  color: var(--text-3);
  transition: transform var(--dur-fast) var(--ease-standard);
}

.topnav__chevron svg {
  width: 14px;
  height: 14px;
}

.topnav__menu.is-open .topnav__chevron {
  transform: rotate(180deg);
}

.topnav__pop {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  width: 320px;
  max-width: calc(100vw - 32px);
  padding: 8px;
  border-radius: 22px;
  background: var(--desk-hi);
  box-shadow: 0 0 0 1px var(--color-border-strong), 0 24px 48px -16px rgba(0, 0, 0, 0.6);
}

.topnav__pop[hidden] {
  display: none;
}

/* The list's own heading folds it in the drawer; the menu button above
   is its heading here, so the list always shows. */
.topnav__pop .acct-nav {
  margin-top: 0;
}

.topnav__pop .acct-nav__head {
  display: none;
}

.topnav__pop .acct-nav.is-folded .acct-nav__list,
.topnav__pop .acct-nav.is-folded .acct-nav__add {
  display: flex;
}

.topnav__pop .acct-nav__list {
  max-height: min(60vh, 440px);
}

.topnav__pop .acct-nav__row,
.topnav__pop .acct-nav__add {
  min-height: 40px;
  border-radius: 12px;
  font-size: 14px;
}

.topnav__pop .acct-nav__row.is-active {
  background: var(--tile);
  box-shadow: none;
}

.topnav__pop .acct-nav__add {
  margin-top: 4px;
  border-top: 1px solid var(--line);
  border-radius: 0 0 12px 12px;
}

/* ---- Right: state pills, light/dark, New Trade ---- */
.topnav__tools {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
  flex: none;
}

/* One family in the corner (the operator 6.11: it read as several things):
   bare icons and plain words at one height, a soft round ground only under
   the pointer. The account is the one small disc, the lead colour marks
   New trade alone. */
.topnav__tools .top-tool,
.topnav__tools #theme-toggle,
.topnav__tools #quick-add-trade {
  width: 36px; /* New trade's own width is below: it opens into a capsule */
  height: 36px;
  min-height: 36px;
  padding: 0;
  border-radius: 50%;
  background: none;
  box-shadow: none;
  color: var(--text-2);
  transition: color var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard);
}

.topnav__tools .top-tool:hover,
.topnav__tools .top-menu.is-open > .top-tool,
.topnav__tools #theme-toggle:hover,
.topnav__tools #quick-add-trade:hover {
  background: var(--tool-bg);
  color: var(--text-1);
  filter: none;
}

.topnav__tools #quick-add-trade {
  width: auto;
  min-width: 36px;
  padding: 0 8px;
  border-radius: 999px;
  color: var(--lead);
}

.topnav__tools #quick-add-trade:hover {
  color: var(--lead);
}

.topnav__tools .top-avatar {
  width: 30px;
  height: 30px;
  margin-left: 6px;
  background: var(--tool-bg);
  color: var(--text-2);
  font-size: 11px;
}

.topnav__tools .demo-tag,
.topnav__tools .readonly-tag,
.topnav__tools .access-pill {
  height: 36px;
  padding: 0 8px;
  background: none;
  box-shadow: none;
  font-size: 12px;
  font-weight: var(--fw-medium);
}

/* The sync state as its dot alone, at every computer width: with the
   label ("Sync problem · 3") beside the demo and read-only tags the links
   ran under the tools from 881 up to about 1440px, and a label that
   changes length moved the whole row. The state stays the button's
   accessible name and tooltip (aria-label/title, sync-status.js); the
   phone's drawer keeps the label. */
.topnav__tools .sync-status {
  flex-basis: auto;
  justify-content: center;
  width: 24px;
  height: 36px;
  min-height: 36px;
  padding: 0;
  border-radius: 50%;
  background: none;
  box-shadow: none;
}

.topnav__tools .sync-status__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* The access pill ("Signed out / Sign in", an offline or expiry state) is the
   one tool that may give way. Without this the links ran on under it from
   about 900px, and with the Ledger link at 1280 to 1440px, so "Settings" lay
   beneath the pill and could not be pressed. While the pill shows, the links
   keep their width and the tools row takes the shortfall out of the pill's
   label (it has an ellipsis); the other tools keep their size. */
.topnav:has(.access-pill:not([hidden])) .topnav__links {
  flex: none;
}

.topnav:has(.access-pill:not([hidden])) .topnav__tools {
  flex: 0 1 auto;
  min-width: 0;
}

.topnav:has(.access-pill:not([hidden])) .topnav__tools > * {
  flex-shrink: 0;
}

.topnav:has(.access-pill:not([hidden])) .topnav__tools > .access-pill {
  flex-shrink: 1;
  min-width: 72px;
}

/* The Prop ledger entry's long name only where the menu has room for it
   (25.10, measured with sync, Demo data, "?" and the module on: "Settings"
   ran under the tools at 900-1000, 1181-1187 and 1401-1420px). */
.topnav__short {
  display: none;
}

@media (max-width: 1440px) {
  .topnav__long {
    display: none;
  }

  .topnav__short {
    display: inline;
  }
}

/* Narrower computers: the menu tightens before anything wraps. The
   widths are measured with every tool shown at once (sync, Demo data,
   Read-only demo) and two-digit account counts: dev/screens/nav.
   Re-measured 21.10 with sync and Demo data: from 1280 to about 1310px
   "Settings" still ran under the tools, so this step started at 1340px; with
   the Help "?" beside the magnifier (21.10) it starts at 1400px. */
@media (max-width: 1400px) {
  .topnav__link {
    padding: 0 12px;
  }

  .topnav__name {
    display: none;
  }

  .topnav__brand {
    margin-right: 0;
  }
}

/* 1190px, not 1120, since the Help "?" (21.10; measured with sync, Demo data
   and "?": "Settings" ran under the tools from 1121 to about 1160px) and the
   Prop ledger entry (25.10, module on: to about 1187px). */
@media (max-width: 1190px) {
  .topnav {
    gap: 8px;
  }

  .topnav__tools {
    gap: 6px;
  }

  .topnav__link {
    padding: 0 8px;
  }

  /* The plus alone; "New Trade" stays its accessible name. */
  .topnav__tools .quick-add-trade__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .topnav__tools #quick-add-trade {
    width: 36px;
    padding: 0;
    justify-content: center;
  }
}

/* A computer with a mouse (26.10, Cezar): New trade rests as its round "+" and
   opens into the capsule with its words under the pointer or the keyboard's
   focus. The menu's widths above were measured with the words shown, so the
   opened capsule still fits; below 1191px it stays the "+" alone. */
@media (min-width: 1191px) and (hover: hover) and (pointer: fine) {
  .topnav__tools #quick-add-trade {
    padding: 0 9px;
    gap: 0;
    transition: padding var(--dur-base) var(--ease-spring), gap var(--dur-base) var(--ease-spring),
      transform var(--dur-instant) var(--ease-spring-snappy);
  }

  .topnav__tools .quick-add-trade__label {
    max-width: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
    transition: max-width var(--dur-base) var(--ease-spring), opacity var(--dur-fast) var(--ease-standard);
  }

  .topnav__tools #quick-add-trade:hover,
  .topnav__tools #quick-add-trade:focus-visible {
    padding: 0 18px;
    gap: var(--space-2);
  }

  .topnav__tools #quick-add-trade:hover .quick-add-trade__label,
  .topnav__tools #quick-add-trade:focus-visible .quick-add-trade__label {
    max-width: 8em;
    opacity: 1;
  }
}

@media (min-width: 1191px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .topnav__tools #quick-add-trade,
  .topnav__tools .quick-add-trade__label {
    transition-duration: 1ms;
  }
}

@media (max-width: 980px) {
  .topnav,
  .view-scroll:has(> #view-root .desk) > .topnav {
    padding-left: 24px;
    padding-right: 24px;
  }

  .view-scroll:not(:has(> #view-root .desk)) > .topnav {
    padding-left: calc(var(--frame) + 24px);
    padding-right: calc(var(--frame) + 24px);
  }

  .topnav__link {
    padding: 0 7px;
    font-size: 13px;
  }

  .topnav__count {
    display: none;
  }
}

/* From 881px the sidebar is gone and the old top bar only carries the
   page's title for assistive technology (TJ.Router writes it). */
@media (min-width: 881px) {
  .sidebar {
    display: none;
  }

  .topbar {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }
}

@media (max-width: 880px) {
  .topnav {
    display: none;
  }
}

/* ---- Main column ---- */

.main {
  grid-column: 1;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  height: var(--topbar-height);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-8);
  background: var(--glass-bg);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--color-border);
  z-index: var(--z-topbar);
  gap: var(--space-3);
}

.topbar__left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.topbar__title {
  font-size: var(--fs-title3);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

/* `scrollbar-gutter: stable` is load-bearing, not polish.
   .view-scroll is the one surface that scrolls in this shell, and its width
   is what every card on every page is measured against. Without a reserved
   gutter, any switch that changes content height also toggles the scrollbar
   and therefore resizes the whole content column: switching the calendar
   from Week (short, no scrollbar) to Month (tall, scrollbar) moved the
   container from 1368px to 1358px, so *every* card on the page re-laid out
   by 10px. That is the "the whole page changes proportions when I switch
   views" symptom, and it applies to every tab/mode switch in the app, not
   just the calendar. Reserving the gutter permanently makes the content
   width independent of content height. */
.view-scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-gutter: stable;
}

/* #view-root is only ever focused programmatically (tabindex -1), as the
   landing spot when the trade drawer closes and its trigger row is gone.
   The global :focus-visible ring would otherwise outline the whole view. */
#view-root:focus {
  outline: none;
}

/* Background scroll lock while any modal is open (see js/components/modal.js
   lockBackgroundScroll/unlockBackgroundScroll) - .view-scroll, not body, is
   the surface that actually scrolls in this app's fixed-shell layout.
   `drawer-open` is the Trade Detail panel's own flag, deliberately separate:
   a modal opened from inside the drawer would otherwise unlock the
   background on its way out while the drawer is still up. */
html.modal-open .view-scroll,
html.drawer-open .view-scroll {
  overflow: hidden;
}

.view-container {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: var(--section-gap);
  padding-bottom: calc(var(--section-gap) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--section-gap);
}

@media (max-width: 560px) {
  .view-container {
    padding: var(--space-4);
    padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
    /* 12px between blocks, not 20: more on one screen (the operator 21.10). */
    gap: var(--space-3);
  }
}

/* ---- Generic grid helpers ---- */

.grid {
  display: grid;
  gap: var(--space-5);
}

.grid-cols-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* 12-column grid for view-level layout (Dashboard). minmax(0, 1fr) for the
   same reason .grid-cols-N uses it: a track must be allowed to shrink below
   its content's min-content width, or long numbers force overflow. */
.grid-12 {
  grid-template-columns: repeat(12, minmax(0, 1fr));
}

.col-span-4 { grid-column: span 4; }
.col-span-8 { grid-column: span 8; }

@media (max-width: 1080px) {
  .grid-cols-3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .col-span-4 {
    grid-column: span 6;
  }

  .col-span-8 {
    grid-column: span 12;
  }
}

@media (max-width: 560px) {
  /* The phone's top bar shares its row with three controls: a step down
     keeps "Growth Simulator" whole. */
  .topbar__title {
    font-size: var(--fs-callout);
  }

  /* With the plan's pill in the row the title was cut to "Jour...". The bottom bar names the
     screen, so the title leaves the screen and stays for assistive technology. */
  .topbar:has(.access-pill:not([hidden])) .topbar__title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
  }

  .grid-cols-3 {
    grid-template-columns: 1fr;
  }

  .col-span-4,
  .col-span-8 {
    grid-column: span 12;
  }
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--stack-gap);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--space-1);
}

.section-header h2 {
  font-size: var(--fs-title3);
}

/* ==========================================================================
   Phone shell (880px and down): the top bar and v12's bottom bar
   (dev/mockups/redesign/v12.html ".mtab"): Portfolio, Journal, "+",
   Analytics, Planner. There is no drawer: the old "More" tab opened one,
   and what it held went where v12 puts it - Settings behind the avatar in
   the top bar, the account list in Settings > Trading accounts (and on
   Portfolio), light/dark in Settings > Appearance, the sync state as a pill
   in the top bar. The <aside class="sidebar"> in index.html is only the
   parking place js/app.js moves the account list and the theme switch back
   to; it is never shown.
   ========================================================================== */

/* A computer has the top menu; the bar and the avatar exist below 881px
   only (see the media query further down). */
.bottom-nav,
.topbar__avatar,
.topbar__add {
  display: none;
}

.sidebar {
  display: none;
}

@media (max-width: 880px) {
  /* New Trade is the top bar's "+" (.topbar__add). */
  #quick-add-trade {
    display: none;
  }

  /* The sync state is a dot (a computer has a round button in the top menu): a
     label beside the tags and the magnifier left the title only "Portfo..."
     on a 375px phone. The state stays the button's accessible name and
     tooltip (sync-status.js). The dot is a 28px target of its own, just left
     of the avatar and clear of its 44px target - not a badge on its corner. */
  /* Not positioned: the search and account popovers (top-menus.css) span the
     bar, so their containing block must be the bar. With this row positioned
     they were laid out against it - 177px wide on a 375px phone. */
  .topbar__actions {
    position: static;
    gap: 0; /* the tools stand close together (the operator 6.11): each one's box is its spacing */
  }

  .topbar__actions .sync-status {
    order: 1;
    flex: none;
    justify-content: center;
    width: 22px;
    height: 28px;
    min-height: 0;
    padding: 0;
    border-radius: 50%;
    background: transparent;
  }

  .topbar__actions .sync-status .sync-status__dot {
    width: 10px;
    height: 10px;
  }

  .topbar__actions [data-top-user-menu] {
    order: 2;
  }

  .topbar__actions .sync-status__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .topbar__actions .demo-tag,
  .topbar__actions .readonly-tag,
  .topbar__actions .demo-signup {
    height: 24px;
    padding: 0 6px;
    font-size: 11px;
  }

  /* A view whose first block is the desk: the bar is the top half of the
     card (the desk below is the bottom half), edge to edge (--frame is 0
     here, css/components/desk.css), reaching up under the status bar like
     every bar. It stays put while the page scrolls under it. */
  .main:has(> .view-scroll > #view-root .desk) > .topbar {
    --bar-bg: var(--desk);
    border: 0;
    background: var(--desk);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* The top bar reaches up under the status bar of an installed app
     (viewport-fit=cover, black-translucent): the inset is its own padding,
     so that strip is the bar's glass, not an empty band (2026-09-27).
     52px, not 56: the phone's bars are a size down (the operator 21.10). */
  .topbar {
    height: calc(52px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) var(--space-4) 0;
  }

  /* v12's avatar: the way into Settings. 36px as drawn, with a 44px touch
     target around it. */
  .topbar__avatar {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--tile-2);
    color: var(--text-1);
  }

  .topbar__avatar::after {
    content: "";
    position: absolute;
    inset: -4px;
  }

  .topbar__avatar svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  .topbar__avatar.is-active {
    box-shadow: inset 0 0 0 1.5px var(--text-3);
  }

  .topbar__avatar:focus-visible {
    outline: 2px solid var(--lead);
    outline-offset: 2px;
  }

  /* ---- Bottom bar ----
     A floating glass pill, v12's: 56px high (--bottom-nav-h; 64 until the
     operator's 21.10 note), round, a hairline ring and a soft shadow; labels 11px; the active item is the one in the full text
     colour (no fill); the "+" is a round lead-colour button. On an iPhone it
     dips into the home-indicator inset, keeping clear of the indicator
     itself; without an inset it sits 8px above the edge (the operator,
     2026-10-05: "the menu pill was too high"). It overlays content, so
     .view-scroll gets bottom padding instead of the bar taking a layout row.
     While the page scrolls down it shrinks to its icons (44px, the labels
     fade and fold away) and the first scroll up brings it back (the
     operator, 2026-10-26; js/app.js bindBottomNavScroll). The labels fold
     together, never one by one - they once dropped at once and jumped
     about under the thumb, one of the operator's notes. New trade is the
     top bar's "+" (.topbar__add). */

  :root {
    --bottom-nav-gap: max(8px, calc(env(safe-area-inset-bottom) - 14px));
    /* 56px, not 64: a size down with the top bar (the operator 21.10). */
    --bottom-nav-h: 56px;
  }

  /* iOS 26, the app opened from the Home Screen (joint test 2026-10-05; the
     same is reported for WebKit by others): with viewport-fit=cover and a
     translucent status bar the viewport comes out about 47pt short of the
     screen, so "bottom" is already that far above the screen's edge and the
     inset must not be added on top. Only lvh still gives the whole height
     there; in an installed app the two are equal everywhere else, and this
     changes nothing. Not measured on a device yet (Settings, Data shows the
     numbers it needs: "This screen"). */
  @media (display-mode: standalone) {
    @supports (height: 100lvh) {
      :root {
        --bottom-nav-gap: max(8px, calc(env(safe-area-inset-bottom) - 14px - (100lvh - 100dvh)));
      }
    }
  }

  .bottom-nav {
    position: fixed;
    /* Never wider than a phone's bar: on a tablet it stays a pill in the middle. */
    left: max(14px, calc(50% - 260px));
    right: max(14px, calc(50% - 260px));
    bottom: var(--bottom-nav-gap);
    height: var(--bottom-nav-h);
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-around;
    padding: 0 6px;
    border-radius: 28px;
    background: var(--glass-bg);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    box-shadow: 0 0 0 1px var(--line), 0 16px 30px -10px rgba(0, 0, 0, 0.6);
    z-index: var(--z-topbar);
    transition: height 200ms var(--ease-standard);
  }

  .bottom-nav.is-compact {
    height: 44px;
  }

  .bottom-nav.is-compact .bottom-nav__item {
    gap: 0;
  }

  .bottom-nav.is-compact .bottom-nav__label {
    max-height: 0;
    opacity: 0;
  }

  /* The bar floats above content, so the scroll area needs to clear it:
     its height, its gap from the edge, and a little air. */
  .view-scroll {
    padding-bottom: calc(var(--bottom-nav-h) + var(--bottom-nav-gap) + var(--space-3));
  }

  .view-container {
    padding-bottom: var(--space-4);
  }

  .bottom-nav__item {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    /* Density never shrinks tap targets - 44px stays 44px. */
    min-height: 44px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: none;
    color: var(--text-3);
    font-size: 11px;
    font-weight: var(--fw-medium);
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-standard);
  }

  .bottom-nav__item.is-active {
    color: var(--text-1);
    background: none;
  }

  .bottom-nav__item:focus-visible,
  .topbar__add:focus-visible {
    outline: 2px solid var(--lead);
    outline-offset: 2px;
  }

  .bottom-nav__icon {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  .bottom-nav__label {
    position: relative;
    max-height: 14px;
    overflow: hidden;
    line-height: 1.2;
    white-space: nowrap;
    transition: max-height 200ms var(--ease-standard), opacity 140ms var(--ease-standard);
  }

  @media (prefers-reduced-motion: reduce) {
    .bottom-nav,
    .bottom-nav__label {
      transition: none;
    }
  }

  /* New trade, beside the magnifier: 36px as drawn, the lead colour, with a
     44px touch target around it (as the avatar). */
  .topbar__add {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 34px;
    height: 36px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--lead);
  }

  /* One family with the computer's corner (the operator 6.11). */
  .topbar__actions .top-tool,
  .topbar__actions #theme-toggle {
    width: 34px;
    height: 36px;
    min-width: 0;
    background: none;
    box-shadow: none;
  }

  /* 44px high by itself: its ::after is the tooltip (css/components/tooltip.css), which a tap would leave standing. */
  .topbar__actions #theme-toggle {
    flex: none;
    height: 44px;
    padding: 0;
    color: var(--text-2);
  }

  .topbar__actions #theme-toggle::after {
    display: none;
  }

  /* The narrowest phones: the access note and the demo word together run off the edge. */
  @media (max-width: 359px) {
    .topbar__actions:has(.access-pill:not([hidden])) .demo-tag {
      display: none;
    }
  }

  .topbar__actions .access-pill {
    padding: 0 4px;
    background: none;
  }

  .topbar__add::after {
    content: "";
    position: absolute;
    inset: -4px;
  }

  .topbar__add svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  :root[data-look="tapebook"] .bottom-nav .bottom-nav__item.is-active .bottom-nav__label::before {
    content: none;
  }
}
