/* Built by scripts/build.py from src/css/*.css — edit those files, not this one. */
  :root {
    --radius: 14px;
    --radius-sm: 10px;
    --radius-lg: 18px;
    --shadow: 0 1px 2px rgba(20,20,40,0.04), 0 6px 20px rgba(20,20,40,0.06);
    --shadow-lg: 0 20px 60px rgba(20,20,40,0.25);
    /* Kept in sync with the active project's color by renderProjectIdentityBar();
       drives --project-tint below. Falls back to a fixed purple before JS runs or
       when no project exists. */
    --project-color: #6c5ce7;
    /* ---- Mobile card design system ----
       Shared by every mobile card (Links, To-Do, Today, Notes) so indents and gaps
       mean the same thing everywhere. */
    --card-pad-y: 12px;       /* card row vertical padding */
    --card-pad-x: 6px;        /* card row horizontal padding (both edges) */
    --card-lead-w: 40px;      /* leading badge/checkbox slot width (col-type, col-check) */
    --card-col-gap: 10px;     /* gap between the leading slot and the title */
    --card-content-indent: calc(var(--card-lead-w) + var(--card-col-gap)); /* where title/priority text starts */
    --card-icon-gap: 2px;     /* gap between action icons in a card's icon row (.row-actions) */
  }
  [data-theme="light"] {
    --bg: #f2f3f9;
    --project-tint: color-mix(in srgb, var(--project-color) 12%, white);
    --bg-elevated: #ffffff;
    --bg-soft: #eceefc;
    --bg-soft-2: #f6f7fd;
    --border: #e3e5f2;
    --text: #14151f;
    --text-dim: #6c6f85;
    --text-faint: #a2a5b8;
    --primary: #6c5ce7;
    --primary-soft: #ecebfe;
    --primary-text: #4b3fd6;
    --danger: #ef4444;
    --danger-soft: #fde8e8;
    --success: #16a34a;
    --success-soft: #e5f7ea;
    --warn: #f59e0b;
    --warn-soft: #fef3d9;
    --overlay: rgba(20,18,35,0.45);
  }
  [data-theme="dark"] {
    --bg: #13141c;
    --project-tint: color-mix(in srgb, var(--project-color) 24%, #13141c);
    --bg-elevated: #1b1c27;
    --bg-soft: #232433;
    --bg-soft-2: #1f2030;
    --border: #2d2e3f;
    --text: #f0f1fa;
    --text-dim: #b3b5cc;
    --text-faint: #8b8ea8;
    --primary: #9089ff;
    --primary-soft: #2a2850;
    --primary-text: #b3adff;
    --danger: #f87171;
    --danger-soft: #3a2229;
    --success: #4ade80;
    --success-soft: #1d3327;
    --warn: #fbbf24;
    --warn-soft: #3a2e12;
    --overlay: rgba(0,0,0,0.6);
  }
  * { box-sizing: border-box; }
  html, body { height: 100%; overscroll-behavior-x: none; }
  body {
    margin: 0;
    font-family: 'Avenir Next', Avenir, -apple-system, BlinkMacSystemFont, sans-serif;
    background: var(--project-tint, var(--bg));
    color: var(--text);
    transition: background .2s, color .2s;
    min-width: 1080px;
  }
  /* The 1080px floor above is right for the desktop layout, but it's
     also the actual reason a phone had to zoom out to show this page at
     all — override it to nothing once the mobile shell is active. */
  body.mobile-view { min-width: 0; }
  a { color: inherit; }
  .icon { width: 26px; height: 26px; display: inline-block; vertical-align: middle; flex-shrink: 0; }
  .icon-sm { width: 21px; height: 21px; }
  .icon-lg { width: 32px; height: 32px; }
  /* Stroke-width compensation for the linkPlus/notePlus icons' scaled <g> (see
     ICONS): ambient 1.8 divided by the scale factor, or its stroke renders thin.
     A thinner-ambient version for cards/tables sits next to .row-actions .icon-sm. */
  .icon-scale-72 { stroke-width: 2.5; }
  .icon-scale-62 { stroke-width: 2.9; }
  button {
    font-family: inherit;
    cursor: pointer;
    border: 1px solid var(--border);
    background: var(--bg-elevated);
    color: var(--text);
    border-radius: var(--radius-sm);
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 500;
    transition: background .15s, transform .1s, border-color .15s, color .15s;
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  button:hover { background: var(--bg-soft); }
  button:active { transform: scale(0.96); }
  button.icon-only { padding: 8px; }
  button.primary-btn { background: var(--primary); border-color: var(--primary); color: #fff; }
  button.primary-btn:hover { filter: brightness(1.08); }
  button.ghost-btn { border-color: transparent; background: none; color: var(--text-dim); }
  button.ghost-btn:hover { background: var(--bg-soft); color: var(--text); }
  button.danger-btn { color: var(--danger); border-color: var(--danger-soft); }
  button.danger-btn:hover { background: var(--danger-soft); }
  button.danger-fill-btn { background: var(--danger); border-color: var(--danger); color: #fff; }
  button.danger-fill-btn:hover { filter: brightness(1.08); }
  input, textarea, select {
    font-family: inherit;
    background: var(--bg-elevated);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 8px 11px;
    font-size: 13.5px;
  }
  input:focus, textarea:focus, select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
  }
  select {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236c6f85' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 13px;
    padding-right: 30px;
  }
  [data-theme="dark"] select {
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239a9cb5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  }

  /* ---- Topbar ---- */
  /* position:fixed, not sticky: sticky stops floating after enough DOM mutations
     (drag-reorder, table re-renders), on desktop too. Fixed takes the header out
     of flow, so content needs a top offset sized to its variable height: see
     --mobile-header-h, set by syncMobileHeaderOffset() (runs on desktop too). */
  #appHeader {
    position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  }
  #topbar {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 28px;
    background: var(--bg-elevated);
    border-bottom: 1px solid var(--border);
  }
  .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 16px; white-space: nowrap; letter-spacing: .5px; }
  /* Purple TEST tag next to the name on the test site only (host contains "--"). */
  .env-badge { background: var(--primary); color: #fff; font-size: 10px; font-weight: 500; letter-spacing: .8px; padding: 3px 7px; border-radius: 6px; line-height: 1; }
  body:not(.mobile-view) .env-badge { font-size: 11px; padding: 4px 8px; }
  .brand .brand-dot { width: 18px; height: 18px; border-radius: 6px; background: linear-gradient(135deg, var(--primary), #ff7ac6); flex-shrink:0; }
  /* Desktop only: logo roughly the size of the topbar's page buttons and
     the project dropdown (~34px tall), text scaled up to match. */
  body:not(.mobile-view) .brand { gap: 11px; font-size: 22px; }
  body:not(.mobile-view) .brand .brand-dot { width: 22px; height: 22px; border-radius: 7px; }
  .search-wrap { position: relative; flex: 0 1 460px; max-width: 460px; }
  /* Direct-child combinator (>) on purpose: a descendant ".search-wrap .icon" also
     matched the clear button's nested icon and gave it position:absolute; left:12px,
     which defeated the button's flex centering (the "X not centered" bug). */
  .search-wrap > .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-faint); pointer-events: none; }
  /* Right padding/inset match the mobile values below, so the clear button sits
     as balanced from the right border as the search icon does from the left. */
  #globalSearch { width: 100%; height: 36px; padding: 0 36px 0 42px; box-sizing: border-box; }
  .search-clear-btn {
    position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0; border-radius: 50%;
    background: none; border: none; color: var(--text-faint); cursor: pointer;
  }
  /* 16px, not 15px: the button is 22px (even); an odd-sized icon centers on a
     half-pixel and can round off by 1px. 16px keeps a whole 3px per side. */
  .search-clear-btn .icon { width: 16px; height: 16px; }
  .search-clear-btn:hover { background: var(--bg-soft); color: var(--text); }
  .search-clear-btn.hidden { display: none; }
  .topbar-divider { width: 1px; height: 26px; background: var(--border); flex-shrink: 0; }
  .quick-launch { display: flex; align-items: center; gap: 9px; flex-shrink: 0; }
  .quick-launch-item {
    display: flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; border-radius: var(--radius-sm);
    text-decoration: none; color: var(--text); background: transparent;
    flex-shrink: 0; transition: transform .1s, background .15s;
  }
  .quick-launch-item:hover { background: var(--bg-soft); transform: translateY(-1px); }
  .quick-launch-item svg { width: 19px; height: 19px; }
  .quick-launch-item img { width: 20px; height: 20px; object-fit: contain; display: block; }
  /* Shortcut without an uploaded icon: a coloured letter tile (2026-10-20 —
     replaced Google's favicon service, which learned every shortcut's site). */
  .ql-letter { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 6px; color: #fff; font-size: 11px; font-weight: 700; line-height: 1; flex-shrink: 0; }
  .more-quick-launch-row .ql-letter { width: 22px; height: 22px; font-size: 12px; }
  .settings-quicklink-row .ql-letter { width: 16px; height: 16px; font-size: 9.5px; border-radius: 4px; }
  /* Save status line in Settings → account (see setCloudSyncStatus). */
  #accountSyncStatus.sync-saved { color: var(--success); }
  #accountSyncStatus.sync-offline { color: var(--warn); }
  #accountSyncStatus.sync-error { color: var(--danger); }
  /* Private vault (2026-10-20) */
  .vault-block { flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-width: 0; }
  .vault-status { font-size: 12.5px; color: var(--text-dim); }
  .vault-status.on { color: var(--success); font-weight: 600; }
  .vault-actions { display: flex; flex-wrap: wrap; gap: 6px; }
  /* Whole key on one line (2026-10-20): no wrapping, font scales down on narrow phones. */
  .vault-key { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: clamp(11px, 3.4vw, 15px); letter-spacing: .3px; padding: 12px 10px; border: 1px dashed var(--border); border-radius: var(--radius-sm); background: var(--bg-soft-2); white-space: nowrap; overflow-x: auto; text-align: center; user-select: all; }
  /* Full-width, equal-size buttons in the vault screens. */
  .vault-btn { width: 100%; justify-content: center; padding: 11px 14px; font-size: 14px; }
  .vault-status .vault-status-sub { display: block; }
  .vault-status .icon { vertical-align: -3px; margin-right: 4px; }
  /* Finish stays an inactive outline until "I've saved my recovery key" is ticked. */
  #vaultFinishBtn:disabled { background: transparent; color: var(--primary-text); border-color: var(--primary); opacity: .55; cursor: not-allowed; filter: none; transform: none; }
  .vault-step { display: flex; flex-direction: column; gap: 10px; }
  .vault-msg { font-size: 12.5px; min-height: 14px; color: var(--danger); }
  .vault-check { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; margin-top: 16px; }
  .vault-check input { width: 16px; height: 16px; margin: 0; accent-color: var(--primary); }
  .vault-input { width: 100%; }
  .vault-points { list-style: none; margin: 0 0 6px; padding: 0; display: flex; flex-direction: column; gap: 8px; font-size: 13px; color: var(--text-dim); }
  .vault-points li { display: flex; gap: 8px; align-items: flex-start; }
  .vault-points .icon { flex-shrink: 0; color: var(--text); margin-top: 1px; }
  .vault-recommended { font-size: 12px; font-weight: 600; color: var(--primary-text); margin-left: 6px; }
  .vault-later { font-size: 11.5px; color: var(--text-faint); text-align: center; }
  /* Privacy notice + delete account (2026-10-20) */
  .privacy-text { font-size: 13px; color: var(--text-dim); line-height: 1.5; max-height: 60vh; overflow-y: auto; }
  .privacy-text h3 { font-size: 13px; color: var(--text); margin: 14px 0 4px; }
  .privacy-text p, .privacy-text ul { margin: 0 0 6px; }
  .privacy-text ul { padding-left: 18px; }
  /* Demo mode (2026-10-21) */
  .demo-try-btn { width: 100%; justify-content: center; margin-top: 4px; }
  .demo-banner { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 7px 16px; background: var(--warn-soft); color: var(--text); font-size: 13px; }
  .demo-banner button { padding: 5px 12px; font-size: 12.5px; white-space: nowrap; }
  body.demo-mode .demo-hide, body:not(.demo-mode) .demo-only { display: none !important; }
  .auth-privacy-line { font-size: 11.5px; color: var(--text-faint); text-align: center; margin-top: 6px; }
  .link-btn { background: none; border: none; padding: 0; color: var(--primary-text); font-size: inherit; font-weight: 600; text-decoration: underline; cursor: pointer; display: inline; }
  .link-btn:hover { background: none; }
  .danger-zone-actions { display: flex; flex-wrap: wrap; gap: 6px; }
  /* Only icons explicitly marked "mono" invert in dark mode — a plain
     black-on-transparent icon (the 2 hand-authored defaults, or anything a
     user uploads) needs it; a real fetched favicon (usually already
     colorful) must NOT be inverted or it'll look broken. */
  [data-theme="dark"] .quick-launch-item img.ql-icon-mono { filter: invert(1); }
  .topbar-actions { display: flex; gap: 8px; }
  /* Desktop: the search -> quick launch -> project switcher -> actions group
     anchors to the header's right edge. The single auto margin sits on the first
     element (.search-wrap), so the free space is between the brand and the group. */
  .search-wrap { margin-left: auto; }

  /* ---- Pinned bar ---- */
  #pinnedBar {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 7px 28px;
    background: var(--bg-soft-2);
    border-bottom: 1px solid var(--border);
  }
  #pinnedBar.hidden { display: none; }
  #pinnedBar .pinned-label { display:flex; flex-direction: column; align-items:center; line-height: 1.05; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); white-space: nowrap; margin-right: 10px; margin-top: 4px; text-align: center; flex-shrink: 0; }
  /* Wraps to additional rows once pins overflow the width, rather than
     forcing a horizontal scrollbar into this slim strip. */
  #pinnedList { display: flex; flex-wrap: wrap; gap: 8px; }
  /* Mobile (2026-10-20): the bar is a collapsible "Pinned Links (n)"
     section, collapsed by default and again every time you come back to
     the Links screen (mobilePinsOpen). Desktop keeps the always-open strip. */
  /* body:not(.mobile-view) on purpose: the button also carries
     .todo-done-toggle, whose later display rule otherwise wins and showed
     it on desktop (2026-10-20 fix). */
  body:not(.mobile-view) .pinned-toggle { display: none; }
  body.mobile-view #pinnedBar { flex-direction: column; align-items: stretch; gap: 6px; padding: 4px 14px 6px; }
  body.mobile-view #pinnedBar .pinned-label { display: none; }
  body.mobile-view .pinned-toggle { display: flex; }
  body.mobile-view #pinnedBar:not(.pins-open) #pinnedList { display: none; }
  /* Phone (2026-10-23): pin icon + label line up with the Links heading
     below; one pinned link per row, full width (tidier, easier to tap). */
  body.mobile-view #pinnedBar .pinned-toggle { padding-left: 0; padding-right: 0; }
  body.mobile-view #pinnedList { flex-direction: column; flex-wrap: nowrap; gap: 6px; }
  body.mobile-view .pin-chip { width: 100%; box-sizing: border-box; padding: 10px 8px 10px 12px; font-size: 14px; }
  body.mobile-view .pin-chip-link { flex: 1; }
  body.mobile-view .pin-chip-link > span:last-child { overflow: hidden; text-overflow: ellipsis; }
  body.mobile-view .pin-chip .unpin { padding: 4px; }
  .pin-chip {
    display: flex; align-items: center; gap: 6px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 3px 6px 3px 10px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    box-shadow: var(--shadow);
    cursor: pointer;
  }
  .pin-chip-link { display: flex; align-items: center; gap: 6px; text-decoration: none; color: inherit; min-width: 0; }
  .pin-chip-link:hover { color: var(--primary-text); }
  .pin-chip .dot { width: 8px; height: 8px; border-radius: 2.5px; flex-shrink: 0; }
  .pin-chip .unpin { display:flex; opacity: .45; cursor: pointer; border-radius: 50%; padding: 2px; }
  .pin-chip .unpin:hover { opacity: 1; background: var(--danger-soft); color: var(--danger); }

  /* ---- Project switcher (in topbar, replaces the old tabs row) ---- */
  .project-switcher { position: relative; flex-shrink: 0; }
  .project-switcher-btn {
    display: flex; align-items: center; gap: 8px;
    height: 36px; /* matches #globalSearch's height exactly */
    padding: 0 10px 0 8px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--bg-soft-2);
    color: var(--text-dim);
    font-weight: 600;
    font-size: 12.5px;
    white-space: nowrap;
    cursor: pointer;
    max-width: 220px;
  }
  .project-switcher-btn .dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
  .project-switcher-btn .project-switcher-name { overflow: hidden; text-overflow: ellipsis; }
  .project-switcher-btn .icon { flex-shrink: 0; color: var(--text-faint); margin-left: -2px; }
  .project-switcher-menu {
    position: absolute; top: calc(100% + 6px); left: 0;
    min-width: 220px; max-width: 280px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: 6px;
    z-index: 70;
  }
  .project-switcher-menu.hidden { display: none; }
  .project-switcher-item {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    font-weight: 500;
    color: var(--text-dim);
    cursor: pointer;
  }
  .project-switcher-item:hover { background: var(--bg-soft); color: var(--text); }
  .project-switcher-item.active { color: var(--text); background: var(--bg-soft-2); }
  .project-switcher-item .dot { width: 8px; height: 8px; border-radius: 2.5px; flex-shrink: 0; }
  /* Reorder projects (2026-10-22): the menu's "Reorder projects" button shows a
     ≡ handle on each project; drag the handle (mouse or finger). */
  .project-switcher-item .proj-grip { margin-left: auto; padding: 6px 4px 6px 12px; display: flex; color: var(--text-faint); cursor: grab; touch-action: none; }
  .project-switcher-item.dragging { background: var(--bg-soft-2); box-shadow: var(--shadow); position: relative; z-index: 1; }
  .project-switcher-item.dragging .proj-grip { cursor: grabbing; }
  .project-switcher-new.on { background: var(--primary-soft); }
  .project-switcher-divider { border-top: 1px solid var(--border); margin: 6px 2px; }
  .project-switcher-new { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 600; color: var(--primary-text); cursor: pointer; }
  .project-switcher-new:hover { background: var(--primary-soft); }
  /* "All Projects" view mode (2026-10-08, see viewAllProjects): a black
     swatch instead of a project color, and a plain page background
     (white; the neutral surface in dark mode) instead of a project tint.
     In dark mode the black swatch gets a faint ring so it stays visible. */
  .dot.dot-all { background: #000; }
  body[data-theme="dark"] .dot.dot-all { box-shadow: 0 0 0 1px var(--text-faint); }
  body.view-all { background: #ffffff; }
  body[data-theme="dark"].view-all { background: var(--bg); }
  /* A create popup's Project picker left blank (All Projects mode opens it
     blank and required) — flagged on Save instead of silently guessing. */
  select.field-invalid { border-color: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
  [data-action="jump-project"] { cursor: pointer; }
  .project-switcher-heading { padding: 6px 10px 4px; font-size: 11px; font-weight: 600; letter-spacing: .4px; text-transform: uppercase; color: var(--text-faint); }

  /* ---- Project identity bar (sits between the pinned bar and the table) ---- */
  #projectIdentityBar {
    display: flex; align-items: center; gap: 10px;
    padding: 14px 28px 6px;
    /* #appHeader is fixed (out of flow) on desktop too now — see its
       comment above. */
    padding-top: calc(var(--mobile-header-h, 70px) + 14px);
  }
  #projectIdentityBar.hidden { display: none; }
  .project-identity-icon { width: 22px; height: 22px; border-radius: 7px; flex-shrink: 0; }
  .project-identity-name { font-weight: 500; font-size: 22px; letter-spacing: -.2px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* ---- Main ---- */
  #mainContent { padding: 14px 28px 90px; }
  /* #projectIdentityBar already carries the fixed-header offset when showing;
     #mainContent only needs its own copy when that bar is .hidden. Applying both
     unconditionally would double the offset (padding adds to the box height). */
  #projectIdentityBar.hidden + #mainContent {
    padding-top: calc(var(--mobile-header-h, 70px) + 14px);
  }

  .toolbar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
  .add-link-box {
    flex: 1 1 auto; min-width: 0;
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2px 4px 2px 14px;
    box-shadow: var(--shadow);
  }
  .add-link-box .icon { color: var(--text-faint); }
  /* Paste box + Upload button side by side (the line is the drop area). */
  .add-link-line { flex: 1 1 50%; min-width: 280px; display: flex; align-items: stretch; gap: 8px; }
  .add-link-line .upload-file-btn { flex-shrink: 0; display: inline-flex; align-items: center; gap: 6px; padding: 0 14px; font-size: 12.5px; white-space: nowrap; box-shadow: var(--shadow); }
  .add-link-line .upload-file-btn .icon { display: inline-block; }
  .add-link-line.drop-over .add-link-box, .add-link-line.drop-over .upload-file-btn { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
  /* Upload-to-Drive sheet: a small panel above the page (not a popup, so it
     never fights the popup/back-button tracking). */
  .upload-panel { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(440px, calc(100vw - 24px)); z-index: 130; box-shadow: 0 0 0 100vmax rgba(20,18,40,.35), var(--shadow-lg); background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; font-size: 13px; }
  .upload-panel h4 { margin: 0 0 6px; font-size: 14px; display: flex; align-items: center; gap: 8px; }
  .upload-panel p { margin: 0 0 10px; color: var(--text-dim); line-height: 1.45; }
  .upload-panel .up-file { font-weight: 600; color: var(--text); word-break: break-all; }
  .upload-panel .up-actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
  .upload-panel .up-bar { height: 6px; border-radius: 3px; background: var(--bg-soft-2); overflow: hidden; margin: 4px 0 6px; }
  .upload-panel .up-bar > i { display: block; height: 100%; width: 0; background: var(--primary); transition: width .15s; }
  .upload-panel .up-err { color: var(--danger); }
  .upload-panel .up-newfolder { margin: 0 0 10px; }
  .upload-panel .up-newfolder input { width: 100%; box-sizing: border-box; }
  .add-link-box input { flex: 1; border: none; box-shadow: none; padding: 8px 4px; font-size: 12.5px; }
  .add-link-box input:focus { box-shadow: none; }
  .status-pill {
    padding: 6px 13px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg-elevated);
    color: var(--text-dim);
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
  }
  .status-pill.active-filter { background: var(--primary); border-color: var(--primary); color: #fff; }
  /* Filter input + existing-tags picker + type dropdown share their own flex row so
     the 46:27:27 split is relative to each other only. Unitless grow RATIOS, not
     percentage flex-basis: percentages summing to 100% leave no room for the
     row's gap and the last item wraps. */
  .link-filter-row { display: flex; align-items: center; gap: 10px; flex: 1 1 50%; min-width: 260px; }
  #typeFilterSelect { font-size: 12.5px; padding-top: 9px; padding-bottom: 9px; flex: 27 1 110px; min-width: 110px; }
  #tagSelectFilter { font-size: 12.5px; padding-top: 9px; padding-bottom: 9px; flex: 27 1 110px; min-width: 110px; }
  .tag-filter-wrap { position: relative; flex: 46 1 160px; min-width: 160px; }
  .tag-filter-wrap input { width: 100%; padding-left: 38px; padding-right: 30px; font-size: 12.5px; padding-top: 9px; padding-bottom: 9px; }
  /* Direct-child combinator (>), not a descendant selector: a descendant rule
     leaks onto nested icons (see the search bar's clear button). */
  .tag-filter-wrap > .icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-faint); pointer-events: none; }
  .tag-filter-clear-btn {
    position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0; border-radius: 50%;
    background: none; border: none; color: var(--text-faint); cursor: pointer;
  }
  .tag-filter-clear-btn .icon { width: 16px; height: 16px; }
  .tag-filter-clear-btn:hover { background: var(--bg-soft); color: var(--text-dim); }

  /* ---- Link table ---- */
  .table-wrap {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow);
  }
  table.link-table { width: 100%; border-collapse: collapse; }
  .link-table thead th {
    text-align: left;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-dim);
    font-weight: 700;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-soft-2);
    white-space: nowrap;
  }
  .link-table th.sortable { cursor: pointer; user-select: none; }
  .link-table th.sortable:hover { color: var(--text); }
  .sort-indicator { margin-left: 4px; font-size: 9px; color: var(--primary-text); }
  .link-table tbody tr { border-bottom: 1px solid var(--border); transition: background .1s; cursor: pointer; }
  .link-table tbody tr:last-child { border-bottom: none; }
  .link-table tbody tr:hover { background: var(--bg-soft-2); }
  .link-table tbody tr.drag-ghost { opacity: .3; }
  .link-table tbody tr.status-archived { opacity: .55; }
  .link-table td { padding: 9px 12px; vertical-align: middle; font-size: 13px; }
  .col-drag { width: 32px; padding-right: 0 !important; }
  .col-drag .drag-handle { cursor: grab; color: var(--text-faint); }
  .col-type { width: 54px; }
  .col-title { min-width: 260px; max-width: 380px; }
  .col-desc { min-width: 160px; }
  .col-tags { min-width: 100px; max-width: 150px; }
  .col-date { width: 92px; white-space: nowrap; color: var(--text-faint); font-size: 12.5px; }
  .col-project { width: 130px; }
  .col-actions { width: 160px; text-align: right; white-space: nowrap; }
  /* Only the header LABEL is left-aligned; the row icons stay grouped at the
     column's right edge. Qualified as th.col-actions so it out-specifies
     .todo-table .col-actions below. */
  .link-table th.col-actions, .todo-table th.col-actions { text-align: left; }
  .type-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 34px;
    padding: 4px 6px;
    border-radius: 6px;
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: .02em;
  }
  .link-title { font-size: 13.5px; font-weight: 600; text-decoration: none; word-break: break-word; display: block; }
  .link-title:hover { color: var(--primary-text); }
  .link-desc-cell {
    font-size: 12.5px; color: var(--text-dim);
    padding: 3px 5px; min-height: 18px; line-height: 1.4;
    /* pre-line keeps the line breaks typed in the edit popup (Enter) while
       still collapsing extra spaces and wrapping long lines normally. */
    white-space: pre-line;
  }
  /* Bullet / numbered lists rendered from "* item" / "1. item" lines
     (see richTextHtml). white-space reset because parents use pre-line/pre-wrap. */
  .rt-list { margin: 2px 0; padding-left: 20px; white-space: normal; }
  /* padding-left = the gap between the bullet/number and the text
     (2px read as cramped; 0.45em ≈ one extra space, 2026-10-16). */
  .rt-list li { margin: 1px 0; padding-left: 0.45em; }
  /* Nested levels (2026-10-21): browsers already step • → ◦ → ▪ for nested
     <ul>; keep sub-lists tight. */
  .rt-list .rt-list { margin: 1px 0; }
  /* Set per level explicitly: note cards are themselves <li>s inside a
     list, so the browser's own nesting styles would start at ◦. */
  ul.rt-list { list-style-type: disc; }
  .rt-list ul.rt-list { list-style-type: circle; }
  .rt-list .rt-list ul.rt-list { list-style-type: square; }
  ol.rt-list { list-style-type: decimal; }
  /* One row per text line, so blank lines always show (also next to lists). */
  .rt-line { min-height: 1lh; }
  /* Phone: outdent/indent buttons under a text box while the cursor is on a
     list line (no Tab key on phone keyboards). */
  .list-indent-row.hidden { display: none !important; }
  .list-indent-bar { display: flex; gap: 6px; }
  .list-indent-bar button { padding: 6px 12px; }
  .tags-cell { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
  .tag-chip { font-size: 10px; font-weight: 600; background: var(--bg-soft); border-radius: 6px; padding: 2px 9px; color: var(--text-dim); }
  .project-badge { display: flex; align-items: center; gap: 5px; font-weight: 600; cursor: pointer; font-size: 12px; }
  .project-badge .dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink:0; }
  .row-actions { display: inline-flex; gap: calc(var(--card-icon-gap) * 1.3); }
  .row-actions button { padding: 6px; border: none; background: none; color: var(--text-faint); border-radius: 7px; }
  .row-actions button:hover { background: var(--bg-soft); color: var(--text); }
  /* Row-action icons on Links/To-Do/Notes cards and Today: 20% larger than the
     shared 21px .icon-sm, with more gap and 6px button padding (bigger tap target).
     Scoped to .row-actions so the Dictate/mic and "+ Add" buttons, which use the
     same icon-sm class, stay as they are. */
  .row-actions .icon-sm { width: 25.2px; height: 25.2px; }
  /* stroke-width 1.5 (1.8 / 1.2) undoes the 20% enlargement: stroke scales with
     the SVG viewBox, so the bigger icons would otherwise read heavier than the
     top bar's and bottom nav's. Same footprint, same effective line weight. */
  .row-actions .icon-sm { stroke-width: 1.5; }
  /* linkPlus/notePlus build their half from a scaled copy of the glyph, so the
     stroke needs compensation = ambient stroke-width / scale factor. Inside
     .row-actions the ambient is 1.5, hence 1.5/0.72 and 1.5/0.62 here. */
  .row-actions .icon-sm .icon-scale-72 { stroke-width: 2.083; }
  .row-actions .icon-sm .icon-scale-62 { stroke-width: 2.419; }
  .row-actions button.on { color: var(--primary-text); background: var(--primary-soft); }
  /* Pin/Archive are plain toggle buttons where a fill makes sense; the
     connections icon and check button are ghost icon-only buttons and should tint
     only the glyph. Wins over the rule above on specificity alone. */
  .row-actions button.ghost-btn.icon-only.on { background: none; }
  .row-actions button.danger-action { color: var(--danger); }
  .row-actions button.danger-action:hover { background: var(--danger-soft); color: var(--danger); }

  .notes-toggle { display: flex; width: fit-content; font-size: 12.5px; font-weight: 600; color: var(--text-dim); background: none; border: none; padding: 6px 0; margin-top: 14px; }
  .notes-toggle .icon { transition: transform .15s; }
  .notes-toggle.open .icon { transform: rotate(180deg); }
  .notes-area {
    width: 100%;
    min-height: 90px;
    resize: vertical;
    font-size: 13px;
    background: var(--bg-soft-2);
    border-radius: var(--radius);
  }
  .notes-area.hidden { display: none; }

  /* ---- Per-project priority To-Do ---- */
  .project-todo-add {
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2px 4px 2px 14px;
    box-shadow: var(--shadow);
    margin-bottom: 12px;
  }
  .project-todo-add .icon { color: var(--text-faint); }
  .project-todo-add input { flex: 1; border: none; box-shadow: none; padding: 8px 4px; font-size: 12.5px; }
  .project-todo-add input:focus { box-shadow: none; }
  .todo-list-inline { list-style: none; margin: 0; padding: 12px 14px; display: flex; flex-direction: column; gap: 7px; }
  .todo-done-bar { border-top: 1px dashed var(--border); margin-top: 2px; }
  .todo-done-toggle {
    display: flex; align-items: center; gap: 6px; width: 100%;
    font-size: 12.5px; font-weight: 500; color: var(--text-dim);
    background: none; border: none; padding: 10px 14px; text-align: left;
  }
  .todo-done-toggle .icon { transition: transform .15s; }
  .todo-done-toggle.open .icon { transform: rotate(180deg); }
  /* Mobile Pinned Links row (2026-10-20): pin icon + bold label on the
     left, chevron anchored right; the whole row is the button. Only the
     chevron turns when open (the rule above would flip the pin too). */
  .pinned-toggle .pinned-toggle-label { font-weight: 700; }
  .pinned-toggle .pinned-toggle-count { font-weight: 500; }
  .pinned-toggle .pinned-chevron { margin-left: auto; display: inline-flex; }
  .pinned-toggle.open .pinned-pin .icon { transform: none; }
  .drag-handle-spacer { width: 21px; flex-shrink: 0; display: inline-block; }

  .empty-hint { font-size: 13px; color: var(--text-faint); padding: 34px 4px; text-align: center; }
  .empty-state { text-align: center; padding: 100px 20px; color: var(--text-faint); }
  .empty-state .icon-lg { width: 40px; height: 40px; margin-bottom: 10px; }

  /* ALL-CAPS like every other section/table heading (.panel-block h3,
     .link-table thead th, connections-popup section names). */
  .search-results-header { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; font-size: 15px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; }

  /* ---- Side drawer (todo + recents) ---- */
  #sidePanelBackdrop {
    position: fixed; inset: 0; background: var(--overlay);
    z-index: 140; opacity: 0; pointer-events: none; transition: opacity .2s;
  }
  #sidePanelBackdrop.visible { opacity: 1; pointer-events: auto; }
  .side-drawer {
    position: fixed; top: 0; right: -52vw; width: 50vw; min-width: 380px; height: 100vh;
    background: var(--bg-elevated);
    border-left: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
    padding: 22px;
    display: flex; flex-direction: column; gap: 24px;
    overflow-y: auto;
    z-index: 150;
    transition: right .25s ease;
  }
  .side-drawer.open { right: 0; }
  .panel-block h3 { display:flex; align-items:center; gap:7px; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em; color: var(--text-dim); margin: 0 0 12px; }
  #todoAddRow, #notesAddRow { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
  /* On mobile the inline "Write a note..." input is swapped for a button that opens
     #noteDetailsModal (single-line input plus on-screen keyboard was awkward).
     Desktop keeps the inline row. */
  body.mobile-view #notesAddRow { display: none; }
  /* Same for the To-Do quick-add input: mobile shows a "+ Add To-Do" button
     (opens #todoDetailsModal) plus Dictate To-Do; the sort select stays visible.
     Show/hide of #mobileNotesActionsRow/#mobileTodoActionsRow is left entirely to
     the .mobile-fab-row rules below: an ID selector here would outrank their
     "display:flex on mobile" and make the buttons vanish. */
  body.mobile-view #todoAddRow .todo-input-box { display: none; }
  /* .mobile-fab-row: shared floating position for the mobile Add/Dictate rows
     (Notes, To-Do) and the Add Link row (.mobile-add-link-row): fixed just above
     the bottom nav, in thumb reach. Faded out while scrolling (body.mobile-scrolling),
     back on scroll-up or stop (syncMobileNavOffset / the scroll listener).
     --mobile-nav-h is measured in JS like --mobile-header-h. */
  .mobile-fab-row { display: none; }
  body.mobile-view .mobile-fab-row {
    display: flex; gap: 8px;
    position: fixed; left: 12px; right: 12px;
    bottom: calc(var(--mobile-nav-h, 64px) + 10px + env(safe-area-inset-bottom));
    z-index: 125;
    transition: opacity .18s ease, transform .18s ease;
  }
  body.mobile-view.mobile-scrolling .mobile-fab-row {
    opacity: .25; transform: translateY(6px) scale(.97);
    pointer-events: none;
  }
  body.mobile-view .mobile-fab-row button {
    flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 10px 12px;
    box-shadow: var(--shadow-lg);
    /* These buttons float over content, so the default subtle var(--border) outline
       barely reads; currentColor ties the ring to each button's own text color
       (--text, or --danger for the dictate buttons). */
    border-width: 1px;
    border-color: currentColor;
  }
  #quickDictateNoteBtn, #notesRecordBtn, #quickDictateTodoBtn, #todosRecordBtn { color: var(--danger); border-color: var(--danger); }
  #quickDictateNoteBtn:hover, #notesRecordBtn:hover, #quickDictateTodoBtn:hover, #todosRecordBtn:hover { background: var(--danger-soft); color: var(--danger); }
  #quickDictateNoteBtn.recording, #notesRecordBtn.recording, #quickDictateTodoBtn.recording, #todosRecordBtn.recording { background: var(--danger); color: #fff; border-color: var(--danger); }
  #quickDictateNoteBtn.recording:hover, #notesRecordBtn.recording:hover, #quickDictateTodoBtn.recording:hover, #todosRecordBtn.recording:hover { background: var(--danger); color: #fff; }
  #quickDictateNoteBtn.recording .icon, #notesRecordBtn.recording .icon, #quickDictateTodoBtn.recording .icon, #todosRecordBtn.recording .icon { animation: rec-pulse 1.1s ease-in-out infinite; }
  /* Desktop-only mic button inline in the compact add-note row (input -> project
     select), red accent like mobile's quick-dictate button (#notesAddRow is hidden
     on mobile). #todosRecordBtn mirrors it in the To-Do add row. */
  #notesRecordBtn, #todosRecordBtn { display: none; flex: 0 0 auto; white-space: nowrap; }
  body:not(.mobile-view) #notesRecordBtn, body:not(.mobile-view) #todosRecordBtn { display: inline-flex; font-size: 11px; }
  body:not(.mobile-view) #notesRecordBtn.hidden, body:not(.mobile-view) #todosRecordBtn.hidden { display: none; }
  #todoAddRow input, #notesAddRow input { flex: 1; }
  #todoAddRow select, #notesAddRow select { width: 100px; font-size: 11px; }
  /* The To-Do page's quick-add bar: the Links page's icon+input box with the
     To-Do icon; used on desktop and mobile. */
  .todo-input-box {
    flex: 1; min-width: 180px;
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2px 4px 2px 14px;
    box-shadow: var(--shadow);
  }
  .todo-input-box .icon { color: var(--text-faint); flex-shrink: 0; }
  .todo-input-box input { flex: 1; border: none; box-shadow: none; padding: 8px 4px; font-size: 12.5px; }
  .todo-input-box input:focus { box-shadow: none; }
  #todoAddRow #mobileTodoSortSelect { width: 180px; }
  body.mobile-view #todoAddRow #mobileTodoSortSelect { display: block; width: 100%; flex: 1 1 100%; margin-top: 2px; }
  /* #todoList/#todayList are now plain containers around a rendered
     .table-wrap (see renderTodoTableInto) rather than <ul> card lists. */
  #todoList, #todayList { display: block; }
  .todo-item {
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-soft-2);
    border-radius: var(--radius-sm);
    padding: 8px 10px;
    font-size: 13px;
    cursor: pointer;
  }
  .todo-item.done { opacity: .5; }
  .todo-item.done .todo-text { text-decoration: line-through; }
  .todo-item .drag-handle { cursor: grab; color: var(--text-faint); }
  .todo-item .todo-check { color: var(--text-faint); flex-shrink:0; }
  .todo-item .todo-check.on { color: var(--success); }
  .todo-item .todo-main { flex: 1; min-width: 0; }
  .todo-item .todo-text { word-break: break-word; }
  .todo-item .todo-text.overdue { color: var(--danger); }
  .todo-meta-row { display: flex; align-items: center; gap: 6px; margin-top: 3px; flex-wrap: wrap; }
  .todo-dates-badge { font-size: 10px; color: var(--text-faint); display: inline-flex; align-items: center; gap: 4px; }
  /* Colour comes from whichever deadline-urgent-* / due-done class is
     paired with this on the element — deliberately no color of its own. */
  .due-in-badge { font-size: 10px; font-weight: 700; display: inline-flex; align-items: center; padding: 2px 7px; border-radius: 6px; background: var(--bg-elevated); }
  /* Tinted rounded rectangle in the project's color (matches .mobile-project-tag),
     no dot. color-mix() derives the tint from the --tagcolor each call site sets
     inline, so this one rule themes every .todo-tag app-wide. */
  .todo-tag { display: inline-flex; align-items: center; gap: 5px; font-size: 10px; font-weight: 700; color: var(--tagcolor, var(--text-dim)); background: color-mix(in srgb, var(--tagcolor, var(--text-dim)) 15%, transparent); border-radius: 6px; padding: 2px 8px; flex-shrink: 0; }
  /* Sort control + collapsed "Done" sub-list — now shared by both the
     mobile To-Dos tab and the desktop sidebar drawer (see #todoAddRow
     rules above for how the control itself is positioned per platform). */
  .todo-done-sublist { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
  /* Scoped to the compact <li> row (todoRowHtml), NOT the <tr class="todo-item
     todo-row"> table rows that share the "todo-item" class: unscoped, its
     specificity (0,2,0) beat the shared .row-actions button rule (0,1,1) and made
     the tables' row-action buttons smaller than on Links/Notes. */
  li.todo-item .ghost-btn { padding: 4px; }
  .todo-item .danger-action { color: var(--danger); }
  .todo-item .danger-action:hover { background: var(--danger-soft); color: var(--danger); }

  /* ---- Per-project To-Do table ---- */
  /* .todo-item is display:flex for the sidebar <li>, but the same class is on the
     To-Do table's <tr>; flex breaks a <tr> out of table layout (header/body
     misalignment), so restore table-row behavior for .todo-row, desktop only
     (mobile switches back to flex for cards further down). */
  body:not(.mobile-view) tr.todo-item.todo-row { display: table-row !important; align-items: initial; gap: 0; padding: 0; background: transparent; }
  .col-check { width: 38px; }
  .col-todo-title { min-width: 200px; }
  .col-deadline { width: 92px; white-space: nowrap; font-size: 12.5px; }
  .col-due-in { width: 92px; white-space: nowrap; font-size: 12.5px; }
  /* The Deadline column text stays plain: urgency shows in the "Due in" chip
     (dueInChipHtml). deadline-urgent-* classes are still applied (deadlineUrgencyClass)
     and reused for the chip colour and the mobile due-in-phrase background. */
  .deadline-urgent-yellow, .deadline-urgent-orange, .deadline-urgent-red { color: inherit; }
  .due-done { color: var(--success); font-weight: 600; }
  .col-priority { width: 80px; }
  .linked-link-cell { display: inline; cursor: pointer; }
  .linked-link-cell:hover { color: var(--primary-text); }
  .todo-row.done { opacity: .55; }
  .todo-row .todo-title-text { font-size: 13.5px; font-weight: 600; cursor: pointer; }
  .todo-row .todo-title-text.overdue { color: var(--danger); }
  .todo-row.done .todo-title-text { text-decoration: line-through; }
  /* Force fixed column widths so <thead> and <tbody> always line up — the
     generic auto table layout above let icon-only/empty cells drift out of
     alignment with their headers on this table. */
  table.todo-table { table-layout: fixed; }
  table.todo-table td, table.todo-table th { vertical-align: middle; }
  /* Fixed px matching the Links table's leading columns (.col-drag 32px +
     .col-type 54px = 86px) so Title text starts at the same x in both tables. */
  .todo-table .col-drag { width: 32px; }
  /* Left-aligned so the status icon sits flush with the column padding, like the
     Links table's type badge in the equivalent column. */
  .todo-table .col-check { width: 54px; }
  /* width:auto = the Title column absorbs all leftover space. With a fixed
     26% here, spare width on a wide window was being spread across EVERY
     column, which is what ballooned the Status column. */
  .todo-table .col-todo-title { width: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* Mobile-card-only row (see todoTableRowHtml) — no desktop column for
     it at all, so it never reserves a spot in the desktop table's grid. */
  .todo-table .col-todo-desc { display: none; }
  .todo-table .col-deadline { width: 8%; }
  .todo-table .col-due-in { width: 8%; }
  /* The mobile-only phrase ("Due in 3 days") lives in the same cell as
     the desktop label ("3 days") — only one of the two is ever visible;
     see the body.mobile-view override below. */
  .todo-table .col-due-in .due-in-phrase { display: none; }
  .todo-table .col-priority { width: 8%; }
  /* Column widths sum to the same 74% of flexible width whether or not
     col-project is shown (Today/Recent vs main To-Dos tab); table-layout:fixed
     can't reconcile unequal totals and the actions column got clipped. */
  .todo-table .col-project { width: 10%; }
  /* Fixed, not percentage: a % width in an auto-layout table is only a
     suggestion and grows for the widest row's icons. 210px fits 5 icons
     (5 x ~37px + 4 x ~2.6px gaps = ~197px) plus a margin. */
  .todo-table .col-actions { width: 176px; text-align: right; white-space: nowrap; }
  /* Desktop: icons start at the column's left edge so the "ACTIONS" header sits over
     the first icon; the -6px cancels the button padding so the glyph aligns. */
  body:not(.mobile-view) .todo-table td.col-actions { text-align: left; }
  body:not(.mobile-view) .todo-table td.col-actions .row-actions { margin-left: -6px; }

  /* Quick-launch link management list, inside desktop Settings modal. */
  .settings-quicklink-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; width: 100%; }
  .settings-quicklink-row {
    display: flex; align-items: center; gap: 8px;
    font-size: 12.5px; font-weight: 600;
    padding: 8px 10px;
    background: var(--bg-soft-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background .12s, border-color .12s;
  }
  .settings-quicklink-row:hover { background: var(--bg-soft); border-color: var(--primary); }
  #removeQuickLinkIconBtn { border-color: var(--danger); }
  .settings-quicklink-row img, .settings-quicklink-row svg { width: 16px; height: 16px; flex-shrink: 0; }
  [data-theme="dark"] .settings-quicklink-row img.ql-icon-mono { filter: invert(1); }
  .settings-quicklink-row .sql-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* Icon preview swatch in the add/edit quick-link modal. */
  .quicklink-icon-preview {
    width: 36px; height: 36px; flex-shrink: 0; border-radius: 8px;
    background: var(--bg-soft-2); display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--border); overflow: hidden;
  }
  .quicklink-icon-preview img { width: 22px; height: 22px; object-fit: contain; }
  .quicklink-icon-preview img.ql-icon-mono { filter: invert(0); }
  [data-theme="dark"] .quicklink-icon-preview img.ql-icon-mono { filter: invert(1); }
  .quicklink-icon-preview.empty::after { content: '?'; color: var(--text-faint); font-size: 13px; font-weight: 700; }
  /* The native file input renders very differently (and badly) across
     browsers — hide it and use a label styled as a normal button instead,
     which triggers the same file picker via the for="" association. */
  .quicklink-icon-file-input {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); border: 0;
  }
  /* Styled like the plain buttons elsewhere (button{} is tag-qualified so
     a <label> doesn't pick it up automatically) — same look, just clickable
     via its for="" association instead of a click handler. */
  .quicklink-icon-choose-btn {
    font-family: inherit; cursor: pointer; white-space: nowrap; flex-shrink: 0;
    border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text);
    border-radius: var(--radius-sm); padding: 8px 12px; font-size: 13px; font-weight: 500;
    display: inline-flex; align-items: center; gap: 6px;
  }
  .quicklink-icon-choose-btn:hover { background: var(--bg-soft); }
  .quicklink-icon-filename { font-size: 11.5px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 140px; }

  /* #notesList wraps an active .notes-cards <ul> and, once any note is archived,
     a toggle bar plus a collapsed second <ul> (same shape as the Links table).
     #recentNotesList stays one plain list (Recent excludes archived notes). */
  #notesList, #recentNotesList, .notes-cards { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
  .note-item.status-archived { opacity: .55; }
  /* Both platforms: link+pencil in a row below the note text, trash at the row's
     right edge, uniform card padding. .note-main takes the first line (flex:1 1
     100%); the buttons wrap to the second, trash pushed right by its auto margin. */
  .note-item {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    box-shadow: var(--shadow);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
    font-size: 13px;
    position: relative;
  }
  .note-item .note-main { flex: 1 1 100%; min-width: 0; }
  .note-item .note-meta { font-size: 10.5px; color: var(--text-faint); margin-bottom: 14px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  .note-item .note-meta .todo-tag { font-size: 10px; }
  .note-item .note-linked-row { margin-bottom: 5px; display: flex; align-items: center; gap: 5px; }
  .note-item .note-linked-row .icon { width: 13px; height: 13px; color: var(--text-faint); flex-shrink: 0; }
  .note-item .note-linked-row .linked-link-cell { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Unlink control — one per linked-link row now that a note can have
     several, so each needs its own way to be removed independently
     (a single linked link used to just get replaced via "link-note"). */
  .note-item .note-linked-row .note-linked-remove { margin-left: auto; cursor: pointer; opacity: .5; display: flex; flex-shrink: 0; border-radius: 50%; border: none; background: none; padding: 0; color: inherit; }
  .note-item .note-linked-row .note-linked-remove:hover { opacity: 1; color: var(--danger); }
  .note-item .note-text { white-space: pre-wrap; word-break: break-word; cursor: pointer; }
  /* "Show more" (2026-10-22): note text stops after 10 lines (--clamp-lines) with
     a fade; applyClamps() adds the toggle button only when the text is longer.
     Same on desktop and phone: Notes, Recent, Search Everything, Connections. */
  .clamp-body { --clamp-lines: 10; line-height: 1.45; }
  /* Note title: bold, then one blank row before the text; one blank row after linked items (2026-10-22). */
  .note-title { font-weight: 700; word-break: break-word; cursor: pointer; }
  .note-title.has-text { margin-bottom: 1.45em; }
  td.bl-title .note-title { cursor: inherit; }
  .note-gap { height: 1.45em; }
  .clamp-body:not(.expanded) { max-height: calc(var(--clamp-lines) * 1.45em); overflow: hidden; }
  .clamp-body.is-clamped:not(.expanded) { -webkit-mask-image: linear-gradient(#000 calc(100% - 2.2em), transparent); mask-image: linear-gradient(#000 calc(100% - 2.2em), transparent); }
  .clamp-more { display: block; margin: 4px 0 0; padding: 2px 0; background: none; border: 0; font: inherit; font-size: 12px; font-weight: 600; color: var(--primary-text); cursor: pointer; }
  .clamp-more:hover { text-decoration: underline; }
  .note-item .danger-action { color: var(--danger); }
  .note-item .danger-action:hover { background: var(--danger-soft); color: var(--danger); }
  /* The action buttons sit in one .row-actions span so they share the tight
     icon gap of the Links/To-Do cards, not .note-item's looser gap:8px. The span
     spans the full row and justify-content:flex-end keeps the icons together. */
  /* flex-end is the base/desktop rule (icons clustered right, next to trash);
     only MOBILE wants left icons + right trash, via the override below. */
  .note-item .row-actions { flex: 1 1 100%; justify-content: flex-end; }
  body.mobile-view .note-item .row-actions { justify-content: flex-start; }
  body.mobile-view .note-item [data-action="delete-note"] { margin-left: auto; }
  /* Mobile only: the project tag additionally moves out to the card's
     top-right corner (resolves against .note-item, the nearest
     positioned ancestor, even though it's nested deeper than a direct
     child) — desktop leaves it inline in the meta row next to the date. */
  /* Match the Links card's own left/right padding tokens, so the trash
     icon's right edge lines up with the Links card's trash icon exactly —
     the base rule above uses a plain 12px that didn't match. */
  body.mobile-view .note-item { padding: var(--card-pad-y) var(--card-pad-x); }
  /* The date/project line and the note text itself sit 5px further in
     from both edges than the card's own padding gives them — the icon
     row below is unaffected, only .note-main's own content. */
  body.mobile-view .note-item .note-main { padding: 0 5px; }
  /* A thin divider under the date/project line, with a little padding
     above it, so the note text below has room to breathe instead of
     crowding right up against the meta line. */
  body.mobile-view .note-item .note-meta {
    padding-right: 70px; padding-bottom: 8px; margin-bottom: 8px;
    border-bottom: 1px solid var(--border);
  }
  /* Dual markup (see noteCardHtml): desktop keeps the dot+pill .todo-tag,
     mobile swaps to the tinted .mobile-project-tag used everywhere else
     on mobile (Today screen, etc.) — each hidden on the other view. */
  body.mobile-view .note-item .note-project-desktop { display: none; }
  body:not(.mobile-view) .note-item .note-project-mobile { display: none; }
  body.mobile-view .note-item .note-project-mobile {
    position: absolute; top: 10px; right: 10px; margin: 0;
  }
  /* A full blank line's worth of breathing room between the last linked
     link/to-do preview row and the note text itself, so they read as two
     clearly separate groups rather than one crowded block. */

  /* ---------------- Connections on rows and cards (2026-10-23) ----------------
     Connect (connections icon with +) and Connections with a count badge,
     tinted in the project colour when > 0. */
  .row-actions .conn-count-btn { position: relative; }
  .conn-badge {
    position: absolute; top: 0; right: -1px; min-width: 15px; height: 15px; padding: 0 4px;
    border-radius: 8px; font-size: 9.5px; font-weight: 800; line-height: 15px; text-align: center;
    background: var(--bg-soft-2); color: var(--text-faint); pointer-events: none;
  }
  .conn-count-btn.has .conn-badge { background: var(--conn-color, var(--primary)); color: #fff; }
  /* Add Connection = the same icon with a "+" in the badge (same size and place as the count). */
  .row-actions .conn-connect-btn { position: relative; }
  /* The badge sticks out to the right: give these two the room back so the
     gaps between all row icons look equal. */
  .row-actions .conn-count-btn, .row-actions .conn-connect-btn { margin-right: 5px; }
  .conn-badge-plus { font-size: 12px; line-height: 14px; color: var(--text-dim); }
  /* Phone cards: more room between the action icons (easier to tap). */
  body.mobile-view .note-item .row-actions,
  body.mobile-view .link-table .col-actions .row-actions,
  body.mobile-view table.todo-table .col-actions .row-actions { column-gap: 12px; }
  /* Phone (2026-10-23): no "⋯" menu, every action shows on the card; Links
     drop Copy there (long-press the title, or Copy URL in the Link popup). */
  body.mobile-view .link-table .row-actions [data-action="copy-link"],
  body.mobile-view .link-table .row-actions [data-action="copy-path"] { display: none; }
  /* Note cards: inline connections (all types), faded when done/archived. */
  .note-item .note-linked-row.conn-faded .linked-link-cell { opacity: .55; text-decoration: line-through; }
  .note-conn-more {
    border: none; background: none; padding: 0 0 4px 18px; font-size: 12px; font-weight: 600;
    color: var(--primary-text); cursor: pointer;
  }
  .note-conn-more:hover { text-decoration: underline; }
  /* ---- Modal ---- */
  .modal-overlay {
    position: fixed; inset: 0; background: var(--overlay);
    display: flex; align-items: center; justify-content: center;
    z-index: 200;
  }
  .modal-overlay.hidden { display: none; }
  /* While any popup is open the page behind it doesn't scroll — the wheel
     scrolls the popup (or the text box under the cursor) instead (2026-10-21). */
  body:has(.modal-overlay:not(.hidden)) { overflow: hidden; }
  .modal, .modal textarea { overscroll-behavior: contain; }
  /* Text boxes grow downwards only; the width stays with the popup. */
  textarea { resize: vertical; max-width: 100%; }
  /* #confirmModal opens from INSIDE other modals (Quick Link / Link Details / To-Do /
     Project Settings Delete) and must paint on top. Equal z-indexes only worked by
     DOM order; #quickLinkModal comes after it in the markup, so its confirm hid
     behind it. A higher z-index fixes every modal-inside-a-modal case. */
  #confirmModal { z-index: 300; }

  /* Splash while Firebase Auth resolves an existing session. Sits ABOVE the auth
     gate so the sign-in form never flashes for a signed-in user; hidden when
     onAuthStateChanged first fires. */
  /* Same gradient as the app icon (.brand-dot), full screen with the "D" mark
     centered, so the splash reads as a scaled-up app icon. */
  #authChecking {
    position: fixed; inset: 0;
    background: linear-gradient(135deg, var(--primary), #ff7ac6);
    display: flex; align-items: center; justify-content: center;
    z-index: 250;
  }
  #authChecking.hidden { display: none; }
  .auth-checking-mark { display: flex; flex-direction: column; align-items: center; gap: 22px; }
  .auth-checking-d { font-size: 44px; font-weight: 800; letter-spacing: .04em; line-height: 1; color: #fff; }
  .auth-checking-spinner {
    width: 30px; height: 30px; border-radius: 50%;
    border: 4px solid rgba(255,255,255,.35); border-top-color: #fff;
    animation: auth-spin .7s linear infinite;
  }
  @keyframes auth-spin { to { transform: rotate(360deg); } }
  .modal {
    position: relative;
    background: var(--bg-elevated);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: 24px;
    width: 400px;
    max-width: 90vw;
    display: flex; flex-direction: column; gap: 14px;
    /* Desktop modals are height-capped and scroll internally, so tall content (e.g.
       Link Details with URL History) never hides Save/Cancel. Mobile has its own
       full-height rule (body.mobile-view .modal-overlay .modal below). */
    max-height: 90vh;
    overflow-y: auto;
  }
  .modal.modal-lg { width: 620px; }
  .modal.modal-xl { width: 800px; padding: 32px; }
  .modal.modal-xl textarea { min-height: 140px; }
  .modal.modal-xl .row label { flex: 0 0 110px; font-size: 14px; }
  /* The connections popup needs real room for its three tables (title +
     due-in + priority, or title + description) to breathe — 2x the usual
     modal-lg width it used to carry. */
  #connectionsModal .modal { width: 1240px; }
  /* 30% wider than the base .modal (400px) — the quick-link management
     list and account section were feeling cramped at the default width. */
  #settingsModal .modal { width: 520px; }
  .modal-close-x { position: absolute; top: 14px; right: 14px; padding: 6px; border-radius: 50%; }
  #settingsModal .modal h2 { margin-bottom: 14px; }
  .modal h2 { display:flex; align-items:center; gap:8px; margin: 0 0 4px; font-size: 17px; font-weight: 700; }
  .modal .row { display: flex; gap: 10px; align-items: center; }
  .modal .row label { flex: 0 0 100px; font-size: 13px; color: var(--text-dim); font-weight: 600; }
  /* A checkbox + its text inside a row (Project settings "Search"). Class
     beats the generic row-label rule above, which also matches this
     nested <label>. */
  /* Settings → Account: stacked on desktop; on mobile Sign out sits on the
     email/sync row, anchored right (2026-10-20). */
  .account-block { flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
  .account-info { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
  body.mobile-view .account-block { flex-direction: row; align-items: center; }
  body.mobile-view .account-block #signOutBtn { margin-left: auto; flex-shrink: 0; }
  .modal .row label.settings-check { flex: 1; display: flex; align-items: center; gap: 8px; font-weight: 500; color: var(--text); cursor: pointer; }
  .settings-check input { width: 16px; height: 16px; margin: 0; accent-color: var(--primary); cursor: pointer; }
  /* A flex:1 child's default min-width is its content width; for a <select> that
     is its widest OPTION, which could push the modal off the right edge on mobile. */
  .modal .row > select, .modal .row > input, .modal .row > textarea { min-width: 0; }
  .todo-linked-preview { font-size: 12px; color: var(--text-dim); word-break: break-word; white-space: normal; line-height: 1.4; }
  .modal p.hint { font-size: 12.5px; color: var(--text-dim); margin: 0; line-height: 1.5; }
  .modal p.hint code { background: var(--bg-soft); padding: 1px 5px; border-radius: 5px; }
  /* Section divider inside a modal — mobile only for now (2026-10-20,
     Settings and Project settings). */
  .modal-divider { display: none; }
  body.mobile-view .modal-divider { display: block; border-top: 1px solid var(--border); margin: 6px 0; }
  /* One empty row of breathing space above Delete/Cancel/Save (mobile). */
  body.mobile-view #projectSettingsModal .modal-actions { margin-top: 26px; }
  /* stretch: every button in the row gets the same height (Delete used to be
     shorter than the icon-carrying Add to Calendar next to it). */
  .modal-actions { display: flex; justify-content: space-between; gap: 8px; margin-top: 6px; align-items: stretch; }
  .modal-actions .right-actions { display: flex; gap: 8px; margin-left: auto; }
  /* Add to Calendar menu (2026-10-22): opens a prefilled event in Google / Outlook, or downloads the .ics. */
  .modal p.suggest-thanks { margin: 0 0 12px; font-size: 13px; color: var(--text-dim); line-height: 1.45; }
  .suggest-note { margin: 4px 0 0; font-size: 11.5px; color: var(--text-faint); line-height: 1.45; }
  .cal-wrap { position: relative; display: inline-flex; }
  .cal-wrap:has(> #addToCalendarTodoBtn.hidden) { display: none; } /* no empty slot (it shifted Cancel) */
  .cal-options { display: flex; flex-direction: column; gap: 4px; margin: 0 0 8px; }
  .modal .cal-opt, .cal-opt { display: flex; align-items: center; gap: 10px; width: auto; flex: none; min-height: 0; margin: 0; padding: 6px 0; font-size: 14px; font-weight: 500; color: var(--text); cursor: pointer; }
  .modal .cal-opt input, .cal-opt input { width: 18px; height: 18px; flex: none; margin: 0; }
  .cal-menu { position: absolute; right: 0; bottom: calc(100% + 6px); min-width: 240px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 6px; z-index: 5; display: flex; flex-direction: column; gap: 2px; }
  .cal-menu button { text-align: left; border: 0; background: none; box-shadow: none; padding: 8px 10px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 500; color: var(--text-dim); cursor: pointer; white-space: nowrap; }
  .cal-menu button:hover { background: var(--bg-soft); color: var(--text); }
  /* Widened ~10% over the base .modal so all 8 color swatches fit on one
     row instead of wrapping to a second line. */
  #projectSettingsModal .modal { width: 440px; }
  /* 12 colors, 6 per row (2 rows) — a fixed 6-column grid rather than
     flex-wrap so the split is always exactly 6+6 regardless of container
     width, instead of however many happen to fit before wrapping. */
  .color-swatch-row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; flex: 1; }
  .color-swatch {
    width: 32px; height: 32px; border-radius: 9px; cursor: pointer;
    border: 2px solid transparent; margin: 0 auto;
  }
  .color-swatch.selected { border-color: var(--text); }
  #importFileInput { display: none; }

  .theme-choice-group { display: flex; gap: 6px; flex: 1; }
  .theme-choice-btn {
    flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
    padding: 9px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg-elevated); color: var(--text-dim); font-size: 12.5px; font-weight: 600;
    cursor: pointer;
  }
  .theme-choice-btn .ic svg { width: 15px; height: 15px; }
  .theme-choice-btn:hover { border-color: var(--primary); color: var(--text); }
  .theme-choice-btn.active { border-color: var(--primary); background: var(--primary-soft); color: var(--primary-text); }
  /* The header's quick toggle is retired (Light/Dark/System lives in Settings);
     kept in the DOM because applyTheme()/toggleTheme() still reference it. */
  #themeToggleBtn { display: none; }

  .tag-editor-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; min-height: 20px; }
  .tag-editor-chip { display: flex; align-items: center; gap: 5px; background: var(--bg-soft); border-radius: 6px; padding: 4px 6px 4px 11px; font-size: 11.5px; font-weight: 600; color: var(--text-dim); }
  .tag-editor-chip .remove-tag { cursor: pointer; opacity: .5; display: flex; border-radius: 50%; }
  .tag-editor-chip .remove-tag:hover { opacity: 1; color: var(--danger); }

  /* Previous Links history (Update flow): the divider deliberately does not run
     edge-to-edge, so it reads as a light separator for an optional section. */
  .previous-links-section { display: flex; flex-direction: column; }
  .previous-links-divider { height: 1px; background: var(--border); width: 65%; margin: 2px auto 14px; }
  .previous-links-heading { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); margin: 0 0 8px; }
  .previous-links-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
  .previous-links-table td { padding: 5px 6px; border-bottom: 1px solid var(--border); color: var(--text-dim); vertical-align: top; }
  .previous-links-table tr:last-child td { border-bottom: none; }
  .previous-links-table .pl-url { word-break: break-all; }
  .previous-links-table .pl-date { white-space: nowrap; color: var(--text-faint); font-size: 11px; }
  .previous-links-table .pl-remove { cursor: pointer; opacity: .5; display: flex; border-radius: 50%; }
  .previous-links-table .pl-remove:hover { opacity: 1; color: var(--danger); }

  /* Same dual markup for the Search Everything Notes table (desktop: dot+pill
     .todo-tag, mobile: tinted .mobile-project-tag). */
  /* Same for the Search Everything Links and To-Dos tables. .search-todo-table (a
     class, not the #searchTodoTable id) also covers the collapsible Done table
     (see renderTodoSearchTable); a duplicate id would match only one. */
  body.mobile-view .search-todo-table .search-todo-project-desktop,
  body.mobile-view .link-table .link-row-project-desktop { display: none; }
  body:not(.mobile-view) .search-todo-table .search-todo-project-mobile,
  body:not(.mobile-view) .link-table .link-row-project-mobile { display: none; }
  /* The generic To-Do mobile rules hide .col-project (its tag lives in
     .mobile-todo-tags); todoSearchRowHtml has no such row, so this table shows its
     own project column under the title. */
  body.mobile-view .search-todo-table.todo-table .col-project {
    display: inline-flex; order: 1;
  }

  /* !important is deliberate: this is the app's generic "force hidden" utility,
     toggled from JS. A more specific rule (e.g. ".modal .row { display:flex }")
     would otherwise win and leave the element visible (seen on the Link Details
     Project row, 2026-09-17). */
  .hidden { display: none !important; }

  /* The Dictate row in the shared New/Edit Note modal shows on both platforms, so
     existing notes can be dictated into on desktop too. */
  #recordNoteRow { display: flex; }
  #recordNoteBtn { color: var(--danger); border-color: var(--danger-soft); }
  #recordNoteBtn:hover { background: var(--danger-soft); color: var(--danger); }
  #recordNoteBtn.recording { background: var(--danger); color: #fff; border-color: var(--danger); }
  #recordNoteBtn.recording:hover { background: var(--danger); color: #fff; }
  #recordNoteBtn.recording .icon { animation: rec-pulse 1.1s ease-in-out infinite; }
  @keyframes rec-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

  ::-webkit-scrollbar { height: 8px; width: 8px; }
  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
  /* Popups (2026-10-21): the scrollbar ran into the rounded corners. Keep
     the track clear of the corners and the thumb a slim inset pill. */
  .modal::-webkit-scrollbar { width: 12px; }
  .modal::-webkit-scrollbar-track { background: transparent; margin-block: var(--radius-lg); }
  .modal::-webkit-scrollbar-thumb { background: var(--border); background-clip: padding-box; border: 3px solid transparent; border-radius: 8px; }
  .modal::-webkit-scrollbar-thumb:hover { background-color: var(--text-faint); }

  .toast {
    position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 12px);
    background: var(--text); color: var(--bg-elevated); padding: 11px 18px;
    border-radius: 999px; font-size: 13px; font-weight: 600; box-shadow: var(--shadow-lg);
    opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease; z-index: 999;
    max-width: 80vw; text-align: center;
  }
  .toast.show { opacity: 1; transform: translate(-50%, 0); }
  /* Toast with an action (Undo after Disconnect / Connect, 2026-10-23). */
  .toast.show.has-action { pointer-events: auto; display: flex; align-items: center; gap: 14px; white-space: nowrap; }
  .toast-action { border: none; background: none; color: inherit; font: inherit; font-weight: 800; text-decoration: underline; padding: 0; cursor: pointer; }


  /* ---- Connect popup (2026-10-23, one screen) ----
     All controls share one height/radius (.connect-ctl). The editor opened by
     "Create new …" gets .modal-over-connect (it comes earlier in the page). */
  #connectModal { z-index: 210; }
  .modal-overlay.modal-over-connect { z-index: 220; }
  .modal.connect-modal { width: 680px; }
  .connect-modal h2 { padding-right: 36px; overflow-wrap: anywhere; }
  .connect-types { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; padding: 4px; background: var(--bg-soft); border-radius: var(--radius); flex-shrink: 0; }
  .connect-type {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px; height: 36px;
    border: none; background: none; border-radius: var(--radius-sm); color: var(--text-dim); font-weight: 600; font-size: 13.5px; cursor: pointer;
  }
  .connect-type:hover { color: var(--text); }
  .connect-type.on { background: var(--bg-elevated); color: var(--primary-text); box-shadow: var(--shadow); }
  .connect-type .icon { width: 17px; height: 17px; }
  .connect-filters { display: flex; flex-wrap: wrap; gap: 8px; flex-shrink: 0; }
  .connect-ctl {
    box-sizing: border-box; height: 38px; display: inline-flex; align-items: center; gap: 8px; padding: 0 11px;
    border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-elevated);
    color: var(--text); font-size: 13.5px; font-family: inherit; margin: 0;
  }
  .connect-proj { position: relative; flex: 0 1 210px; min-width: 0; }
  .connect-proj-btn { width: 100%; cursor: pointer; }
  .connect-proj-btn .dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
  .connect-proj-btn > .icon { color: var(--text-faint); flex-shrink: 0; }
  .connect-proj-name { flex: 1; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .connect-proj-menu { max-height: 280px; overflow-y: auto; width: 100%; min-width: 220px; }
  .connect-search { flex: 1 1 200px; min-width: 0; }
  .connect-hide { cursor: pointer; color: var(--text-dim); font-weight: 600; white-space: nowrap; }
  .connect-hide.on { color: var(--primary-text); border-color: color-mix(in srgb, var(--primary) 45%, var(--border)); background: var(--primary-soft); }
  .connect-hide .icon { width: 16px; height: 16px; }
  .connect-list { display: flex; flex-direction: column; gap: 2px; height: 320px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius); padding: 4px; }
  .connect-item { flex-shrink: 0; border-radius: 8px; }
  .connect-item.open { background: var(--bg-soft); }
  .connect-row { display: flex; align-items: center; border-radius: 8px; font-size: 13.5px; }
  .connect-row:hover { background: var(--bg-soft); }
  /* Tick only on the checkbox (big tap area); the rest of the row opens the preview. */
  .connect-check { display: flex; align-items: center; justify-content: center; align-self: stretch; padding: 0 8px 0 10px; cursor: pointer; flex-shrink: 0; }
  .connect-row-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px; padding: 8px 8px 8px 2px; border: none; background: none; color: var(--text); font: inherit; text-align: left; cursor: pointer; border-radius: 8px; }
  .connect-row-chev { display: inline-flex; color: var(--text-faint); flex-shrink: 0; transition: transform .15s; }
  .connect-item.open .connect-row-chev { transform: rotate(180deg); }
  .connect-preview { padding: 2px 12px 10px 44px; font-size: 13px; color: var(--text); display: flex; flex-direction: column; gap: 6px; }
  /* Long text scrolls inside its own box (finger or wheel), not the list. */
  .connect-preview-text { max-height: 12em; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; line-height: 1.45; overflow-wrap: anywhere; padding-right: 4px; }
  .connect-preview-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
  .connect-preview .tag-chip { background: var(--bg-elevated); border: 1px solid var(--border); }
  .connect-preview-dates { font-size: 12px; color: var(--text-dim); }
  .connect-preview-done { font-size: 11px; font-weight: 700; color: var(--success); }
  .connect-preview-empty { font-style: italic; color: var(--text-dim); }
  .connect-also-row { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13.5px; flex-shrink: 0; }
  .connect-also-row:hover { background: var(--bg-soft); }
  .connect-row input, .connect-also-row input { margin: 0; width: 17px; height: 17px; accent-color: var(--primary); flex-shrink: 0; padding: 0; }
  .connect-row-icon { display: inline-flex; color: var(--text-faint); }
  .connect-row.faded .connect-row-title { color: var(--text-faint); text-decoration: line-through; }
  .connect-row-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .connect-connected-chip { font-size: 10.5px; font-weight: 700; padding: 2px 8px; border-radius: 6px; background: color-mix(in srgb, var(--success) 18%, transparent); color: var(--success); flex-shrink: 0; }
  .connect-empty { padding: 10px; margin: 0; }
  .connect-create-btn {
    display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%; height: 40px; flex-shrink: 0;
    border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-elevated);
    color: var(--primary-text); font-weight: 700; font-size: 13.5px; cursor: pointer;
  }
  .connect-create-btn:hover { background: var(--primary-soft); }
  .connect-foot { margin-top: 0; }
  /* "Also connect" box inside the Link / To-Do / Note editor. */
  .connect-also { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: var(--bg-soft); }
  .connect-also-head { display: block; font-size: 13px; font-weight: 700; line-height: 1.5; overflow-wrap: anywhere; }
  .connect-also-head .icon { width: 15px; height: 15px; color: var(--primary-text); vertical-align: -3px; margin-right: 4px; }
  /* Long names wrap onto the next line instead of widening the popup. */
  .connect-also-src { display: inline; }
  .connect-also-src .icon { color: var(--text-dim); margin-left: 2px; }
  .connect-also { min-width: 0; max-width: 100%; box-sizing: border-box; }
  .connect-also-row .connect-row-title { white-space: normal; overflow-wrap: anywhere; }
  .connect-also-hint { margin: 8px 0 2px; font-size: 12px; color: var(--text-dim); }
  .connect-also-row { padding: 6px 4px; }
  /* Phone: full-screen sheet; header, type toggle and filters stay put, the
     list scrolls, the buttons sit at the bottom at equal width. */
  body.mobile-view #connectModal .modal { overflow: hidden; padding: 20px 16px 0; gap: 12px; }
  body.mobile-view .connect-modal h2 { font-size: 16px; }
  body.mobile-view .connect-proj { flex: 1 1 100%; }
  body.mobile-view .connect-list { flex: 1 1 auto; height: auto; min-height: 0; }
  body.mobile-view .connect-row-main { padding: 12px 8px 12px 2px; }
  body.mobile-view .connect-check { padding: 0 10px 0 12px; }
  body.mobile-view .connect-check input { width: 20px; height: 20px; }
  body.mobile-view .connect-foot { margin: 0 -16px; padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid var(--border); background: var(--bg-elevated); }
  body.mobile-view .connect-foot .right-actions { width: 100%; }
  body.mobile-view .connect-foot .right-actions button { flex: 1 1 0; height: 46px; font-size: 15px; }
  body.mobile-view .connect-create-btn { height: 46px; font-size: 15px; }

  /* Connections popup sections + rows (2026-10-23). */
  .conn-section { margin-top: 14px; }
  .conn-section-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
  .conn-section-head h3 {
    flex: 1; margin: 0; display: flex; align-items: center; gap: 6px;
    font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint);
  }
  .conn-section-head h3 .icon { width: 14px; height: 14px; flex-shrink: 0; }
  .conn-section-count {
    font-size: 10.5px; font-weight: 800; letter-spacing: 0; padding: 0 6px; border-radius: 999px;
    background: var(--bg-soft-2); color: var(--text-dim);
  }
  .conn-add-btn {
    display: inline-flex; align-items: center; gap: 4px; border: none; background: none; cursor: pointer;
    font-size: 12.5px; font-weight: 700; color: var(--primary-text); padding: 5px 8px; border-radius: var(--radius-sm);
  }
  .conn-add-btn:hover { background: var(--primary-soft); }
  .conn-add-btn .icon { width: 14px; height: 14px; }
  .conn-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
  .conn-row {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap; cursor: pointer;
    padding: 6px 6px 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg-soft); font-size: 13px;
  }
  .conn-row:hover, .conn-row:focus-visible { border-color: var(--primary); }
  .conn-row-icon { display: inline-flex; color: var(--text-faint); }
  .conn-row-icon.done { color: var(--success); }
  .conn-row-title { flex: 1 1 160px; min-width: 0; font-weight: 600; overflow-wrap: anywhere; }
  .conn-row-date { font-size: 11px; color: var(--text-faint); white-space: nowrap; }
  .conn-row-status { font-size: 10.5px; font-weight: 700; color: var(--text-faint); background: var(--bg-soft-2); border-radius: 6px; padding: 1px 6px; }
  .conn-row.conn-faded .conn-row-title { color: var(--text-faint); text-decoration: line-through; }
  .conn-row { position: relative; padding-right: 36px; }
  .conn-x {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%); display: inline-flex; border: none; background: none; cursor: pointer;
    color: var(--text-faint); padding: 4px; border-radius: 50%;
  }
  .conn-x:hover { color: var(--danger); background: var(--danger-soft); }
  .conn-x .icon { width: 15px; height: 15px; }
  .conn-empty { padding: 2px 2px 4px; }
  body.mobile-view .conn-x { top: 6px; transform: none; }
  .connect-foot .primary-btn:disabled { opacity: .45; cursor: default; }
  /* Connections popup section names: not bold on desktop, like the other section titles. */
  body:not(.mobile-view) .conn-section-head h3 { font-weight: 600; }
  /* ==================================================================
     Mobile shell (< 768px): bottom tab bar replaces the topbar icons; the side
     drawers become full-screen tab targets; tables restyle into cards via flex +
     column "order" (no new markup), so every table gets the same treatment.
     `body.mobile-view` is toggled by JS (see isMobileView()); nothing below
     applies above 767px wide. */
  .mobile-nav { display: none; }

  body.mobile-view .search-wrap { flex: 1 1 100%; max-width: none; margin-left: 0; order: 3; }
  /* Clear button's inset (right:10px) and #globalSearch's padding-right
     (36px) are now shared with desktop directly in the base rule above —
     both platforms get the same, already-tuned spacing. */
  body.mobile-view #topbar { flex-wrap: wrap; padding: 10px 14px; gap: 8px 10px; }
  /* Mobile header: brand left; project switcher and settings gear (the only
     .topbar-actions child still visible) grouped at the right. Search keeps its own
     full-width row (order:3). One auto margin on the switcher only: giving it AND
     .topbar-actions one would split the leftover space between them. */
  body.mobile-view .project-switcher { margin-left: auto; }
  body.mobile-view .topbar-actions { margin-left: 0; }
  body.mobile-view .quick-launch { display: none; }
  body.mobile-view .topbar-divider { display: none; }
  body.mobile-view #linksPageBtn, body.mobile-view #notesPanelBtn, body.mobile-view #todoPanelBtn, body.mobile-view #todayPageBtn, body.mobile-view #recentsPanelBtn { display: none; }
  /* The project name + settings gear this bar showed is redundant on mobile (the
     switcher names the project; its "Project Settings" row opens the same modal),
     so it is dropped here. Desktop still uses this bar. */
  body.mobile-view #projectIdentityBar { display: none; }
  /* #appHeader is fixed on mobile, so each visible panel needs its own top offset
     sized to the header's variable height: #mainContent (Links) below; To-Dos/Notes/
     Shortcuts/Today via the .side-drawer/#moreScreen/#todayScreen rule further down. */
  body.mobile-view .project-identity-name { font-size: 18px; }
  /* The page background itself is the tinted --project-tint (see body{}) —
     individual cards/tables (.note-item, .table-wrap, the To-Do table)
     carry their own white background instead, matching the Links tab. */
  body.mobile-view #mainContent {
    padding: 10px 14px 84px;
    padding-top: calc(var(--mobile-header-h, 120px) + 10px);
    /* Clearance so the last card scrolls clear of BOTH the fixed bottom nav and the
       floating Add/Dictate bar (see .mobile-fab-row). */
    padding-bottom: calc(var(--mobile-nav-h, 64px) + 74px);
    background: transparent;
  }
  /* Icon+label page heading like the To-Do/Notes/Today/Recent pages, shown on
     mobile and desktop; the link icon inside the add-link box is hidden as
     redundant (see .add-link-box .icon below). */
  .mobile-links-heading {
    display: flex; align-items: center; gap: 7px;
    font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em;
    color: var(--text-dim); margin: 0 0 12px;
  }
  /* Recent Activity: a divider between its four sections, like
     .mobile-links-heading but always visible, with space above all but the first. */
  .recent-section-heading {
    display: flex; align-items: center; gap: 7px;
    font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em;
    color: var(--text-dim); margin: 22px 0 12px;
  }
  #recentOpenedHeading { margin-top: 0; }
  .add-link-box .icon { display: none; }
  body.mobile-view .project-switcher-menu { max-width: 88vw; left: auto; right: 0; } /* anchored to the right so it never runs off-screen (2026-10-22) */
  body.mobile-view #sidePanelBackdrop { display: none; }
  body.mobile-view .toolbar-row { gap: 8px; }
  /* Same as the Notes/To-Do quick-add: typing a URL + Enter in .add-link-box wasn't
     reliable on mobile, so mobile shows a "+ Add Link" button (opens the New Link
     modal). Desktop keeps the inline paste-and-Enter row. */
  /* Positioning/flex now handled by the shared .mobile-fab-row rules below
     (this row is floating, fixed above the bottom nav — see .mobile-fab-row). */
  body.mobile-view .add-link-box, body.mobile-view .add-link-line { display: none; }
  body.mobile-view .link-filter-row { gap: 8px; flex: 1 1 100%; flex-wrap: wrap; }
  body.mobile-view .tag-filter-wrap { min-width: 0; flex: 1 1 140px; }
  body.mobile-view #tagSelectFilter { flex: 1 1 100px; min-width: 90px; }
  body.mobile-view #typeFilterSelect { flex: 1 1 100px; min-width: 90px; }
  body.mobile-view .project-todo-add { min-width: 0; }

  /* Side drawers and the More screen are full-viewport tab targets on mobile,
     shown by .mobile-visible (JS); the desktop .open/backdrop mechanism stays inert. */
  /* Normal document flow, NOT position:fixed: right where #mainContent sits, below
     #appHeader, so brand/switcher/search stay visible on every tab (fixed overlays
     covered the header and collided with it). */
  body.mobile-view .side-drawer,
  body.mobile-view #moreScreen,
  body.mobile-view #todayScreen {
    position: static; top: auto; right: auto; width: 100%; min-width: 0; height: auto;
    box-shadow: none; border-left: none; z-index: auto;
    /* Overrides .side-drawer's white background: the page background is the tinted
       --project-tint; cards/tables carry their own white, like the Links tab. */
    background: transparent;
    /* min-height fills the viewport so the tinted page background doesn't show
       under a short screen (Today); taller content still grows. */
    min-height: 100vh;
    transition: none; display: none; padding: 10px 14px 84px;
    /* Now the first thing after the fixed #appHeader on every one of
       these screens (#projectIdentityBar no longer precedes #todoPanel
       on the To-Dos screen either — it's hidden on mobile everywhere). */
    padding-top: calc(var(--mobile-header-h, 120px) + 10px);
    /* Same clearance as #mainContent: the Notes/To-Do floating bar sits above the
       nav here too (harmless extra space on Today/More). */
    padding-bottom: calc(var(--mobile-nav-h, 64px) + 74px);
  }
  body.mobile-view .side-drawer.mobile-visible,
  body.mobile-view #moreScreen.mobile-visible,
  body.mobile-view #todayScreen.mobile-visible { display: flex; flex-direction: column; gap: 24px; }
  #moreScreen { display: none; }
  #todayScreen { display: none; }
  /* Today and Recent span every project, so the switcher/pinned bar/identity row
     are hidden there. Shortcuts (More) keeps the switcher (quick links are per
     project; 2026-10-08), hiding only its pinned bar/identity row. Desktop keeps
     the full header on every page; only mobile strips search (no room for a row;
     quick-launch and dividers are already hidden on mobile). */
  body.mobile-screen-more #pinnedBar,
  body.mobile-screen-more #projectIdentityBar,
  body.mobile-screen-today #pinnedBar,
  body.mobile-screen-today #projectIdentityBar,
  body.mobile-screen-recent #pinnedBar,
  body.mobile-screen-recent #projectIdentityBar { display: none; }
  /* Upcoming/Recent keep the project switcher (2026-10-23): there it filters
     only that screen (overviewProjectFilter), starting at All Projects. */
  body.mobile-view.mobile-screen-more .search-wrap,
  body.mobile-view.mobile-screen-today .search-wrap,
  body.mobile-view.mobile-screen-recent .search-wrap { display: none; }
  /* Pinned items are pinned links — not relevant on the To-Dos or Notes
     tab, so the pinned bar stays Links-tab-only on mobile. */
  body.mobile-screen-todos #pinnedBar,
  body.mobile-screen-notes #pinnedBar { display: none; }
  /* A note's project is metadata, not a filter, so "this project"'s name + gear
     would mislead here. The project switcher stays. */
  body.mobile-screen-notes #projectIdentityBar { display: none; }

  /* ---- Desktop page navigation (Links / To-Do / Notes / Today / Recent) ----
     The mobileScreen/applyMobileScreen() switch also drives desktop: the To-Do,
     Notes and Recent drawers and the Today screen are full pages reached from
     topbar icons. Gated to non-mobile so it never fights the more specific
     body.mobile-view rules above. */
  body:not(.mobile-view) .side-drawer.mobile-visible,
  body:not(.mobile-view) #todayScreen.mobile-visible {
    position: static; top: auto; right: auto; width: 100%; min-width: 0; height: auto;
    box-shadow: none; border-left: none; z-index: auto;
    /* Overrides .side-drawer's own (unscoped) white background — matches
       Links, where the tinted body shows around a white table-wrap rather
       than the whole page being a solid white panel. */
    background: transparent;
    transition: none;
    display: flex; flex-direction: column; gap: 24px;
    padding: 14px 28px 90px;
    /* #projectIdentityBar is permanently hidden (renderProjectIdentityBar()), so its
       hidden-state rule only offsets #mainContent; every other page needs its own
       copy of the offset here (none are siblings of #projectIdentityBar). */
    padding-top: calc(var(--mobile-header-h, 70px) + 14px);
  }
  /* Full-width rows, icon+name centered, plus a dashed "add" tile — per
     the design: these are meant to be big, obvious tap targets, not the
     small topbar icons. */
  .more-quick-launch { display: flex; flex-direction: column; gap: 10px; }
  .more-quick-launch-row {
    display: flex; align-items: center;
    border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-elevated);
    box-shadow: var(--shadow); overflow: hidden;
  }
  .more-quick-launch-row a {
    flex: 1; display: flex; align-items: center; justify-content: center; gap: 10px;
    padding: 16px 14px; text-decoration: none; color: var(--text); font-size: 14.5px; font-weight: 600;
  }
  .more-quick-launch-row a svg { width: 22px; height: 22px; }
  [data-theme="dark"] .more-quick-launch-row a img.ql-icon-mono { filter: invert(1); }
  .more-quick-launch-row a img { width: 22px; height: 22px; object-fit: contain; }
  .more-quick-launch-edit { flex-shrink: 0; padding: 16px 14px; border: none; border-left: 1px solid var(--border); background: none; color: var(--text-faint); border-radius: 0; }
  .more-quick-launch-add {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 16px 14px; border: 1px dashed var(--border); border-radius: var(--radius);
    color: var(--text-faint); font-size: 14px; font-weight: 600; background: none;
  }
  .more-quick-launch-add:hover { color: var(--primary-text); border-color: var(--primary); }

  .mobile-nav {
    position: fixed; left: 0; right: 0; bottom: 0;
    justify-content: space-around;
    background: var(--bg-elevated);
    border-top: 1px solid var(--border);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
    z-index: 130;
  }
  body.mobile-view .mobile-nav { display: flex; }
  .mobile-nav-btn {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    background: none; border: none; padding: 6px 2px; color: var(--text-faint);
    font-size: 10px; font-weight: 600; border-radius: var(--radius-sm); min-width: 0;
  }
  .mobile-nav-btn .mn-label { white-space: nowrap; }
  .mobile-nav-btn.active { color: var(--primary-text); }
  .mobile-nav-btn .mn-icon svg { width: 22px; height: 22px; }

  /* Desktop topbar page-nav icons (Links/To-Do/Notes/Today) — same active
     look already used for row-action toggle buttons elsewhere. */
  .topbar-page-btn.active { color: var(--primary-text); background: var(--primary-soft); }

  /* Links table -> cards */
  body.mobile-view .link-table thead { display: none; }
  body.mobile-view .link-table tbody tr {
    display: flex; flex-wrap: wrap; align-items: center; gap: 5px var(--card-col-gap);
    padding: var(--card-pad-y) var(--card-pad-x); border-bottom: 1px solid var(--border);
  }
  body.mobile-view .link-table td { display: inline-flex; align-items: center; width: auto; padding: 0; }
  body.mobile-view .col-drag { display: none; }
  /* Fixed-width leading slot (same --card-lead-w as the To-Do card's
     check icon) so both cards' titles start at the same indent. */
  body.mobile-view .link-table .col-type { order: 0; width: var(--card-lead-w); flex: 0 0 auto; }
  /* flex-basis 0 + min-width 0: a long title wraps inside its own space next to the
     type badge instead of the whole title dropping to the next line (2026-10-23). */
  body.mobile-view .link-table .col-title { order: 1; flex: 1 1 0%; min-width: 0; max-width: 100%; }
  body.mobile-view .link-table .col-title .link-title { white-space: normal; overflow-wrap: anywhere; }
  body.mobile-view .link-table .col-desc { order: 3; flex: 1 1 100%; font-size: 12px; }
  body.mobile-view .link-table .col-tags { order: 4; flex: 1 1 100%; max-width: 100%; }
  /* Link tags (.tag-chip) use the same box metrics as the type badge and the
     priority/due-in/project chips, so they match in height side by side. */
  body.mobile-view .tag-chip {
    padding: 4px 6px; font-size: 9.5px; font-weight: 600; letter-spacing: .02em;
  }
  /* "Added" date isn't shown on the mobile card — the title/tags/type
     already fill the space better. */
  body.mobile-view .link-table .col-date { display: none; }
  body.mobile-view .link-table .col-project { order: 5; }
  /* Actions row: everything on one row, delete pushed right via margin-left:auto on
     the row itself (the card's bottom row), so it lands bottom-right in line. */
  body.mobile-view .link-table .col-actions { order: 6; flex: 1 1 100%; margin-top: 4px; }
  body.mobile-view .link-table .col-actions .row-actions { width: 100%; }
  body.mobile-view .link-table .col-actions [data-action="delete-link"],
  body.mobile-view .link-table .col-actions [data-action="delete-note"] {
    margin-left: auto;
  }
  /* Pin button now shows on mobile cards too (2026-10-20) — pinned links
     appear in the collapsible Pinned Links bar under the header. */

  /* To-do tables (To-Dos, Today, Recent To-Dos, search) -> cards, four rows:
       1. title (wraps)
       2. description (only when there is one)
       3. priority + due-in (+ project, cross-project pages only) tags
       4. action-icon row: check first, then the icons, trash far right
     The check button sits in the icon row (flex wrapping to content length made
     rows look random on row 1). Tags keep their own row: with up to five icons plus
     tag pills plus trash one line is too narrow on a phone. Rows 3 and 4 are one
     .col-actions flex-wrap line; the tags span is forced onto its own full-width
     line (order:-1 + flex-basis:100%), see .mobile-todo-tags. No divider between
     rows 3/4 and the description, just the cards' normal gap. */
  body.mobile-view table.todo-table thead { display: none; }
  body.mobile-view table.todo-table tbody tr.todo-row {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px var(--card-col-gap);
    padding: var(--card-pad-y) var(--card-pad-x); border-bottom: 1px solid var(--border);
    /* .todo-item's base rule (shared with the backlinks popup's plain
       <li> rows) paints a gray background — override it back to the
       table's own white here, same as the desktop-only reset above. */
    background: var(--bg-elevated);
  }
  body.mobile-view table.todo-table td { display: inline-flex; align-items: center; width: auto; padding: 0; }
  /* The desktop-only columns whose content now lives inside the
     action-icon row instead (see comment above). */
  body.mobile-view table.todo-table .col-check,
  body.mobile-view table.todo-table .col-priority,
  body.mobile-view table.todo-table .col-due-in,
  body.mobile-view table.todo-table .col-project,
  body.mobile-view table.todo-table .col-drag {
    display: none;
  }
  /* Row 1: title alone. Left padding lines the title up with the check icon's
     visible start (the ghost-btn has internal padding); extra right padding keeps
     long titles off the card edge. */
  body.mobile-view table.todo-table .col-todo-title {
    order: 0; flex: 1 1 100%; max-width: 100%;
    white-space: normal; word-break: break-word;
    padding-left: 10px; padding-right: 10px;
  }
  /* Row 2: description — its own line, dropped entirely when there's no
     description to show, same left/right indent as the title above it. */
  body.mobile-view table.todo-table .col-todo-desc {
    order: 1; flex: 1 1 100%; font-size: 12px; color: var(--text-dim);
    padding-left: 10px; padding-right: 10px;
  }
  body.mobile-view table.todo-table .col-todo-desc:empty { height: 0; overflow: hidden; padding: 0; }
  /* Block, not the cells' inline-flex: otherwise every line of the description
     became its own side-by-side column (2026-10-23). Shows 3 lines + Show more. */
  body.mobile-view table.todo-table .col-todo-desc { display: block; }
  .todo-desc-clamp { --clamp-lines: 3; }
  /* The raw deadline date is redundant next to the Due In tag. */
  body.mobile-view table.todo-table .col-deadline { display: none; }
  /* Not part of the new card spec — dropped from the mobile card
     entirely (still shown in the desktop table's own column). */
  /* Rows 3+4: tags then the action-icon row in one flex-wrap container (see
     .row-actions override below); align-items:center keeps the pills centered
     against the taller icon buttons. */
  body.mobile-view table.todo-table .col-actions {
    order: 2; flex: 1 1 100%; margin-top: 2px;
  }
  body.mobile-view table.todo-table .col-actions .row-actions {
    width: 100%; flex-wrap: wrap; align-items: center; gap: 6px calc(var(--card-icon-gap) * 1.3);
  }
  body.mobile-view table.todo-table .col-actions [data-action="delete-todo"] {
    margin-left: auto;
  }
  /* Only for the mobile card: the desktop table already shows check/priority/
     due-in/project in real columns (display:none block above). */
  body:not(.mobile-view) .mobile-check-btn,
  body:not(.mobile-view) .mobile-todo-tags {
    display: none;
  }
  /* Own full-width line ahead of the icon buttons (order:-1 sorts first,
     flex-basis:100% claims the line), so tags never crowd the five icons. */
  body.mobile-view table.todo-table .mobile-todo-tags {
    order: -1; flex: 1 1 100%;
    display: inline-flex; align-items: center; gap: var(--card-col-gap);
    /* .col-actions has no left padding while .col-todo-title/.col-todo-desc add 10px;
       match it so the first tag lines up under the title. */
    padding-left: 10px;
  }
  /* Same box metrics as .type-badge so the due-in and project tags match the
     priority chip's height. .mobile-project-tag is the shared tinted project chip,
     also used outside the To-Do table (Notes cards, connections popup). */
  .mobile-project-tag {
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 9.5px; font-weight: 600; letter-spacing: .02em;
    padding: 4px 6px; border-radius: 6px; background: var(--bg-soft-2);
  }
  body.mobile-view table.todo-table .mobile-todo-tags .due-in-phrase {
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 9.5px; font-weight: 600; letter-spacing: .02em;
    padding: 4px 6px; border-radius: 6px; background: var(--bg-soft-2);
  }
  body.mobile-view table.todo-table .mobile-todo-tags .due-in-phrase.deadline-urgent-red { background: #ef444422; color: #ef4444; }
  body.mobile-view table.todo-table .mobile-todo-tags .due-in-phrase.deadline-urgent-orange { background: #f59e0b22; color: #f59e0b; }
  body.mobile-view table.todo-table .mobile-todo-tags .due-in-phrase.deadline-urgent-yellow { background: #eab30822; color: #eab308; }
  body.mobile-view table.todo-table .mobile-todo-tags .due-in-phrase.due-done { background: color-mix(in srgb, var(--success) 20%, transparent); color: var(--success); }
  body.mobile-view table.todo-table .mobile-todo-tags .due-in-phrase:empty { display: none; }

  /* Every modal becomes a full-viewport sheet on mobile — safer to tap
     than a small centered card, and there's always a visible Cancel/X
     to dismiss (no Escape key on a phone). */
  body.mobile-view .modal-overlay .modal {
    width: 100vw; max-width: 100vw; height: 100%; max-height: 100%;
    border-radius: 0; overflow-y: auto; padding-bottom: 32px;
  }
  /* #confirmModal stays the small centered card from the desktop .modal rule, not a
     full-screen sheet (a plain Yes/No prompt). */
  body.mobile-view #confirmModal .modal {
    width: 400px; max-width: calc(100vw - 32px); height: auto; max-height: 85vh;
    border-radius: var(--radius-lg); overflow-y: auto; padding-bottom: 24px;
  }
  /* Modal rows stack label-above-field on mobile: a fixed 100px label column ate
     the narrow sheet and truncated inputs. The !important pair overrides rows'
     inline align-items:flex-start (desktop multi-line fields), which would block
     full-width stretch once stacked. */
  body.mobile-view .modal .row {
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 4px;
  }
  body.mobile-view .modal .row label { flex: 0 0 auto; }
  body.mobile-view .modal .row label:empty { display: none; }

  /* ---- Phone touch heights (2026-10-23): one shared 46px rule for popup buttons,
     inputs and selects, and page-level selects; same size as the Connect popup. ---- */
  body.mobile-view .modal button:not(.icon-only):not(.modal-close-x):not(.link-btn):not(.proj-grip),
  body.mobile-view .mobile-fab-row button {
    min-height: 46px; font-size: 15px; justify-content: center;
  }
  body.mobile-view .modal input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]),
  body.mobile-view .modal select,
  body.mobile-view select {
    min-height: 46px; font-size: 15px;
  }
  body.mobile-view select { padding-right: 32px; }
  /* Cancel/Save rows: buttons share the width like the Connect popup. */
  body.mobile-view .modal .modal-actions .right-actions { width: 100%; }
  body.mobile-view .modal .modal-actions .right-actions button { flex: 1 1 0; }

  /* ---- Phone project picker: bigger text, roomier rows (2026-10-23) ---- */
  body.mobile-view .project-switcher-item { font-size: 15px; padding: 13px 12px; }
  body.mobile-view .project-switcher-heading { font-size: 12px; }

  /* ---- Phone To-Do / Today card icon spacing = Links and Notes cards (12px between icons) ---- */
  body.mobile-view table.todo-table .col-actions .row-actions { gap: 6px 12px; }

  /* ---- Desktop headings: lighter weight (2026-10-23); phone keeps its look ---- */
  body:not(.mobile-view) .mobile-links-heading,
  body:not(.mobile-view) .recent-section-heading,
  body:not(.mobile-view) .panel-block h3,
  body:not(.mobile-view) #pinnedBar .pinned-label,
  body:not(.mobile-view) .search-results-header { font-weight: 600; }
  body:not(.mobile-view) .pinned-toggle .pinned-toggle-label { font-weight: 600; }

  /* ---- Phone To-Do popup buttons (2026-10-23): "Add to Calendar" gets its own
     full-width purple row under "Created"; Delete / Cancel / Save share the
     row below (they were squeezed into one row with it). ---- */
  body.mobile-view #todoDetailsModal .modal-actions { flex-wrap: wrap; row-gap: 10px; }
  body.mobile-view #todoDetailsModal .modal-actions .right-actions { display: contents; }
  body.mobile-view #todoDetailsModal .modal-actions > button,
  body.mobile-view #todoDetailsModal .modal-actions .right-actions > button { flex: 1 1 0; }
  body.mobile-view #todoDetailsModal .cal-wrap:not(:has(> #addToCalendarTodoBtn.hidden)) { order: -1; flex: 1 1 100%; display: flex; }
  body.mobile-view #todoDetailsModal .cal-wrap > button { flex: 1; background: var(--primary); border-color: var(--primary); color: #fff; }
  /* Same text size as the Links filter row (All tags / All types), 46px tall. */
  body.mobile-view #todoAddRow #mobileTodoSortSelect { font-size: 12.5px; }
  /* Links filter row (2026-10-23): the "Filter links…" box matches the
     46px tag/type dropdowns next to it (height and 12.5px text). */
  body.mobile-view .link-filter-row .tag-filter-wrap input { box-sizing: border-box; height: 46px; font-size: 12.5px; }
