    /* ── Source picker (+) button ─── */
    .source-picker-btn {
      width: 32px;
      height: 32px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      background: transparent;
      border: 0;
      color: var(--ink-strong);
      border-radius: var(--r-pill);
      font-size: 22px;
      font-weight: 350;
      cursor: pointer;
      transition: background var(--dur) ease, color var(--dur) ease, transform var(--dur) ease;
      padding: 0;
      line-height: 1;
    }
    .source-picker-btn:hover { background: var(--hover-bg); color: var(--accent-hover); transform: translateY(-1px); }

    /* Anchored to both edges of the composer it belongs to, so the popup is
       always exactly as wide as the input box rather than a fixed 640px. */
    .source-picker-popup {
      display: none;
      position: absolute;
      left: 0;
      right: 0;
      bottom: calc(100% + 8px);
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--r-xl);
      z-index: 70;
      overflow: hidden;
      box-shadow: var(--shadow-pop);
    }
    .source-picker-popup.open {
      display: flex;
      flex-direction: column;
      max-height: calc(100vh - 24px);
    }
    .source-picker-popup.opens-below {
      top: calc(100% + 8px);
      bottom: auto;
    }

    .source-picker-header {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 12px 14px;
      border-bottom: 1px solid var(--border-soft);
    }
    .source-picker-tabs {
      display: flex;
      flex: 0 0 auto;
      gap: 14px;
      margin-bottom: 0;
      padding: 0;
      border: 0;
      border-radius: var(--r-md);
      background: transparent;
    }
    .source-picker-tab {
      flex: 0 1 auto;
      padding: 7px 2px 6px;
      border-radius: 0;
      border: 0;
      border-bottom: 2px solid transparent;
      background: transparent;
      color: var(--muted);
      font-size: 13px;
      font-weight: 550;
      cursor: pointer;
      transition: background var(--dur), color var(--dur);
      text-align: center;
    }
    /* Tabs are marked by a rule under the active label, not a filled shape.
       The pill this replaced was the loudest thing in a quiet UI, and it had to
       carry a tan strong enough to beat the surface, which made it louder still.
       The transparent border on the resting state reserves the 2px so switching
       tabs never shifts the row. Same pattern as .zz-tab on the Zelená page,
       which already worked this way. */
    .source-picker-tab:hover { background: transparent; color: var(--ink); }
    .source-picker-tab.active {
      background: transparent;
      color: var(--ink);
      border-bottom-color: var(--accent);
    }

    .source-picker-filter {
      width: auto;
      flex: 1 1 auto;
      min-width: 0;
      background: var(--surface);
      border: 1px solid var(--border-input);
      border-radius: var(--r-sm);
      color: var(--ink);
      padding: 8px 10px;
      font-size: 13px;
      outline: none;
      transition: border-color var(--dur);
    }
    .source-picker-filter::placeholder { color: var(--faint); }
    .source-picker-filter:focus {
      border-color: var(--border-focus);
      box-shadow: none;
    }
    .source-picker-filter::selection {
      background: rgba(15, 23, 42, 0.14);
      color: inherit;
    }

    /* Source-adjacent actions and the one chat setting share a compact footer
       instead of competing with the filter for header space. */
    /* Three even actions, one row: what a message can carry besides its text.
       The modes moved to the composer, which is what un-crowded this. */
    .source-picker-actions {
      flex: 0 0 auto;
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 6px;
      border-top: 1px solid var(--border-soft);
    }
    .source-picker-actions[hidden] { display: none; }

    .source-picker-action {
      flex: 1 1 0;
      min-width: 0;
      min-height: 36px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      padding: 0 12px;
      border: 0;
      border-radius: var(--r-sm);
      background: transparent;
      color: var(--text);
      font: inherit;
      font-size: 13px;
      font-weight: 500;
      cursor: pointer;
      white-space: nowrap;
      transition: background var(--dur), color var(--dur);
    }
    .source-picker-action[hidden] { display: none; }
    .source-picker-action:hover { background: var(--hover-bg); color: var(--ink); }
    .source-picker-action svg {
      width: 17px;
      height: 17px;
      flex: 0 0 17px;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.8;
      stroke-linecap: round;
      stroke-linejoin: round;
    }

    .source-picker-list {
      flex: 1 1 auto;
      min-height: 0;
      max-height: 340px;
      overflow-y: auto;
      padding: 6px;
    }
    .source-picker-item {
      padding: 10px 10px;
      cursor: pointer;
      border: 0;
      border-radius: var(--r-sm);
      font-size: 13.5px;
      color: var(--ink);
      display: flex;
      align-items: center;
      gap: 10px;
      transition: background var(--dur);
    }
    .source-picker-item:hover { background: var(--hover-bg); }
    .source-picker-item.active { background: var(--active-bg); }
    .source-picker-source-icon {
      width: 21px;
      height: 21px;
      opacity: .78;
    }
    .source-picker-source-icon svg {
      width: 18px;
      height: 18px;
    }
    .source-picker-empty { padding: 16px 14px; color: var(--muted); font-size: 13px; text-align: center; }
    .source-picker-empty.loading {
      display: grid;
      place-items: center;
      justify-items: center;
      min-height: 92px;
      padding: 18px 14px;
    }
    .source-picker-empty.loading .quiet-loader {
      margin: 0 auto;
    }

    /* ── Upload modal ─── */
    .upload-modal-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(15, 23, 42, .36);
      z-index: 100;
      align-items: center;
      justify-content: center;
    }
    .upload-modal-overlay.open { display: flex; }

    .upload-modal {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--r-xl);
      box-shadow: var(--shadow-pop);
      width: 560px;
      max-width: 90vw;
      max-height: 80vh;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }
    .upload-modal-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 16px 20px;
      border-bottom: 1px solid var(--border-soft);
    }
    .upload-modal-title { font-size: 16px; font-weight: 650; color: var(--ink); }
    .upload-modal-close {
      background: transparent;
      border: none;
      color: var(--muted);
      font-size: 20px;
      cursor: pointer;
      padding: 4px 8px;
      border-radius: var(--r-sm);
      transition: background var(--dur), color var(--dur);
    }
    .upload-modal-close:hover { background: var(--hover-bg); color: var(--ink); }

    .upload-modal-body { padding: 20px; overflow-y: auto; flex: 1; }

    /* A notice that leaves on its own, in the same light-surface-and-hairline
       language as every other floating thing here: the card ground, the app's
       float shadow, --r-lg. It sits over the top of the content column and
       never takes focus, so whatever was being typed keeps the caret. */
    .app-notice {
      position: fixed;
      top: 18px;
      /* Centred over the content, not the window: the rail holds the left edge,
         and everything else in the app -- the composer, the empty-state line --
         is centred on what is left. Half the rail's width is the correction. */
      left: calc(50% + (var(--rail-width) / 2));
      z-index: 120;
      display: flex;
      flex-direction: column;
      gap: 2px;
      max-width: min(30rem, calc(100vw - 2rem));
      padding: 11px 15px;
      border: 1px solid var(--border);
      border-radius: var(--r-lg);
      background: var(--surface);
      box-shadow: var(--shadow-float);
      cursor: default;
      opacity: 0;
      /* The -6px is the distance it falls in; the -50% is what keeps it centred
         while it does, so both live in the same transform. */
      transform: translate(-50%, -6px);
      transition: opacity .18s ease, transform .18s ease;
    }

    /* An author-sheet `display` beats the UA rule for [hidden], so the attribute
       has to be honoured explicitly or the notice never actually leaves. */
    .app-notice[hidden] { display: none; }

    .app-notice.visible {
      opacity: 1;
      transform: translate(-50%, 0);
    }

    .app-notice-title {
      color: var(--ink);
      font-size: 13.5px;
      font-weight: 600;
      letter-spacing: -.01em;
    }

    .app-notice-text {
      color: var(--muted);
      font-size: 13px;
      line-height: 1.5;
      text-wrap: pretty;
    }

    .app-notice-text[hidden] { display: none; }

    /* The rail is off-canvas here, so the content is the whole window again. */
    @media (max-width: 720px) {
      .app-notice { left: 50%; }
    }

    @media (prefers-reduced-motion: reduce) {
      .app-notice { transition: opacity .18s ease; transform: translate(-50%, 0); }
    }

    /* A deploy never takes over an open analysis on its own. The compact card
       appears only when a new worker is ready and leaves the reload to the
       reader, using one text action instead of a large install-style banner. */
    .pwa-update {
      position: fixed;
      right: 18px;
      bottom: max(18px, env(safe-area-inset-bottom));
      z-index: 125;
      display: flex;
      align-items: center;
      gap: 10px;
      max-width: calc(100vw - 36px);
      padding: 9px 9px 9px 14px;
      border: 1px solid var(--border);
      border-radius: var(--r-lg);
      background: var(--surface);
      box-shadow: var(--shadow-float);
      color: var(--text);
      font-size: 13.5px;
    }
    .pwa-update[hidden] { display: none; }
    .pwa-update-apply,
    .pwa-update-dismiss {
      border: 0;
      background: transparent;
      color: var(--accent);
      font: inherit;
      cursor: pointer;
    }
    .pwa-update-apply {
      min-height: 32px;
      padding: 0 7px;
      font-weight: 650;
    }
    .pwa-update-apply:hover { color: var(--accent-hover); }
    .pwa-update-apply:disabled { color: var(--faint); cursor: wait; }
    .pwa-update-dismiss {
      width: 32px;
      height: 32px;
      display: grid;
      flex: 0 0 32px;
      place-items: center;
      padding: 0;
      border-radius: var(--r-pill);
      color: var(--muted);
    }
    .pwa-update-dismiss:hover { background: var(--hover-bg); color: var(--ink); }
    .pwa-update-dismiss svg {
      width: 16px;
      height: 16px;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.8;
      stroke-linecap: round;
    }
    .pwa-update button:focus-visible {
      outline: 2px solid var(--border-focus);
      outline-offset: 1px;
    }

    @media (max-width: 560px) {
      .pwa-update {
        left: 12px;
        right: 12px;
        bottom: max(12px, env(safe-area-inset-bottom));
        max-width: none;
      }
      .pwa-update > span { flex: 1 1 auto; }
      .pwa-update-apply { min-height: 44px; }
      .pwa-update-dismiss { width: 44px; height: 44px; flex-basis: 44px; }
    }

    .connections-intro {
      margin: 0 0 14px;
      color: var(--muted);
      font-size: 13px;
      line-height: 1.55;
    }
    /* Holds current access and how to set it up, so it needs more room than a
       plain confirmation dialog. */
    .upload-modal-wide { width: 640px; }

    /* A link and the one thing anyone does with it. Nothing is filled in here,
       so it drops the rules an upload form needs and reads as the app's own
       floating card: warm ground, one hairline, generous corners. Dismissing is
       a click anywhere off it, so no button competes with the copy. */
    .share-modal { width: 424px; }
    .share-modal .upload-modal-header { padding: 20px 22px 2px; border-bottom: 0; }
    .share-modal .upload-modal-title {
      font-family: var(--font-display);
      font-size: 20px;
      font-weight: 600;
      letter-spacing: -.01em;
    }
    .share-modal .upload-modal-body { padding: 8px 22px 0; }
    .share-modal .upload-modal-footer { padding: 16px 22px 20px; border-top: 0; justify-content: flex-end; }
    /* The glyph rides with the label rather than sitting in its own box: same
       optical weight as the app's other icons, and the padding closes up so the
       pair reads as one control instead of a wide green slab. */
    .share-modal-copy {
      display: inline-flex;
      align-items: center;
      gap: 7px;
      padding: 8px 15px 8px 13px;
      border-radius: var(--r-md);
    }
    .share-modal-copy svg {
      width: 14px;
      height: 14px;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.8;
      stroke-linecap: round;
      stroke-linejoin: round;
    }
    .share-modal-intro {
      margin: 0 0 14px;
      color: var(--muted);
      font-size: 13px;
      line-height: 1.55;
    }
    /* The link is the point of the dialog, so it is selectable and scrolls
       sideways rather than wrapping into something nobody can copy by hand. */
    .share-modal-link {
      display: block;
      padding: 11px 13px;
      border: 1px solid var(--border-soft);
      border-radius: var(--r-lg);
      background: var(--surface-inset);
      color: var(--ink);
      font-family: var(--font-mono);
      font-size: 12.5px;
      line-height: 1.4;
      overflow-x: auto;
      white-space: nowrap;
      user-select: all;
    }
    /* The cross is drawn, not typed, so it needs the stroke every other dialog
       gives its own -- without it the paths fill to nothing and the button is
       an invisible target. */
    .share-modal .upload-modal-close svg {
      width: 17px;
      height: 17px;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.7;
      stroke-linecap: round;
    }

    .connections-list { display: flex; flex-direction: column; gap: 10px; }
    .connection-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      padding: 12px 14px;
      border: 1px solid var(--border-soft);
      border-radius: var(--r-md);
      background: var(--surface-inset);
    }
    .connection-name { font-size: 14px; }
    .connection-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
    .connection-revoke {
      flex: none;
      font: inherit;
      font-size: 13px;
      padding: 6px 12px;
      border-radius: var(--r-sm);
      border: 1px solid var(--border-soft);
      background: var(--surface);
      color: var(--text);
      cursor: pointer;
    }
    .connection-revoke:hover { border-color: var(--text); }
    .connection-revoke[disabled] { opacity: .55; cursor: default; }
    .connections-empty { color: var(--muted); font-size: 13px; }

    .connect-codex-steps {
      margin: 0 0 14px;
      padding-left: 1.2rem;
      color: var(--text);
      font-size: 13px;
      line-height: 1.7;
    }
    .connect-codex-steps li { margin: 0; }

    /* The command is the point of the dialog, so it gets to be selectable and
       scrollable rather than wrapping into something nobody can copy by hand. */
    .connect-codex-command {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 12px 14px;
      margin: 0 0 14px;
      border: 1px solid var(--border-soft);
      border-radius: var(--r-md);
      background: var(--surface-inset);
    }
    /* Separates the one-off install from the command someone comes back for
       later, without a heading heavy enough to compete with the steps. */
    .connect-codex-divider {
      margin: 4px 0 12px;
      color: var(--muted);
      font-size: 12.5px;
      font-weight: 600;
    }

    /* A rule only where one section actually follows another, so the first
       heading after the list is separated without boxing every block in. */
    .connect-codex-divider + ol,
    .connect-codex-divider + .connect-codex-command { margin-top: 0; }
    .connect-codex-command + .connect-codex-divider {
      margin-top: 20px;
      padding-top: 18px;
      border-top: 1px solid var(--border-soft);
    }

    .connect-codex-command code {
      flex: 1 1 auto;
      min-width: 0;
      overflow-x: auto;
      white-space: nowrap;
      font-size: 12.5px;
      color: var(--ink);
      user-select: all;
    }

    .connect-codex-onboarding { margin-bottom: 14px; }
    .connect-codex-open { text-decoration: none; white-space: nowrap; }
    .connect-codex-fallback-title {
      margin-top: 4px;
      padding-top: 18px;
      border-top: 1px solid var(--border-soft);
    }

    .upload-modal-dropzone {
      border: 1px dashed var(--border-card-hover);
      border-radius: var(--r-lg);
      background: var(--surface-inset);
      padding: 32px;
      text-align: center;
      cursor: pointer;
      transition: border-color var(--dur), background var(--dur);
      margin-bottom: 16px;
    }
    .upload-modal-dropzone:hover,
    .upload-modal-dropzone.drag-over { border-color: var(--green); background: var(--green-soft); }
    .upload-modal-dropzone-icon { font-size: 36px; display: block; margin-bottom: 8px; }
    .upload-modal-dropzone-text { color: var(--muted); font-size: 13px; }
    .upload-modal-dropzone-hint { color: var(--faint); font-size: 12px; margin-top: 4px; }

    .upload-modal-filelist { display: flex; flex-direction: column; gap: 8px; }
    .upload-modal-file-item {
      display: flex;
      align-items: flex-start;
      gap: 12px;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--r-lg);
      box-shadow: var(--shadow-card);
      padding: 12px 14px;
    }.upload-modal-file-name {
      font-size: 13px;
      font-weight: 600;
      color: var(--ink);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }.upload-modal-file-docname {
      width: 100%;
      background: var(--surface-inset);
      border: 1px solid var(--border-card-hover);
      border-radius: var(--r-sm);
      color: var(--ink);
      padding: 8px 10px;
      font-size: 13px;
      outline: none;
      transition: border-color var(--dur), background var(--dur);
    }
    .upload-modal-file-docname::placeholder { color: var(--faint); }
    .upload-modal-file-docname:focus { border-color: var(--border-focus); background: var(--surface); }
    .upload-modal-footer {
      padding: 12px 20px 16px;
      border-top: 1px solid var(--border-soft);
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 12px;
    }

    /* ── Chat row menu ─── */
    /* The rail's per-chat options. Same light floating language as the notice
       and the tooltips: surface ground, hairline, the app's float shadow. */
    .chat-row-menu {
      position: fixed;
      z-index: 130;
      min-width: 168px;
      padding: 5px;
      border: 1px solid var(--border);
      border-radius: var(--r-lg);
      background: var(--surface);
      box-shadow: var(--shadow-float);
      display: flex;
      flex-direction: column;
      gap: 1px;
    }

    .chat-row-menu[hidden] { display: none; }

    .chat-row-menu-item {
      appearance: none;
      border: 0;
      background: transparent;
      color: var(--ink);
      font-family: inherit;
      font-size: 13.5px;
      line-height: 20px;
      text-align: left;
      padding: 7px 10px;
      border-radius: var(--r-sm);
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 9px;
      transition: background var(--dur);
    }

    .chat-row-menu-icon {
      width: 16px;
      height: 16px;
      flex: 0 0 auto;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.7;
      stroke-linecap: round;
      stroke-linejoin: round;
      /* Quieter than the label it belongs to, except on the destructive row,
         where the colour is the warning and the icon carries it too. */
      opacity: .75;
    }

    .chat-row-menu-item.is-destructive .chat-row-menu-icon { opacity: 1; }

    .chat-row-menu-item:hover,
    .chat-row-menu-item:focus-visible {
      background: var(--hover-bg);
      outline: none;
    }

    /* Deleting a chat cannot be undone, so it does not read like the two above
       it -- but it stays quiet until pointed at, rather than colouring the menu. */
    .chat-row-menu-item.is-destructive { color: var(--red); }
    .chat-row-menu-item.is-destructive:hover,
    .chat-row-menu-item.is-destructive:focus-visible {
      background: color-mix(in srgb, var(--red) 8%, transparent);
    }
