  :root {
    color-scheme: dark; --resp-font-size: 11px;
    /* Rang tokenlari (--btn-*, --accent*, --border, --text-muted/-secondary,
       --modal-x-*, --color-success/danger/warning/info) semantic.css +
       components.css'ga ko'chdi — yagona rang manbai o'sha. Bu yerda faqat
       rangsiz tokenlar (spacing, radius, font, runtime-set) qoladi.
       --brand/--brand-2 ham semantic.css'da (PALETTE bo'limi). */
    --app-font-family: 'Inter', -apple-system, Segoe UI, sans-serif;
    --app-font-size: 14px;
    /* 4px asosli spacing shkalasi (8px sistemaning yarim qadamlari bilan) —
       yangi/qayta yoziladigan komponentlar shu tokenlardan foydalansin,
       hardcoded px qiymat yozmasin. */
    --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
    --space-5: 20px; --space-6: 24px; --space-7: 32px; --space-8: 48px; --space-9: 64px;
    --radius-sm: 4px; --radius-md: 6px; --radius-lg: 10px;
    /* Qo'shimcha surface qatlamlari (Linear uslubidagi layered panel/card
       ierarxiya uchun) — --surface allaqachon bor edi, shu bilan bir qatorda. */
    /* Sovuq neytral (status page uslubi) — yashil tuslanish olib tashlandi. */
    --surface: #161b21; --surface-2: #1d232b; --surface-3: #262d36;
    /* Ilgari ko'p joyda literal hex (token'siz) yozilgan edi — shu ikkita
       "qora" qatlam endi token orqali, shunda :root[data-theme="black"]
       (pastda) ularni pastga bosib, o'zgarish BUTUN ilova bo'ylab ishlaydi,
       alohida-alohida selector qidirmasdan. */
    --surface-void: #0d1014; --surface-recessed: #10151a;
    /* --color-success/danger/warning/info (+ -bg/-border) semantic.css'ga
       ko'chdi (SEMANTIC bo'limi, har mavzu uchun). */
    /* Declared (unset) here purely so static CSS tooling can resolve them —
       these five are only ever given real values at runtime by the inline
       script in index.html's <head>, which sets them on :root.in-telegram
       from Telegram.WebApp's own theme params/safe-area, and only inside
       the Telegram Mini App (see the .in-telegram rules below). Every var()
       reference to these already carries its own fallback for the
       non-Telegram case, so this line changes no rendered behavior. */
    --tg-button-color: initial; --tg-button-text-color: initial; --tg-bg-color: initial; --tg-text-color: initial; --tg-safe-area-top: initial;
    /* Same reasoning as the --tg-* block above, for the other custom
       properties in this file that only ever get a real value from an
       inline `style="--foo: ..."` JS sets on a specific element at runtime
       (send-button heat glow, sidebar/split-pane drag widths, chat drawer
       width, per-tab color tint) — every var() reference already carries
       its own fallback, so declaring these unset here doesn't change what
       renders; it just gives static tooling something to resolve. */
    --heat-spread: initial; --heat-color: initial; --heat-glow: initial; --glow-angle: initial; --flicker: initial;
    --sidebar-w: initial; --split-v-a: initial; --split-v-b: initial; --split-h-a: initial; --split-h-b: initial;
    --chat-drawer-width: initial;
    --ember-duration: initial; --ember-drift: initial; --ember-rise: initial;
    --smoke-duration: initial; --smoke-drift: initial;
    /* Unlike the ones above, every `.has-tint` element these three feed is
       always given a real value alongside the class itself (see
       applyTabTint in tabs.js) — but color-mix() has no fallback-argument
       syntax the way var()'s second argument works, so an actually-unset
       --tab-tint would make color-mix() (and the whole background
       declaration) invalid rather than quietly falling back. A real default
       color keeps that failure mode from being silent if that pairing is
       ever broken by a future edit. */
    --tab-tint: #35d07f;
  }
  /* Telegram Mini App theme accent — --tg-* custom properties are set at
     runtime (see the inline script in index.html's <head>) from
     Telegram.WebApp.themeParams; this only takes effect inside Telegram
     (the class is never added otherwise) and only remaps the handful of
     colors this app already routes through custom properties. */
  :root.in-telegram {
    --btn-primary-bg: var(--tg-button-color, #35d07f);
    --btn-primary-color: var(--tg-button-text-color, #eee);
    --btn-primary-hover-bg: var(--tg-button-color, #29b76c);
  }
  html.in-telegram body { background: var(--tg-bg-color, inherit); color: var(--tg-text-color, inherit); }
  /* Reserves the strip Telegram's own native X/⋮ controls sit in (see the
     safeAreaChanged/contentSafeAreaChanged listener in index.html's <head>
     for how --tg-safe-area-top gets its value) — without this #topbar drew
     directly under those controls and both fought over the same taps. The
     grid row grows by exactly the same amount the padding adds (box-sizing:
     border-box is global), so the topbar's own icons don't get squeezed
     into a shorter box — they just sit lower, below Telegram's chrome. */
  html.in-telegram #app { grid-template-rows: calc(48px + var(--tg-safe-area-top, 0px)) 1fr; }
  html.in-telegram #topbar { padding-top: var(--tg-safe-area-top, 0px); align-items: flex-end; padding-bottom: 6px; }
  * { box-sizing: border-box; }
  body {
    margin: 0; font-family: var(--app-font-family); font-size: var(--app-font-size); color: #d7f5df; height: 100vh; height: 100dvh;
    background:
      radial-gradient(ellipse 900px 600px at 88% -5%, rgba(53, 208, 127, 0.10) 0%, transparent 60%),
      radial-gradient(ellipse 1400px 900px at 15% 105%, rgba(18, 52, 32, 0.4) 0%, transparent 55%),
      var(--surface-void);
  }

  .stars-bg {
    position: fixed; inset: 0; z-index: -2; pointer-events: none;
    background-image:
      radial-gradient(1.6px 1.6px at 8% 12%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 18% 6%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 27% 18%, #fff, transparent 60%),
      radial-gradient(1.4px 1.4px at 38% 4%, #fff, transparent 60%),
      radial-gradient(1px 1px at 47% 22%, #cfd6ff, transparent 60%),
      radial-gradient(1.6px 1.6px at 60% 9%, #fff, transparent 60%),
      radial-gradient(1px 1px at 72% 16%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 81% 5%, #cfd6ff, transparent 60%),
      radial-gradient(1.4px 1.4px at 92% 20%, #fff, transparent 60%),
      radial-gradient(1px 1px at 5% 35%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 15% 42%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 33% 48%, #fff, transparent 60%),
      radial-gradient(1.6px 1.6px at 50% 38%, #fff, transparent 60%),
      radial-gradient(1px 1px at 65% 45%, #cfd6ff, transparent 60%),
      radial-gradient(1.2px 1.2px at 85% 40%, #fff, transparent 60%),
      radial-gradient(1px 1px at 10% 62%, #fff, transparent 60%),
      radial-gradient(1.4px 1.4px at 25% 70%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 42% 65%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 58% 72%, #fff, transparent 60%),
      radial-gradient(1px 1px at 75% 60%, #cfd6ff, transparent 60%),
      radial-gradient(1.4px 1.4px at 90% 68%, #fff, transparent 60%),
      radial-gradient(1px 1px at 20% 85%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 45% 90%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 70% 88%, #fff, transparent 60%),
      radial-gradient(1.4px 1.4px at 95% 85%, #fff, transparent 60%);
    background-repeat: no-repeat;
    opacity: 0.5;
    animation: twinkle-a 3.6s ease-in-out infinite;
  }
  .stars-bg::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
      radial-gradient(1.2px 1.2px at 15% 42%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 33% 48%, #fff, transparent 60%),
      radial-gradient(1.6px 1.6px at 50% 38%, #fff, transparent 60%),
      radial-gradient(1px 1px at 65% 45%, #cfd6ff, transparent 60%),
      radial-gradient(1.2px 1.2px at 85% 40%, #fff, transparent 60%),
      radial-gradient(1px 1px at 10% 62%, #fff, transparent 60%),
      radial-gradient(1.4px 1.4px at 25% 70%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 42% 65%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 58% 72%, #fff, transparent 60%),
      radial-gradient(1px 1px at 75% 60%, #cfd6ff, transparent 60%),
      radial-gradient(1.4px 1.4px at 90% 68%, #fff, transparent 60%),
      radial-gradient(1px 1px at 20% 85%, #fff, transparent 60%),
      radial-gradient(1.2px 1.2px at 45% 90%, #cfd6ff, transparent 60%),
      radial-gradient(1px 1px at 70% 88%, #fff, transparent 60%),
      radial-gradient(1.4px 1.4px at 95% 85%, #fff, transparent 60%);
    background-repeat: no-repeat;
    animation: twinkle-b 4.8s ease-in-out infinite;
    animation-delay: 1.2s;
  }
  @keyframes twinkle-a { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
  @keyframes twinkle-b { 0%, 100% { opacity: 0.2; } 50% { opacity: 0.85; } }

  @media (prefers-reduced-motion: reduce) {
    .stars-bg, .stars-bg::after { animation: none; }
  }
  button { cursor: pointer; }
  input, select, textarea, button { font-family: inherit; font-size: inherit; }
  /* :not([type=checkbox]):not([type=radio]) — the `background` shorthand
     resets background-image to `none`, which clobbers DaisyUI's own
     checkbox/radio checked-state checkmark (drawn as a background-image
     using --chkbg/--chkfg, deliberately low-specificity via :where() so
     plain tag selectors like this one win) — checkboxes rendered as
     checked with no visible checkmark before this exclusion. */
  input:not([type=checkbox]):not([type=radio]), select, textarea { background: var(--surface-recessed); border: 1px solid var(--border); color: var(--btn-color); border-radius: 4px; padding: 6px 8px; }
  /* Native <select> rendering (arrow width/position, text clipping) differs
     per OS/browser (Windows Chrome vs macOS Safari vs Linux Firefox etc),
     which made text look "half cut off" on some platforms. Forcing a single
     custom arrow + consistent right padding makes every select render
     identically everywhere. */
  select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='14' height='14' fill='none' stroke='%239aa0ac' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 14px;
    padding-right: 26px;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  /* DaisyUI's .input/.select classes carry higher specificity than the bare
     tag selector above, and their background/text color come from daisyui's
     own theme tokens — which default to daisyui's LIGHT theme values unless
     the OS is in dark mode, regardless of this app's own dark/light
     theme. That produced a near-white-on-white field (env-select,
     collections-search) in this app's default dark theme on a light-OS
     browser. Id selectors outrank .input/.select's class specificity, so
     this reliably wins back this app's own colors on the two fields using
     daisyui classes so far. */
  #env-select, #collections-search, #chat-org-select, #chat-new-dm-select { background: var(--surface-recessed); color: var(--btn-color); border-color: var(--border); }
  /* General version of the fix above — every daisyui .input/.select/.checkbox
     adopted since (auth-screen, Settings, Admin, Organizations) hit the same
     white-on-dark regression, and patching each new field by #id doesn't
     scale. This targets the daisyui class itself so any future .input/.select
     usage is covered without another one-off override. */
  .input, .select, .textarea { background-color: var(--surface-recessed); color: var(--btn-color); border-color: var(--border); }
  /* DaisyUI's .select class ships its own appearance/arrow that beats the
     bare `select` tag rule above on specificity (env-select uses this
     class), leaving that field with the native OS dropdown look/truncation
     the tag rule was meant to fix everywhere. Re-apply the same custom
     arrow here so daisyui selects match too. */
  select.select {
    display: block;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='14' height='14' fill='none' stroke='%239aa0ac' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 14px;
    padding-right: 26px;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  .input:focus, .select:focus, .textarea:focus { outline: none; border-color: var(--color-primary); }
  .checkbox { border-color: var(--border); }
  /* DaisyUI's own :disabled/[disabled] state on .btn uses a low-alpha dark
     token meant to dim against a LIGHT theme surface — on this app's dark
     background that token is nearly the same color as the surface itself,
     so a disabled .btn (e.g. #ws-test-send-btn before Connect) renders with
     invisible label text instead of a merely-dimmed one. */
  .btn:disabled, .btn[disabled] { opacity: 0.5; background-color: #2a2d36; color: #eee; border-color: var(--color-border); }
  input:focus, select:focus, textarea:focus { border-color: var(--color-primary); outline: none; }
  input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible {
    outline: 2px solid var(--color-primary-2);
    outline-offset: 2px;
  }
  input, select, button { box-sizing: border-box; height: 34px; }
  /* DaisyUI's own .input/.select set a line-height/padding-block sized for
     their own (taller) default height — squeezed down to this app's 34px
     via the rule above, the text's line box no longer fits the content box
     and its descenders got clipped at the bottom (visible on #env-select's
     "DEFAULT" and the history filter selects). Setting line-height to
     exactly the 32px content box (34px - 1px border top/bottom) and zeroing
     block padding makes the browser center the text within that box
     instead of the taller one DaisyUI assumed. */
  input, select { line-height: 32px; padding-top: 0; padding-bottom: 0; }
  /* Desktop: transparent wrappers so #req-name-row's children lay out as one
     flat flex row. Declared unconditionally (not inside a media query) so
     it stays ahead of the max-width:560px override in source order —
     override rules only win the cascade if they come later than what
     they're overriding. */
  .req-name-row-fields, .req-name-row-actions { display: contents; }
  button { background: linear-gradient(180deg, var(--btn-hover-bg), var(--btn-bg)); border: 1px solid var(--btn-border); color: var(--btn-color); border-radius: 4px; padding: 6px 12px; }
  button:hover { background: linear-gradient(180deg, color-mix(in srgb, var(--btn-hover-bg) 130%, white), var(--btn-hover-bg)); }
  button.primary { background: linear-gradient(180deg, var(--accent-2), var(--btn-primary-bg)); border-color: var(--btn-primary-bg); color: var(--btn-primary-color); }
  button.primary:hover { background: linear-gradient(180deg, var(--accent-2), var(--btn-primary-hover-bg)); }
  button.danger { background: linear-gradient(180deg, color-mix(in srgb, var(--btn-danger-bg) 70%, black), var(--btn-danger-bg)); border-color: var(--btn-danger-border); color: var(--btn-danger-color); }
  /* Save/confirm row-actions (e.g. the ✓ next to a variable row's ✕ delete) —
     green vs. red gives the same at-a-glance affordance as danger already does. */
  .v-save { background: var(--btn-vsave-bg); border-color: var(--btn-vsave-border); color: var(--btn-vsave-color); }
  .v-save:hover { background: var(--btn-vsave-hover-bg); }
  .v-save, .v-delete, .session-delete-btn { display: inline-flex; align-items: center; justify-content: center; }
  /* De-emphasized action: for secondary/utility buttons that shouldn't compete
     visually with the row's primary action (e.g. Save next to Copy as cURL). */
  button.ghost-btn { background: rgba(255,255,255,0.04); border-color: var(--btn-ghost-border); color: var(--btn-ghost-color); }
  #action-more-btn { display: inline-flex; align-items: center; justify-content: center; }
  button.ghost-btn:hover { background: var(--btn-ghost-hover-bg); color: var(--btn-ghost-hover-color); }
  /* DaisyUI's own `.btn-ghost` (used raw, with no app-specific class, all
     over — the Response actions dropdown, admin panels, modal close-rows,
     ...) isn't theme-aware here: its text color comes from daisyui's own
     tokens which default to a near-invisible light gray once this app's own
     light theme is active (same root cause already documented for
     .topbar-nav-btn above). Rather than adding a one-off fix per screen
     (which is how that ever became a 17-file problem), route the class
     itself through this app's already-themed --btn-ghost-* tokens so every
     current and future raw `.btn-ghost` gets it for free. */
  .btn-ghost { color: var(--btn-ghost-color); border: 1px solid transparent; }
  /* Ghost tugma cheti — doim emas (topbar band ko'rinmasin), faqat
     hover/fokusda ajralib chiqsin. Solid tugmalarda chegara bare `button`
     qoidasidan (--btn-border -> --color-border-strong) keladi. */
  .btn-ghost:hover, .btn-ghost:focus-visible { border-color: var(--color-border-strong); }
  .btn-ghost:hover { background: var(--btn-ghost-hover-bg); color: var(--btn-ghost-hover-color); }
  /* Same gap as .btn-ghost above, for DaisyUI's raw `.btn-primary` — used
     with no app-specific `.primary` class on auth-screen (Login, Notify
     admin, Back to login), so it never picked up this app's accent and
     stayed DaisyUI's default theme color (indigo) in every theme,
     including light, where it read as visibly off-brand next to the rest
     of the warm/orange palette. Routed through the same --btn-primary-*
     tokens (and the same layered-depth gradient/glow) as `button.primary`. */
  .btn-primary { background: linear-gradient(180deg, var(--accent-2), var(--btn-primary-bg)); border-color: var(--btn-primary-bg); color: var(--btn-primary-color); box-shadow: 0 1px 0 rgba(255,255,255,0.35) inset, 0 -6px 10px -6px rgba(0,0,0,0.25) inset, 0 2px 12px -2px rgba(53,208,127,0.45); }
  .btn-primary:hover { background: linear-gradient(180deg, var(--accent-2), var(--btn-primary-hover-bg)); }
  .mobile-only-action { display: none; }

  /* Glass/glow button finish, shared by every button in the app — a static
     (non-animated) take on the shine/plate layering used by the theme
     toggle below. Layered as pseudo-elements so each variant's own
     background-color (primary/danger/icon-btn) still shows through. */
  button {
    position: relative; overflow: hidden; isolation: isolate; border-radius: 6px;
    box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 -6px 10px -6px rgba(0,0,0,0.5) inset, 0 1px 2px rgba(0,0,0,0.35);
    transition: transform 0.12s ease, box-shadow 0.15s ease, background 0.15s ease;
  }
  button::before {
    content: ''; position: absolute; inset: 0; pointer-events: none; mix-blend-mode: plus-lighter;
    background: linear-gradient(155deg, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.03) 35%, transparent 55%),
                conic-gradient(from 45deg, transparent, rgba(255,255,255,0.05));
  }
  button::after {
    content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
    box-shadow: 0 -1px 2px rgba(0,0,0,0.3) inset;
  }
  /* Tactile "layered depth" hover/press: lift 1px on hover, settle flat on
     press — reinforces the plate/glass layering above instead of just
     swapping background color. */
  button:hover { transform: translateY(-1px); box-shadow: 0 1px 0 rgba(255,255,255,0.1) inset, 0 -6px 10px -6px rgba(0,0,0,0.5) inset, 0 4px 10px rgba(0,0,0,0.4); }
  button:active { transform: translateY(0) scale(0.97); }
  /* Primary carries the accent glow the other variants don't — keeps the
     "layered depth" lift legible as the button that matters most. */
  button.primary { box-shadow: 0 1px 0 rgba(255,255,255,0.35) inset, 0 -6px 10px -6px rgba(0,0,0,0.25) inset, 0 2px 12px -2px rgba(53,208,127,0.45); }
  button.primary:hover { box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 -6px 10px -6px rgba(0,0,0,0.25) inset, 0 4px 18px -2px rgba(53,208,127,0.6); }
  :root[data-theme="light"] button::before {
    background: linear-gradient(155deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0.1) 35%, transparent 55%),
                conic-gradient(from 45deg, transparent, rgba(255,255,255,0.15));
  }
  :root[data-theme="light"] button::after { box-shadow: 0 -1px 2px rgba(0,0,0,0.08) inset; }
  /* Loading state: a golden ring rotates around the button's border while an
     async action (Send, Save, ...) is in flight — set/cleared via the
     setButtonLoading(btn, bool) helper, not tied to any one button. */
  @property --glow-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
  @keyframes glow-rotate { to { --glow-angle: 360deg; } }
  button.is-loading { box-shadow: 0 0 15px rgba(212, 175, 55, 0.6), 0 1px 0 rgba(255,255,255,0.08) inset, 0 1px 2px rgba(0,0,0,0.35); }
  /* .btn:disabled (above) mutes disabled buttons to a flat, slightly-blue
     dark gray — appropriate for "unavailable", wrong for "busy". A loading
     button is disabled only to block double-submits, not because it's
     unavailable, so it should still show its normal theme color instead of
     fading toward that gray-blue (which is what read as the button
     "turning blue" the longer a request ran — the color was there the
     whole time, just gradually more noticeable as attention lingered on it). */
  button.is-loading:disabled {
    opacity: 1; cursor: wait;
    background-color: var(--btn-primary-bg); color: var(--btn-primary-color); border-color: var(--btn-primary-bg);
  }
  button.is-loading::after {
    content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 2px; pointer-events: none;
    background: conic-gradient(from var(--glow-angle, 0deg), transparent 0%, #d4af37 25%, #f5d97a 50%, #d4af37 75%, transparent 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude;
    animation: glow-rotate 1.2s linear infinite;
  }
  /* #send-btn "heating up" the longer a request runs: --heat-color/
     --heat-glow/--heat-spread are set live by JS (see startSendDurationTimer
     in response-core.js), ramping green -> amber -> red AND growing the
     glow's blur radius as elapsed time passes, so a slow response reads as
     visibly "hotter" at a glance instead of only being knowable from the
     numeric timer next to it. The button's own fill (background-color/
     border-color) is likewise set inline by that same JS tick — it blends
     from the theme's normal --btn-primary-bg to a deep ember red-orange
     (see bgColorForRatio) as elapsed time grows, instead of staying a flat
     theme color underneath a glow that's trying to look like fire; the
     values here are just the pre-JS/first-paint fallback. Cancel (visible
     the whole time Send is disabled) is the one clearly clickable control
     here — Send drops to 0.6 opacity like any other disabled button, it
     just keeps its pulsing glow instead of an otherwise-inert gray. */
  #send-btn.is-loading {
    opacity: 0.6; cursor: wait;
    background-color: var(--btn-primary-bg); color: var(--btn-primary-color); border-color: var(--btn-primary-bg);
    box-shadow: 0 0 var(--heat-spread, 15px) var(--heat-glow, rgba(212, 175, 55, 0.6)), 0 1px 0 rgba(255,255,255,0.08) inset, 0 1px 2px rgba(0,0,0,0.35);
    animation: heat-pulse 1.4s ease-in-out infinite;
  }
  #send-btn.is-loading::after {
    background: conic-gradient(from var(--glow-angle, 0deg), transparent 0%, var(--heat-color, #d4af37) 25%, var(--heat-color, #f5d97a) 50%, var(--heat-color, #d4af37) 75%, transparent 100%);
  }
  /* "On fire" — past BLAZE_THRESHOLD_RATIO (response-core.js), the button
     stops just glowing and gets the full treatment. Follows real flame
     physics as closely as a button-sized CSS effect reasonably can:
       - Blackbody color gradient: a tight near-white/yellow core (hottest,
         closest to the "flame base"), fading through orange at mid-radius,
         to a soft red-orange haze at the outer edge (coolest, like a flame
         tip) — never one flat color.
       - Turbulence: --flicker (randomized every ~90ms by JS, see
         setBlazing) multiplies every layer's blur/spread on top of the
         keyframe's own smooth oscillation, so the glow's size genuinely
         jitters instead of pulsing on a metronome.
       - Embers (.send-ember) drifting up with randomized per-spark heat/
         size/path, and slower, darker .send-smoke wisps trailing above —
         real fire always trails smoke, and it reads visibly cooler/darker
         than the flame itself.
     overflow: visible so embers/smoke can rise past the button's own edge
     instead of being clipped at it. */
  #send-btn.is-loading.on-fire {
    position: relative;
    overflow: visible;
    animation: blaze-pulse 0.5s ease-in-out infinite;
  }
  @keyframes blaze-pulse {
    0% {
      box-shadow:
        0 0 calc(6px * var(--flicker, 1)) calc(1px * var(--flicker, 1)) rgba(255, 248, 220, 0.9),
        0 0 calc(20px * var(--flicker, 1)) calc(5px * var(--flicker, 1)) rgba(255, 160, 40, 0.8),
        0 0 calc(40px * var(--flicker, 1)) calc(12px * var(--flicker, 1)) rgba(255, 90, 20, 0.5),
        0 0 calc(66px * var(--flicker, 1)) calc(20px * var(--flicker, 1)) rgba(255, 40, 30, 0.3),
        0 1px 0 rgba(255,255,255,0.08) inset;
    }
    35% {
      box-shadow:
        0 0 calc(8px * var(--flicker, 1)) calc(2px * var(--flicker, 1)) rgba(255, 250, 225, 0.95),
        0 0 calc(26px * var(--flicker, 1)) calc(7px * var(--flicker, 1)) rgba(255, 175, 40, 0.85),
        0 0 calc(50px * var(--flicker, 1)) calc(16px * var(--flicker, 1)) rgba(255, 100, 15, 0.55),
        0 0 calc(78px * var(--flicker, 1)) calc(24px * var(--flicker, 1)) rgba(255, 30, 25, 0.35),
        0 1px 0 rgba(255,255,255,0.08) inset;
    }
    60% {
      box-shadow:
        0 0 calc(5px * var(--flicker, 1)) calc(1px * var(--flicker, 1)) rgba(255, 245, 215, 0.85),
        0 0 calc(17px * var(--flicker, 1)) calc(4px * var(--flicker, 1)) rgba(255, 150, 35, 0.75),
        0 0 calc(36px * var(--flicker, 1)) calc(10px * var(--flicker, 1)) rgba(255, 80, 20, 0.45),
        0 0 calc(60px * var(--flicker, 1)) calc(18px * var(--flicker, 1)) rgba(255, 35, 30, 0.28),
        0 1px 0 rgba(255,255,255,0.08) inset;
    }
    100% {
      box-shadow:
        0 0 calc(7px * var(--flicker, 1)) calc(1.5px * var(--flicker, 1)) rgba(255, 248, 220, 0.9),
        0 0 calc(22px * var(--flicker, 1)) calc(6px * var(--flicker, 1)) rgba(255, 165, 40, 0.8),
        0 0 calc(44px * var(--flicker, 1)) calc(13px * var(--flicker, 1)) rgba(255, 90, 20, 0.5),
        0 0 calc(70px * var(--flicker, 1)) calc(21px * var(--flicker, 1)) rgba(255, 35, 28, 0.3),
        0 1px 0 rgba(255,255,255,0.08) inset;
    }
  }
  .send-ember {
    position: absolute; bottom: 4px; width: 3px; height: 3px; border-radius: 50%;
    background: radial-gradient(circle, #fff2c4 0%, #ffb347 40%, #ff5a1f 75%, transparent 100%);
    pointer-events: none;
    animation: ember-rise var(--ember-duration, 1.1s) ease-out forwards;
  }
  @keyframes ember-rise {
    0% { transform: translate(0, 0) scale(1); opacity: 1; }
    70% { opacity: 0.9; }
    100% { transform: translate(var(--ember-drift, 0px), calc(-1 * var(--ember-rise, 34px))) scale(0.3); opacity: 0; }
  }
  /* Cooler, darker, slower than an ember, and it expands as it rises
     instead of shrinking — smoke diffuses, it doesn't burn out. */
  .send-smoke {
    position: absolute; bottom: 6px; width: 5px; height: 5px; border-radius: 50%;
    background: radial-gradient(circle, rgba(90, 85, 80, 0.55) 0%, rgba(60, 55, 52, 0.35) 55%, transparent 80%);
    filter: blur(1px);
    pointer-events: none;
    animation: smoke-rise var(--smoke-duration, 1.6s) ease-out forwards;
  }
  @keyframes smoke-rise {
    0% { transform: translate(0, 0) scale(0.8); opacity: 0.6; }
    40% { opacity: 0.5; }
    100% { transform: translate(var(--smoke-drift, 0px), -46px) scale(2.4); opacity: 0; }
  }
  /* Send is the dramatic one while blazing — Cancel recedes (muted,
     desaturated, no glow) so it doesn't visually compete, without actually
     disabling it; hover/focus still restore it to full color since it's
     still the one real way to stop the request. */
  #cancel-req-btn.heat-dulled { filter: saturate(0.4) brightness(0.85); opacity: 0.65; box-shadow: none; transition: filter 0.4s ease, opacity 0.4s ease; }
  #cancel-req-btn.heat-dulled:hover, #cancel-req-btn.heat-dulled:focus-visible { filter: none; opacity: 1; }
  /* Same pulse, same color — the duration readout and the button it's
     timing read as one connected "heating up" signal instead of two
     independently-animated pieces. */
  @keyframes heat-pulse {
    0%, 100% { filter: brightness(1); }
    50% { filter: brightness(1.25); }
  }
  #send-duration {
    font-family: monospace; font-size: 11px; color: var(--heat-color, var(--color-text-muted)); font-weight: 600; min-width: 3em;
    text-shadow: 0 0 var(--heat-spread, 0px) var(--heat-glow, transparent);
    animation: heat-pulse 1.4s ease-in-out infinite;
  }
  .icon-btn { border-radius: 8px; }
  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0;
  }
  .hidden { display: none !important; }

  /* `display: none` by default — this used to be `flex`, painting the login
     form immediately on every load including a page reload with a still-valid
     session, then hiding it once /api/auth/me resolved and enterApp() ran.
     That produced a visible login-form flash on refresh; the JS in
     index.html's inline init script already sets `display: flex` explicitly
     via showAuthScreen() once the check actually fails, so this default no
     longer needs to. */
  #auth-screen { display: none; align-items: center; justify-content: center; height: 100%; position: relative; overflow: hidden; perspective: 1400px; }
  /* Aurora fon — uchta sekin suzuvchi radial-gradient blob, brand rangi
     atrofida. pointer-events:none — pastdagi karta bilan hech qanday
     interaktsiyaga xalaqit bermaydi. prefers-reduced-motion'da to'xtaydi. */
  #auth-screen::before, #auth-screen::after {
    content: ''; position: absolute; border-radius: 50%; pointer-events: none; filter: blur(60px);
    opacity: 0.5; will-change: transform;
  }
  #auth-screen::before {
    width: 480px; height: 480px; top: -140px; left: -120px;
    background: radial-gradient(circle, rgba(53,208,127,0.45) 0%, rgba(53,208,127,0) 70%);
    animation: authAuroraDrift1 16s ease-in-out infinite;
  }
  #auth-screen::after {
    width: 420px; height: 420px; bottom: -160px; right: -100px;
    background: radial-gradient(circle, rgba(94,235,160,0.35) 0%, rgba(94,235,160,0) 70%);
    animation: authAuroraDrift2 20s ease-in-out infinite;
  }
  @keyframes authAuroraDrift1 {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50% { transform: translate(60px, 40px) scale(1.15); }
  }
  @keyframes authAuroraDrift2 {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50% { transform: translate(-50px, -30px) scale(1.1); }
  }
  @media (prefers-reduced-motion: reduce) {
    #auth-screen::before, #auth-screen::after { animation: none; }
  }
  /* DaisyUI's own component CSS (not just Tailwind utilities) assumes
     border-box sizing, which preflight:false intentionally doesn't set
     globally (see index.html comment). Scope it to just this pilot area so
     .btn/.input padding stays inside the widths/heights app.css already
     fixes, instead of pushing the rendered box wider than intended. */
  #auth-screen, #auth-screen * { box-sizing: border-box; }
  #auth-screen .card {
    background: linear-gradient(180deg, rgba(28,34,42,0.85) 0%, rgba(18,22,28,0.85) 100%);
    backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
    padding: 48px 44px; border-radius: 14px; width: 420px; max-width: 92vw;
    border: 1px solid var(--color-border-strong);
    box-shadow: 0 0 0 1px rgba(255,255,255,0.02), 0 24px 60px rgba(0,0,0,0.45), 0 0 90px rgba(53, 208, 127, 0.08);
    position: relative; transform-style: preserve-3d;
    transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.25s ease;
    /* Sichqoncha holati auth.js'dagi mousemove handler orqali shu ikki
       CSS custom property'ga yoziladi — --tilt-x/--tilt-y default 0deg
       bo'lgani uchun JS umuman ishlamasa ham (masalan touch device) karta
       tekis holatda qoladi, hech qanday fallback shart emas. */
    --tilt-x: 0deg; --tilt-y: 0deg;
    transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
  }
  @media (prefers-reduced-motion: reduce) {
    #auth-screen .card { transition: none; transform: none !important; }
  }
  #auth-screen h1 {
    margin: 0 0 8px; font-size: 40px; font-weight: 700; letter-spacing: 0.5px; text-align: center;
    background: linear-gradient(135deg, #ffffff 0%, #5eeba0 60%, #35d07f 100%);
    -webkit-background-clip: text; background-clip: text; color: transparent;
    text-shadow: 0 0 30px rgba(53, 208, 127, 0.3);
  }
  .auth-tagline { margin: 0 0 22px; text-align: center; font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
  #auth-screen input { width: 100%; margin-bottom: 10px; }
  #auth-screen .error { color: #ff6b6b; font-size: 12px; min-height: 16px; margin: 4px 0; }
  /* Named .auth-link, not .toggle — DaisyUI ships its own .toggle
     component (a checkbox switch with fixed geometry), and that collided
     with this app's pre-existing .toggle link style, rendering a stray
     switch-knob circle on top of the "Forgot password?"/"Register instead"
     text. Renaming avoids the clash instead of fighting DaisyUI's rules. */
  #auth-screen .auth-link { font-size: 12px; color: var(--color-primary-2); cursor: pointer; margin-top: 10px; text-align: center; }
  #auth-screen .field-label { display: block; font-size: 12.5px; color: var(--text-muted); margin-bottom: 6px; }
  #auth-screen .field { margin-bottom: 4px; }
  #auth-screen .field input { margin-bottom: 0; width: 100%; padding-right: 38px; }
  /* Positioning root for the eye-toggle button is this wrapper (just the
     input), not .field — .field also contains the label above it, and
     positioning against that pushed the button up into the label's row
     instead of centering it on the input. */
  #auth-screen .input-wrap { position: relative; }
  /* Plain .icon-btn, not DaisyUI's .btn — an icon-only toggle pinned inside
     an input doesn't benefit from DaisyUI's padding/min-height here, and
     mixing them is what pushed this button outside the input border. */
  #auth-screen .input-wrap .icon-btn { position: absolute; right: 4px; top: 4px; width: 27px; height: 27px; padding: 0; font-size: 15px; opacity: 0.85; display: flex; align-items: center; justify-content: center; background: transparent; border: none; }
  #auth-screen .input-wrap .icon-btn:hover { opacity: 1; }
  #auth-screen .field-error { color: #ff6b6b; font-size: 11px; margin: 4px 0 8px; min-height: 14px; }
  #auth-screen .field-hint { font-size: 11px; margin: 4px 0 8px; }
  #auth-screen .field-hint.weak { color: #ff6b6b; }
  #auth-screen .field-hint.medium { color: #e8b339; }
  #auth-screen .field-hint.strong { color: #4caf50; }
  #auth-screen #auth-forgot-link { margin: 4px 0 16px; }
  /* DaisyUI's stylesheet loads after Tailwind's CDN utility <style>
     (see index.html head order), so at equal specificity .btn's own width
     rule wins the cascade over .w-full — force it explicitly here instead
     of reordering the two CDN tags. */
  #auth-screen .btn.w-full { width: 100%; }
  #auth-screen #auth-submit { margin-top: 4px; }
  #auth-screen .auth-tagline { font-size: 13px; color: var(--text-muted); }
  #auth-screen #auth-pending p { font-size: 13px; line-height: 1.5; }
  #auth-screen #auth-pending button { width: 100%; margin-top: 8px; }
  #auth-screen #auth-forgot .field-hint { color: #4caf50; font-size: 12.5px; }
  #auth-screen #auth-forgot-back-btn { margin-top: 10px !important; }
  #auth-submit[disabled] { opacity: 0.6; cursor: default; }

  /* #app-shell exists so #chat-drawer (a position:fixed bottom-right popup,
     see its own rule) can sit as a DOM sibling of #app without needing to
     live outside the app root — it contributes no width to this flex row
     (fixed elements are out of normal flow), so #app is simply flex:1 of
     the shell's full width at all times. */
  #app-shell { display: flex; height: 100vh; height: 100dvh; overflow: hidden; }
  #app {
    display: none; flex: 1; min-width: 0; grid-template-columns: var(--sidebar-w, 240px) minmax(0, 1fr); grid-template-rows: 48px 1fr; grid-template-areas: "topbar topbar" "sidebar main";
    opacity: 0; transform: scale(0.98); transition: opacity 220ms ease, transform 220ms ease;
  }
  /* .active flips display:none -> grid (can't be transitioned); .app-visible
     (added a frame later by enterApp, after the browser has painted the
     display:grid state) is what actually animates opacity/transform in —
     splitting these two lets the fade play instead of the display switch
     itself just snapping the app into view with no transition at all. */
  #app.active { display: grid; }
  #app.active.app-visible { opacity: 1; transform: scale(1); }
  @media (prefers-reduced-motion: reduce) {
    #app { transition: none; }
  }
  #topbar { grid-area: topbar; display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 0 var(--space-4); border-bottom: 1px solid var(--color-border); }
  #topbar .brand { font-weight: 700; display: flex; align-items: center; gap: var(--space-2); }
  .brand-icon { flex-shrink: 0; }
  .brand-req { color: var(--brand); }
  .brand-way { color: #d7f5df; }
  :root[data-theme="light"] .brand-way { color: var(--color-text); }
  #auth-screen .card h1 { display: flex; align-items: center; justify-content: center; gap: 10px; }
  #auth-screen .card h1 .brand-icon { width: 28px; height: 28px; }
  /* Had zero mobile-specific rules at all — the desktop card (fixed 420px,
     44px side padding) just got squeezed down to 92vw on a phone, which on
     anything under ~380px wide left barely 250px of actual usable width
     for the inputs, reading as "the desktop layout, shrunk" rather than an
     actual mobile layout, and forcing a pinch-zoom to tap accurately.
     min-height:100dvh + centered content instead of a floating card, edge
     padding instead of desktop-scale padding, and explicit 48px inputs/
     buttons (DaisyUI's own default .input/.btn height already happens to be
     3rem/48px when no -sm/-lg size class is added, which this screen
     doesn't use — stated explicitly here so it doesn't silently regress if
     a size class ever gets added). */
  @media (max-width: 480px) {
    #auth-screen { height: 100dvh; padding: 0; }
    #auth-screen .card {
      width: 100%; max-width: 100%; min-height: 100dvh; border-radius: 0;
      border: none; padding: 28px 18px; display: flex; flex-direction: column; justify-content: center;
      box-shadow: none;
    }
    #auth-screen input, #auth-screen .btn { height: 48px; font-size: 16px; }
    #auth-screen h1 { font-size: 32px; }
    /* Tilt sichqoncha-o'rnatilgan effekt — mobil to'liq ekranli kartochkada
       ma'nosiz va rotateX/Y perspective bilan qatlashib buzilishga sabab
       bo'lardi, shuning uchun mobilda butunlay o'chirilgan. */
    #auth-screen .card { transform: none !important; }
  }
  #topbar .right { display: flex; gap: var(--space-3); align-items: center; font-size: 12px; color: var(--text-muted); flex-shrink: 0; }
  /* Visually splits the icon cluster into groups (account/tools/appearance)
     instead of one undifferentiated row — a thin vertical rule, not a full
     button, so it doesn't compete with the icons themselves for attention. */
  .topbar-sep { width: 1px; align-self: stretch; margin: var(--space-2) 0; background: var(--color-border); flex-shrink: 0; }
  /* Custom tooltip for icon-only topbar buttons — replaces the native
     `title` tooltip (stripped into data-tip by the topbar-tip init in
     applyI18n()) with a fast, consistently styled one. A real element
     appended to <body> (see initTopbarTips below), not a ::before/::after
     pseudo-element — .icon-btn's own ::before/::after are already used for
     the button sheen effect (see the global `button` rules above), and
     `button`'s `overflow: hidden` would clip a pseudo-element tooltip
     poking out below the button anyway. */
  #topbar-tip-bubble {
    position: fixed; background: var(--surface); color: #e6e8ee; font-size: 11px; font-weight: 500;
    padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); white-space: nowrap; pointer-events: none;
    z-index: 300; border: 1px solid var(--border); box-shadow: 0 4px 10px rgba(0,0,0,0.35);
    opacity: 0; transition: opacity 0.1s ease; transform: translateX(-50%);
  }
  #topbar-tip-bubble.visible { opacity: 1; }
  :root[data-theme="light"] .topbar-sep { background: var(--color-border); }
  :root[data-theme="light"] #topbar-tip-bubble { background: var(--color-text); color: var(--color-bg); }
  .topbar-info { font-family: monospace; font-size: 12px; color: var(--text-muted); }
  .topbar-copyable { cursor: pointer; padding: 2px var(--space-2); border-radius: var(--radius-sm); }
  .topbar-copyable:hover { background: var(--surface-2); color: #eee; }
  #topbar-ip { color: #ccc; font-size: 13px; font-weight: 600; }
  #topbar-sysinfo { display: inline-flex; align-items: center; gap: var(--space-3); }
  #topbar-sysinfo.hidden { display: none; }
  #sysinfo-toggle-btn[aria-pressed="true"] { border-color: var(--btn-primary-bg); color: var(--color-primary-2); }
  @media (max-width: 640px) { .topbar-info { display: none; } }
  .network-status { display: flex; align-items: center; gap: var(--space-2); font-size: 11px; color: var(--text-muted); cursor: default; }
  .network-status-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
  .network-status.online .network-status-dot { background: var(--color-success); box-shadow: 0 0 4px var(--color-success); }
  .network-status.offline .network-status-dot { background: var(--color-danger); box-shadow: 0 0 4px var(--color-danger); animation: network-status-pulse 1.4s ease-in-out infinite; }
  .network-status.offline .network-status-label { color: #ff9c9c; }
  @keyframes network-status-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
  @media (max-width: 640px) { .network-status-label { display: none; } }
  :root[data-theme="light"] .topbar-copyable:hover { background: var(--color-surface-3); color: var(--color-text); }
  :root[data-theme="light"] #topbar-ip { color: #616b78; }
  #sidebar-toggle-btn { display: none; }
  #sidebar-backdrop { display: none; }

  #sidebar { grid-area: sidebar; position: relative; border-right: 1px solid var(--color-border); overflow: hidden; padding: var(--space-2); display: flex; flex-direction: column; min-height: 0; }
  #sidebar-resizer { position: absolute; top: 0; right: -3px; width: 6px; height: 100%; cursor: col-resize; z-index: 10; background: transparent; }
  #sidebar-resizer:hover, #sidebar-resizer.dragging { background: var(--btn-primary-bg); }
  #env-picker-dropdown { flex: 0 0 auto; position: relative; }
  #env-picker-btn { max-width: 140px; gap: 5px; overflow: hidden; }
  #env-picker-btn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Env tanlagich menyusi — daisyui .menu/.bg-base-* klasslari bu ilovaning
     dark mavzusida oq-fonда ko'rinmay qolardi (qarang yuqoridagi izoh),
     shuning uchun to'liq loyiha tokenlari bilan bo'yaladi. */
  #env-dropdown-menu {
    position: absolute; top: calc(100% + 4px); left: 0; z-index: 60;
    min-width: 200px; max-height: 60vh; overflow-y: auto;
    margin: 0; padding: 6px; list-style: none;
    background: var(--surface); color: var(--btn-color);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  }
  #env-dropdown-menu li { margin: 0; }
  #env-dropdown-menu li:empty { border-top: 1px solid var(--border); margin: 5px 0; }
  #env-dropdown-menu a {
    display: flex; align-items: center; gap: 8px;
    padding: 7px 10px; border-radius: var(--radius-sm);
    font-size: 13px; color: inherit; cursor: pointer;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #env-dropdown-menu a > span:last-child { overflow: hidden; text-overflow: ellipsis; }
  #env-dropdown-menu a:hover, #env-dropdown-menu a:focus-visible { background: var(--surface-recessed); outline: none; }
  #env-dropdown-menu a.menu-active { color: var(--btn-primary-bg); font-weight: 600; }
  /* Collections/History each read as their own card: a neutral outline +
     rounded corners instead of the old plain-list-with-a-divider look, so
     the sidebar groups are unambiguous at a glance without competing for
     attention with the actually-selected row (blue accent, see
     .collection-item.active/.request-item.active below). Padding moves
     onto the card itself (was on #sidebar) so the border wraps each
     section's full content, not just its header. */
  .sidebar-section { border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-2); margin-bottom: var(--space-2); min-height: 0; }
  .sidebar-section:last-child { margin-bottom: 0; }
  /* Section header doubles as the accordion toggle — the "+"/Import/Clear
     buttons inside it stop propagation (inline onclick, since they're built
     from static HTML here rather than JS-attached like the row buttons
     elsewhere) so clicking them doesn't also collapse the section. */
  .sidebar-section-header { display: flex; align-items: center; gap: var(--space-1); cursor: pointer; padding: var(--space-1) 2px; user-select: none; }
  /* h3 bo'sh joyni bo'shatib bersin — aks holda uzun sarlavha o'ng tomondagi
     action tugmalarni sidebar chetidan tashqariga surib, ular kesilib qoladi. */
  .sidebar-section-header h3 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .sidebar-section-header:focus-visible { outline: 2px solid var(--color-primary-2); outline-offset: 2px; }
  .sidebar-section-header h3 { margin: 0; flex: 1; }
  .sidebar-section-body { overflow: hidden; }
  .sidebar-section-body.collapsed { display: none !important; }
  #section-collections .sidebar-section-body { overflow-y: hidden; }
  #collections-list { flex: 1; overflow-y: auto; min-height: 60px; scrollbar-width: thin; }
  #collections-search { flex-shrink: 0; font-size: 12px; padding: var(--space-2); }
  .search-hidden { display: none !important; }
  #collections-list::-webkit-scrollbar, #history-list::-webkit-scrollbar { width: 8px; }
  #collections-list::-webkit-scrollbar-track, #history-list::-webkit-scrollbar-track { background: transparent; }
  #collections-list::-webkit-scrollbar-thumb, #history-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
  #collections-list::-webkit-scrollbar-thumb:hover, #history-list::-webkit-scrollbar-thumb:hover { background: #4a5062; }
  :root[data-theme="light"] #collections-list::-webkit-scrollbar-thumb, :root[data-theme="light"] #history-list::-webkit-scrollbar-thumb { background: var(--color-border-strong); }
  :root[data-theme="light"] #collections-list::-webkit-scrollbar-thumb:hover, :root[data-theme="light"] #history-list::-webkit-scrollbar-thumb:hover { background: #c2c8d0; }

  @media (max-width: 768px) {
    #app { grid-template-columns: minmax(0, 1fr); grid-template-areas: "topbar" "main"; }
    /* The topbar's icon cluster doesn't fit narrow screens — without this it
       forces #topbar wider than the viewport, which (since grid tracks size
       to their widest row) stretches the whole page and adds page-level
       horizontal scroll. Scrolling it internally keeps the page itself fixed
       to the viewport width; every button stays reachable via a swipe. */
    #topbar { min-width: 0; }
    #topbar > div:first-child, #topbar .right { min-width: 0; flex-shrink: 1; overflow-x: auto; scrollbar-width: none; }
    #topbar > div:first-child::-webkit-scrollbar, #topbar .right::-webkit-scrollbar { display: none; }
    /* Konteyner overflow-x:auto bilan scroll bo'ladi — lekin flex bolalar
       o'zi siqilib ketmasin (aks holda ikonka tugmalari 2px kenglikka
       tushib, telefonda bosib bo'lmaydi). flex-shrink:0 — to'liq o'lchamda
       qolib, qator scroll qilinadi. */
    #topbar .right > * { flex-shrink: 0; }
    #me-label { max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    #sidebar-toggle-btn { display: inline-flex; align-items: center; justify-content: center; }
    /* Narrow phones can't fit the full icon cluster even with the scroll
       fallback above — it reads as cut-off chrome rather than a scrollable
       row. Fold the rarely-used icons into the existing "more" menu instead
       of relying on discoverability of horizontal scroll. */
    /* #chat-btn deliberately excluded — it's a fixed-position corner FAB
       now (see its own rule further down), not part of this row's layout
       flow, so it has nothing to do with this row running out of space. */
    #netspeed-btn, #sysinfo-toggle-btn, #manage-vars-btn, #manage-globals-btn,
    #ws-client-btn, #traceroute-btn, #tools-menu-wrap, .topbar-sep { display: none; }
    /* Hide the bell icon itself, not the whole #notifications-wrap — the
       wrap is #notifications-panel's own positioning parent
       (position:relative/absolute), so hiding the wrap took the panel down
       with it. That left "More → Notifications" (which just proxies a
       click onto this bell — see topbar.js's wire()) toggling a panel
       that could never actually become visible, no matter how many times
       the hidden `.hidden` class got removed from it. */
    #notifications-btn { display: none; }
    /* #topbar .right scrolls horizontally on narrow screens (see rule above)
       — an overflow:auto ancestor clips absolutely-positioned children on
       both axes, so the dropdown below was getting cut off instead of
       floating over the page. Fixed positioning escapes that clipping
       (only transform/filter/perspective ancestors constrain a fixed
       element, not overflow), so the menu is reachable regardless of the
       button's scroll offset within .right. */
    /* !important: the unqualified .topbar-more-menu base rule (position:absolute)
       is defined later in this file, same specificity — without !important
       source order would let it win back over this media-query override. */
    .topbar-more-menu { position: fixed !important; top: 48px !important; right: 8px !important; left: auto !important; max-width: 80vw; }
    .mobile-only-action { display: block; }
    /* Mobile navigation: Collections list is its own full-screen screen, not
       an overlay drawer over #main — a translateX drawer meant every panel
       (env/collections/history) fought for space inside a cramped 82vw
       strip. #app[data-mobile-screen] toggles which of #sidebar/#main fills
       the screen; "detail" is the request builder, "list" is Collections.
       See sidebar.js setMobileScreen() for the toggle logic. */
    #sidebar-resizer { display: none; }
    #sidebar-backdrop { display: none !important; }
    #sidebar {
      /* grid-area:main (not the now-undefined "sidebar" area) — #app's
         template-areas drops to a single "topbar"/"main" column at this
         width, so an unplaced item would auto-flow into whatever cell grid
         auto-placement finds first, risking an overlap with #topbar. Sharing
         #main's cell is intentional: #sidebar and #main are mutually
         exclusive via the display:none rules below, so stacking them in the
         same cell is just "whichever one is showing occupies this space". */
      grid-area: main;
      position: static; width: 100%; max-width: 100%; height: 100%;
      transform: none; box-shadow: none; z-index: auto;
    }
    #app[data-mobile-screen="detail"] #sidebar { display: none; }
    #app[data-mobile-screen="list"] #main { display: none; }
    #main { padding: var(--space-3); }
    /* Nine tabs (Params/Headers/Body/Auth/Assertions/Settings/Scripts/Load
       Test/Response) can't all read as icon+label on a phone row without
       shrinking below a reliable tap target. Keep the label (icon-only with
       no text is a guessing game for anything past Params/Headers/Body) and
       let .tabs' existing overflow-x:auto (see base rule above) scroll the
       strip instead of squeezing every tab down to fit — each tab keeps a
       44px-tall tap target and scroll-snaps so the active one lands fully
       in view rather than half-clipped at the viewport edge. */
    .tabs { gap: 2px; scroll-snap-type: x proximity; }
    .tab { padding: var(--space-3) var(--space-4); min-height: 44px; scroll-snap-align: start; white-space: nowrap; }
    /* Method select / Send / Cancel / More stay at the desktop's compact
       2rem (32px) height below the base breakpoint used by mouse pointers —
       too short for a reliable thumb tap. Bump the primary request-bar
       controls to a 44px touch target on mobile/tablet widths. */
    #req-method, #send-btn, #cancel-req-btn { min-height: 44px; height: 44px; }
    #action-more-btn { min-width: 44px; min-height: 44px; }
    /* Bottom sheet instead of a centered dialog: a full-width panel sliding
       up from the bottom uses the phone's natural thumb-reach zone and
       leaves more usable height than a small centered box with margins on
       every side. cmdk (command palette) keeps its own top-aligned style —
       it's a search-first UI, not a form dialog, so the sheet metaphor
       doesn't fit it. Monitoring is excluded too — it's already a
       full-screen shell at every width (see .monitoring-shell), so the
       88vh-capped sheet treatment would just add an unwanted gap under it. */
    .modal-overlay:not(#cmdk-modal):not(#monitoring-view):not(#grpc-modal):not(#uptime-modal):not(#uptime-status-pages-modal):not(#admin-modal) { align-items: flex-end; }
    .modal-overlay:not(#cmdk-modal):not(#monitoring-view):not(#grpc-modal):not(#uptime-modal):not(#uptime-status-pages-modal):not(#admin-modal) .reqmodal {
      width: 100%; max-width: 100%; max-height: 88vh;
      border-radius: 16px 16px 0 0; margin: 0; padding-bottom: max(20px, env(safe-area-inset-bottom));
      animation: sheet-slide-up 0.2s ease-out;
    }
    @keyframes sheet-slide-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
    @media (prefers-reduced-motion: reduce) {
      .modal-overlay:not(#cmdk-modal):not(#monitoring-view):not(#grpc-modal):not(#uptime-modal):not(#uptime-status-pages-modal):not(#admin-modal) .reqmodal { animation: none; }
    }
    /* The sheet used to also draw a drag-handle bar implying swipe-to-close
       right next to the functional X button — two affordances for the same
       action, and the drag handle didn't even do anything (no swipe gesture
       wired up). One clear close action (the X) instead. */
    .reqmodal .close-row { flex-wrap: wrap; }
    .reqmodal .close-row button { flex: 1 1 0; min-width: 0; }
    /* Flow view: header controls wrap+scroll like #topbar rather than
       overflowing the viewport (new-flow-name/env-select/run-btn no longer
       fit one row under 768px). Palette becomes an off-canvas drawer (same
       pattern as #sidebar) instead of permanently eating ~220px of the
       narrow canvas. Minimap is dropped — at this width it just overlaps
       the zoom controls / palette toggle with no room to breathe. */
    #flows-view-header { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
    #flows-view-header::-webkit-scrollbar { display: none; }
    /* flex-wrap:nowrap shrinks items to fit instead of wrapping to a new
       row — without an explicit no-shrink + no-internal-wrap floor,
       buttons squeeze down to where their own label text wraps onto two
       lines (e.g. "Run flow" -> "Run"/"flow"). Give every header control
       a content-based floor so the row scrolls instead of squashing them. */
    #flows-view-header > * { flex-shrink: 0; white-space: nowrap; }
    #new-flow-name { max-width: 120px; }
    #flows-select, #flow-run-env-select { max-width: 130px; }
    #flow-palette {
      position: fixed; top: 0; bottom: 0; left: 0; width: 82vw; max-width: 300px;
      background: var(--surface-void); z-index: 60; transform: translateX(-100%); transition: transform 0.2s ease;
      box-shadow: 2px 0 12px rgba(0,0,0,0.4);
    }
    #flow-palette.open { transform: translateX(0); }
    #flow-palette-backdrop.open {
      display: block; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 55;
    }
    #flow-minimap { display: none; }
    #flow-console-drawer { height: min(280px, 60vh); }
    /* Body/Response side-by-side layout (layout-split-h) squeezes two
       panels into half a phone's width each — unreadable. Force both
       split layouts to stack as a single column on narrow screens.
       !important: the unqualified #tab-content-scroll.layout-split-h/-v
       base rule (same #id.class specificity) is declared later in this
       file — without !important, source order lets it win back over this
       media-query override even while the media condition holds, since
       equal specificity ties resolve by "last one in the file" rather than
       by which @media block is more specific to the viewport. */
    #tab-content-scroll.layout-split-h, #tab-content-scroll.layout-split-v {
      grid-template-columns: 1fr !important; grid-template-rows: none !important; grid-auto-rows: auto;
      overflow-y: auto; height: auto;
    }
    /* !important: #tab-content-scroll.layout-split-h > #tab-response.active
       (id selector, higher specificity than this .tab-panel.active class
       rule) sets grid-column:3 further down — on this now-single-column
       mobile grid that stranded the Response panel in a phantom empty
       implicit column 3, with Params/Headers/etc's own column-1 content to
       its left reading as a big blank gap. */
    #tab-content-scroll.layout-split-h > .tab-panel.active,
    #tab-content-scroll.layout-split-v > .tab-panel.active { grid-column: 1 !important; grid-row: auto !important; }
    /* #request-tabs-list itself now wraps into rows instead of scrolling
       horizontally (see its base rule) — that already keeps it within the
       viewport width on its own, so this bar no longer needs its own
       horizontal-scroll fallback. New-tab/tab-groups buttons stay put at
       #request-tabs-bar's own (non-wrapping) flex-row level. */
    #request-tabs-bar { max-width: 100%; }
    /* Any plain <table> (admin/org/run-results pages) is wider than a
       phone and would otherwise force the whole page to scroll
       horizontally. Scope the scroll to the table itself. */
    table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
    table thead, table tbody, table tr { display: revert; }
    /* .data-table's base width:100% (app.css, non-mobile rule) pins the
       table's used width to exactly the container's width — table-layout's
       auto-sizing can still make a column wider than its 100%-constrained
       share when content can't shrink further (that's what keeps the
       button columns on the Admin/Organizations tables scrollable instead
       of squeezed), but plain text has no such floor: its min-content width
       is a single character, so a cell with a long unbroken value (a UUID
       in an audit-log path, say) just wraps character-by-character into
       whatever's left of the 100% instead of forcing the table wider.
       white-space:nowrap raises every cell's min-content to its full
       unwrapped text width, so a long value forces its column (and the
       table) past 100% instead of wrapping — which is exactly what
       overflow-x:auto above is there to scroll. */
    .data-table th, .data-table td { white-space: nowrap; }
    /* Horizontal table scroll has no visual cue there's more off to the
       side — nothing here hints "scroll" the way a cut-off column edge or a
       scrollbar would on desktop. A mask-image edge fade reads as "content
       continues" regardless of the underlying background color (table rows,
       modal surface, sidebar — all differ across light/dark theme), so it's
       used instead of a color-matched gradient overlay, which would need a
       different value per surface. It's a static affordance (doesn't hide
       itself once you've scrolled to a true edge) — simpler than the
       scroll-position-aware two-layer gradient trick, and "there's more
       this way" stays true/useful at every scroll position anyway for a
       many-column table with no single natural stopping point.
       .tabs and #request-tabs-bar are deliberately NOT included here — both
       already have a proper scroll-position-aware fade (.tabs-fade /
       .request-tabs-fade, driven by updateTabsFade()/updateRequestTabsFade()
       in topbar.js), which correctly hides at the true start/end instead of
       always showing like this static mask would. */
    table {
      mask-image: linear-gradient(to right, transparent, black 14px, black calc(100% - 14px), transparent);
      -webkit-mask-image: linear-gradient(to right, transparent, black 14px, black calc(100% - 14px), transparent);
    }
    /* Response body: the gutter's line-number-per-logical-line alignment
       (see the comment on .code-block above) only holds if the code never
       wraps — on a phone that means every line, however short, needs a
       horizontal scroll to read past a few characters. Drop the gutter and
       let the body wrap instead; that trade (no line numbers) reads far
       better on a narrow screen than "scroll right to read each line". */
    .code-gutter { display: none; }
    #resp-body { white-space: pre-wrap; word-break: break-word; overflow-x: hidden; }
    /* 13px (desktop's --resp-font-size default) reads fine on a monitor at
       arm's length but is genuinely small held closer on a phone — bump
       response body text up a step for mobile specifically, without
       touching the desktop default (still user-adjustable via Settings'
       font-size select either way, this is just the floor). */
    #resp-body { font-size: 15px; line-height: 1.65; }
    #resp-status, .resp-status-pill { font-size: 13px; }
    #resp-size { font-size: 12px; }
    /* Same reasoning for the densest small-text UI: table/list meta text
       (data-table cells, history entries) sits at 10-11px, chosen for a
       desktop pointer/eye distance — raise the floor a couple px for
       thumb/close-range mobile reading without changing row heights enough
       to break the layouts above. */
    .data-table, .data-table th, .data-table td { font-size: 12px; }
    .history-meta, .history-time, .role-badge, .status-badge { font-size: 12px; }
  }
  /* Below ~560px, squeezing method+url+send (or name+collection+save) onto
     one flex line no longer overflows the page (the 768px rules above fix
     that), but each control shrinks to an unreadable sliver — a URL box
     showing 4 visible characters isn't usable. Stack them instead so every
     control gets the full row width. */
  @media (max-width: 560px) {
    /* Method+URL stay on one row (30/70) instead of stacking — a dropdown
       taking a full row of its own wastes more vertical space than a
       cramped-but-usable inline layout does. Send drops to its own row
       since a 3-way split leaves it too narrow to tap reliably. */
    #req-url-row { flex-wrap: wrap; align-items: stretch; gap: 6px; }
    /* #req-bar-group also wraps to 2 rows here (method+url / send) — still
       one bordered block, just with an internal horizontal divider before
       Send's full-width row instead of a side-by-side 3-way split. */
    #req-bar-group { flex-wrap: wrap; }
    #req-method { flex: 0 0 30% !important; width: auto; border-bottom: 1px solid var(--border); }
    #req-bar-group .url-input-wrap { flex: 1 1 65% !important; width: auto; border-bottom: 1px solid var(--border); }
    #send-btn { flex: 1 1 100% !important; width: 100%; }
    /* Save row: name+collection share row 1, Save+More share row 2 — an
       explicit 2x2 grid instead of relying on flex-wrap. flex-wrap here
       let the 2nd wrapped line's height grow past what #main's
       overflow:hidden container expected for this row, clipping the
       More button out of view entirely. Grid rows size to content
       exactly, so nothing gets pushed past the visible area. */
    /* .req-name-row-fields/.req-name-row-actions are display:contents on
       desktop (see base rule) so they don't affect the normal flex .row
       layout at all. Below 768px each becomes its own flex row: fields
       (name+collection) split 1fr/1fr, actions (save+more) give the
       icon-only More button a fixed 40px track instead of an equal fr
       share — an equal split let its column shrink to whatever the
       row's other content dictated, squeezing the icon to a sliver. */
    #req-name-row { flex-direction: column; align-items: stretch; gap: 4px; margin-bottom: 6px; }
    /* Name + collection used to split 50/50 in one row — on a ~360-390px
       phone that leaves each input too narrow to show its own content
       ("request name (for s…" cut off mid-placeholder). Stacked full-width
       rows cost a bit more vertical space but neither one truncates. */
    .req-name-row-fields { display: flex; flex-direction: column; gap: 4px; }
    .req-name-row-actions { display: flex; gap: 4px; }
    .req-name-row-fields #req-name, .req-name-row-fields #req-collection-target { flex: 1 1 auto; min-width: 0; width: 100%; }
    .req-name-row-actions #save-req-btn { flex: 1 1 auto; min-width: 0; width: auto; }
    .req-name-row-actions #action-more-btn { flex: 0 0 40px; width: 40px; min-width: 40px; padding-left: 0; padding-right: 0; }
    /* Params/Headers/multipart-body rows use a fixed-min-width grid (see
       .kv-row/.multipart-row below) sized for desktop — their combined
       column minimums (354px / 394px) exceed a phone's content width and
       force the row to overflow instead of wrapping, since grid columns
       don't wrap. Shrink the minimums so the row fits down to ~300px. */
    .kv-row { grid-template-columns: 20px minmax(70px, 1fr) minmax(90px, 1fr) 28px; }
    .multipart-row { grid-template-columns: 20px minmax(60px, 1fr) 60px minmax(90px, 1fr) 28px; }
    /* Zoom text-size controls dropped in favor of the platform's own
       pinch-to-zoom — three more small tap targets crammed into an already
       packed toolbar row (Pretty/Search/zoom/Fullscreen/More) isn't worth
       it now that the response body wraps instead of needing horizontal
       scroll (see the ≤768px rule above), which was the main reason
       zooming in used to help readability here. */
    .resp-zoom-group { display: none; }
    /* Save as Variable: label + 2 inputs + button on one flex row squeeze
       each input to an unusable sliver on a phone. Stack them — label on
       its own line, then each input/button gets the full row width. */
    #capture-row { flex-direction: column; align-items: stretch; }
    #capture-row input { width: 100%; min-width: 0; flex: 1 1 auto; }
    #capture-btn { width: 100%; }
    /* History rows: method badge + status/time + url all on one line leaves
       the url column too narrow to be useful once the sidebar itself is
       narrow (mobile drawer, or the 190px tablet sidebar). Wrap the url
       onto its own second line instead of truncating it hard. */
    .history-item { flex-wrap: wrap; row-gap: 2px; }
    .history-url { flex: 1 1 100%; order: 3; white-space: normal; word-break: break-all; -webkit-line-clamp: 2; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
  }
  #sidebar h3 { font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; color: var(--text-muted); margin: var(--space-4) 0 var(--space-2); }
  /* Method/URL/Send row + Name/Save row read as one card, matching the
     sidebar's Collections/History treatment (see .sidebar-section above) —
     same neutral outline + rounded corners, just wrapping the two stacked
     .row's instead of one section. */
  #request-bar-card { border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-2) var(--space-3) 2px; margin-bottom: var(--space-3); }
  /* Method + URL + Send yagona interaction block — bitta umumiy border/
     radius, ichki chegaralar segmentlarni ajratadi (Postman/Linear uslubidagi
     bitta "so'rov paneli" ko'rinishi uchun). overflow:hidden shu bitta
     radius'ni ichidagi select/input/button'larning o'z burchaklarisiz ham
     to'g'ri kesib beradi — ular quyida border/radius'dan mahrum qilingan. */
  #req-bar-group {
    display: flex; align-items: stretch; flex: 1; min-width: 0;
    border: 1px solid var(--border); border-radius: var(--radius-md);
    background: var(--surface); overflow: hidden;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
  }
  #req-bar-group:focus-within { border-color: var(--btn-primary-bg); box-shadow: 0 0 0 3px color-mix(in srgb, var(--btn-primary-bg) 22%, transparent); }
  /* Guruhning o'zi :focus-within bilan yashil border ko'rsatadi — ichidagi
     select/input/tugmaga global :focus-visible offset outline (218-qator)
     ustiga qo'yilsa ikki halqa bo'lib, overflow:hidden bilan qirqiladi. */
  #req-bar-group :focus-visible { outline: none; }
  #req-bar-group #req-method { border: none; border-right: 1px solid var(--border); border-radius: 0; background: transparent; }
  #req-bar-group .url-input-wrap { border-right: 1px solid var(--border); }
  #req-bar-group .url-input-backdrop, #req-bar-group #req-url { border: none; border-radius: 0; background: transparent; }
  #req-bar-group #send-btn, #req-bar-group #cancel-req-btn { border: none; border-radius: 0; }
  .row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; flex-shrink: 0; align-items: center; min-width: 0; max-width: 100%; }
  /* Send / duration timer / Cancel / status sit right next to each other
     here, tight enough to read as one action group instead of spreading
     across the row. */
  #req-url-row { gap: 8px; }
  /* min-width:0 overrides the flex item default of min-width:auto, which
     otherwise floors an input/select at its intrinsic content width (the
     UA's ~170-180px default text-input size) regardless of how much the
     row's own layout/media queries try to shrink it — the classic flexbox
     "won't shrink past content" trap. */
  .row input, .row select { flex: 1; min-width: 0; }
  /* Checkbox .row ichida ham cho'zilib ketmasin - yuqoridagi qoida har bir
     input'ni flex:1 qiladi, lekin checkbox o'zining kvadrat shaklini
     saqlashi kerak (aks holda daisyui checkmark chizig'i buzilib ketadi). */
  .row input[type="checkbox"] { flex: 0 0 auto; }
  #req-name-row #req-name { flex: 1; }
  #req-name-row #req-collection-target { flex: 1.4; height: 2rem; min-height: 2rem; line-height: 2rem; padding-top: 0; padding-bottom: 0; }
  #req-method { height: 2rem; min-height: 2rem; line-height: 2rem; padding-top: 0; padding-bottom: 0; }
  .sidebar-section-header { border-radius: var(--radius-md); transition: background-color 0.15s ease; }
  .sidebar-section-header:hover { background: var(--surface); }
  .collection-item, .request-item, .folder-item, .history-item { transition: background-color 0.12s ease; }
  .collection-item:focus-visible, .request-item:focus-visible, .folder-item:focus-visible { outline: 2px solid var(--color-primary-2); outline-offset: -2px; }
  .collection-item { padding: var(--space-2); border-radius: var(--radius-sm); cursor: pointer; font-size: 12px; }
  /* Replaces a single line of muted text with a small centered card — the
     old one-liner read as "something's broken/still loading" in the large
     empty sidebar space rather than "click the + above to get started". */
  .sidebar-empty-state { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-3); padding: var(--space-7) var(--space-3); }
  .sidebar-empty-icon { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--color-border); color: var(--color-text-muted); }
  .sidebar-empty-text { font-size: 12px; color: var(--text-muted); line-height: 1.5; }
  .sidebar-empty-btn { font-size: 12px; padding: var(--space-2) 14px; }
  :root[data-theme="light"] .sidebar-empty-icon { background: var(--color-surface-3); border-color: var(--color-border); color: #8a929e; }
  /* Umumiy bo'sh holat bloki (renderEmptyState): modal ro'yxatlari (history) va
     sidebar (collections, --compact bilan) shuni ishlatadi. */
  .empty-state { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-3); padding: var(--space-7) var(--space-4); }
  .empty-state--compact { padding: var(--space-6) var(--space-3); gap: var(--space-2); }
  .empty-state-icon { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--color-border); color: var(--color-text-muted); flex-shrink: 0; }
  .empty-state-icon svg { width: 20px; height: 20px; }
  .empty-state-title { font-size: 13px; font-weight: 600; color: var(--color-text); }
  .empty-state-text { font-size: 12px; color: var(--text-muted); line-height: 1.5; max-width: 320px; }
  .empty-state-cta { font-size: 12px; }
  :root[data-theme="light"] .empty-state-icon { background: var(--color-surface-3); border-color: var(--color-border); color: #8a929e; }
  .collection-item:hover { background: var(--surface-2); }
  /* Active state used to be the same var(--color-selected-bg) indigo-purple fill every
     collection/request row already carries a purple-leaning color dot/border
     in (the per-collection auto-color hash lands on purple often) — the two
     purples next to each other made it hard to tell "this row is selected"
     from "this row belongs to a purple collection". A neutral gray fill plus
     a blue accent stripe (same blue as every other active/selected state in
     the app: .request-tab.active, .method-chip.active) reads unambiguously
     as selection regardless of the collection's own color. */
  .collection-item.active, .request-item.active { background: var(--surface-3); color: var(--color-text); font-weight: 600; box-shadow: inset 3px 0 0 var(--btn-primary-bg); }
  .round-add-btn:hover { background: var(--color-selected-bg); border-color: var(--btn-primary-bg); }
  .section-header-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
  .section-header-actions button { min-width: 24px; min-height: 24px; }
  /* Yagona "qo'shish yoki import" tugmasi — qo'shni ikon tugmalar bilan bir
     xil o'lcham/uslub, faqat accent rang bilan ajralib turadi. */
  #collection-add-import-btn { color: var(--color-primary-2); }
  #collection-add-import-btn:hover { background: color-mix(in srgb, var(--btn-primary-bg) 16%, transparent); }
  #collection-add-import-btn:active { transform: scale(0.92); }

  /* Add or Import Collection modal — uchta katta variant tugmasi. */
  .add-import-options { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 4px; }
  .add-import-option {
    justify-content: flex-start; gap: 12px; width: 100%;
    padding: 12px 14px; font-size: 14px; border: 1px solid var(--border);
    border-radius: var(--radius-md); text-align: left;
  }
  .add-import-option:hover { border-color: var(--btn-primary-bg); background: var(--surface-recessed); }
  .add-import-num {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; flex-shrink: 0; border-radius: 50%;
    background: var(--btn-primary-bg); color: var(--btn-primary-color, #04160c);
    font-size: 12px; font-weight: 700;
  }
  .header-icon-btn { background: transparent; border: none; width: 28px; height: 28px; padding: 0; border-radius: var(--radius-md); display: inline-flex; align-items: center; justify-content: center; color: inherit; transition: background-color 0.12s ease; }
  .header-icon-btn:hover { background: rgba(127, 127, 127, 0.18); }
  /* Folders sit visually between collections and requests: same row shape
     as a request (icon + name + actions, padding-left indent — not a
     margin, so the colored accent border stays flush like requests do),
     but bold + slightly muted so it doesn't read as a leaf/request row. */
  .folder-item { padding: var(--space-1) var(--space-2) var(--space-1) 20px; border-radius: var(--radius-sm); cursor: default; font-size: 12px; font-weight: 600; color: var(--text-secondary); display: flex; gap: var(--space-1); align-items: center; }
  .caret { display: inline-block; flex-shrink: 0; font-size: 9px; width: 10px; transition: transform 0.1s; }
  .caret.collapsed { transform: rotate(-90deg); }
  .folder-item:hover { background: var(--surface); }
  /* Daraxt ichidagi subtle harakat — global `button` gradient/shadow'ini
     bekor qilib, ghost matn tugmasiga aylantiramiz (ilgari ko'tarilgan
     yashil blok bo'lib, daraxtdan ajralib turardi). */
  .add-folder-btn { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-1) 0 var(--space-1) 20px; font-size: 12px; height: 24px; padding: 0 var(--space-2); background: transparent; border: none; box-shadow: none; color: var(--text-muted); font-weight: 500; }
  .add-folder-btn::before, .add-folder-btn::after { display: none; }
  .add-folder-btn:hover { background: var(--surface-2); color: var(--color-text); transform: none; box-shadow: none; }
  :root[data-theme="light"] .add-folder-btn { color: #616b78; }
  :root[data-theme="light"] .add-folder-btn:hover { background: var(--surface-3); }
  .folder-item.drag-over, .collection-item.drag-over { background: var(--color-selected-bg); color: var(--color-text); outline: 2px dashed var(--btn-primary-bg); outline-offset: -2px; }
  .request-item.dragging { opacity: 0.5; }
  .request-item { padding: var(--space-1) var(--space-2) var(--space-1) 20px; border-radius: var(--radius-sm); cursor: pointer; font-size: 12px; display: flex; gap: var(--space-2); align-items: center; }
  .request-item.bulk-selected { background: var(--color-selected-bg); box-shadow: inset 3px 0 0 var(--btn-primary-bg); }
  .request-item .bulk-select-checkbox { flex-shrink: 0; margin: 0; }
  #bulk-action-bar { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2); margin-top: var(--space-2); border-top: 1px solid var(--border); flex-wrap: wrap; max-width: 100%; }
  /* min-width:0 + max-width:100%: a long folder name as the selected
     <option> otherwise floors this select at its intrinsic content width
     (same flexbox "won't shrink past content" trap as .row input/select
     below) — on the 190px tablet sidebar that pushes the row past the
     sidebar's edge into horizontal scroll instead of just wrapping. */
  #bulk-move-target { min-width: 0; max-width: 100%; flex: 1 1 auto; }
  #bulk-action-bar.hidden { display: none; }
  #bulk-move-target { max-width: 130px; }
  #bulk-select-toggle-btn.active { background: var(--color-selected-bg); color: var(--color-primary-2); }
  .request-item:hover { background: var(--surface-2); }
  .method-badge { font-size: 10px; font-weight: 700; width: 38px; flex-shrink: 0; }
  .m-GET, .m-POST, .m-PUT, .m-PATCH, .m-DELETE, .m-HEAD { font-weight: 700; }
  .m-GET { color: var(--method-get); } .m-POST { color: var(--method-post); } .m-PUT { color: var(--method-put); }
  .m-DELETE { color: var(--method-delete); } .m-PATCH { color: var(--method-patch); } .m-HEAD { color: var(--method-head); }
  /* #req-method carries its m-* class directly (see updateReqMethodColor in
     request-builder.js) so the closed <select> box itself is colored/bold,
     not just its option list. */

  #main { grid-area: main; overflow: hidden; padding: 16px 16px 0; display: flex; flex-direction: column; min-height: 0; min-width: 0; }
  #main-left { display: flex; flex-direction: column; min-height: 0; min-width: 0; flex: 1; }
  /* Tablet range: more room than a phone, but not enough for the full
     230px sidebar + capped content rows. Narrow the sidebar rather than
     collapsing it off-canvas, and let the flow-palette drawer (built for
     phones below) kick in here too since 220px fixed still eats a big
     chunk of a tablet-width canvas. */
  @media (min-width: 769px) and (max-width: 1024px) {
    #app { grid-template-columns: 190px minmax(0, 1fr); }
    #flow-palette-toggle-btn { display: inline-flex; align-items: center; justify-content: center; }
    #flow-palette {
      position: fixed; top: 0; bottom: 0; left: 0; width: 280px; max-width: 60vw;
      background: var(--surface-void); z-index: 60; transform: translateX(-100%); transition: transform 0.2s ease;
      box-shadow: 2px 0 12px rgba(0,0,0,0.4);
    }
    #flow-palette.open { transform: translateX(0); }
    #flow-palette-backdrop.open {
      display: block; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 55;
    }
  }
  /* Tablets are touch devices at a width the 768px mobile block doesn't
     cover — request bar controls and tabs still need a 44px tap target
     here even though the layout itself stays desktop-like (one row, no
     stacking). Gated on hover:none so a mouse-driven 769-1024px window
     (a resized desktop browser) keeps the compact 32px controls. */
  @media (min-width: 769px) and (max-width: 1024px) and (hover: none) {
    #req-method, #send-btn, #cancel-req-btn { min-height: 44px; height: 44px; }
    #action-more-btn { min-width: 44px; min-height: 44px; }
    .tab { min-height: 44px; padding-top: var(--space-3); padding-bottom: var(--space-3); }
  }
  /* min-width:0: #tab-content-scroll is a flex item of #main-left (flex
     column) — without it, wide content inside (e.g. the Response tab's
     unbroken white-space:pre JSON lines) sets this item's intrinsic min
     width instead of respecting the column's actual width, forcing the
     whole page into horizontal scroll instead of scrolling internally. */
  #tab-content-scroll { flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0; min-width: 0; padding-bottom: 16px; }
  #tab-content-scroll.layout-split-h { display: grid; grid-template-columns: minmax(0, var(--split-h-a, 1fr)) 0 minmax(0, var(--split-h-b, 1fr)); grid-auto-rows: 1fr; gap: 16px; overflow-y: hidden; height: 100%; }
  #tab-content-scroll.layout-split-v { display: grid; grid-template-columns: 1fr; grid-template-rows: minmax(0, var(--split-v-a, 1fr)) 0 minmax(0, var(--split-v-b, 1fr)); gap: 16px; overflow-y: hidden; height: 100%; }
  #tab-content-scroll.layout-split-h > .tab-panel.active,
  #tab-content-scroll.layout-split-v > .tab-panel.active { display: block; overflow-y: auto; min-height: 0; }
  #tab-content-scroll.layout-split-h > .tab-panel.active:not(#tab-body):not(#tab-response),
  #tab-content-scroll.layout-split-v > .tab-panel.active:not(#tab-body):not(#tab-response) { grid-column: 1 / -1; }
  /* Body+Response only share the two grid columns when both are shown at
     once (selectTab() adds this for non-GET/HEAD methods) — for a bodyless
     method only Response is active, and without this it'd still be pinned
     to a single half-width column, leaving the other half blank. */
  #tab-content-scroll.layout-split-h > .tab-panel.active.solo,
  #tab-content-scroll.layout-split-v > .tab-panel.active.solo { grid-column: 1 / -1; grid-row: 1 / -1; }
  #tab-content-scroll.layout-split-h #tab-response.active,
  #tab-content-scroll.layout-split-v #tab-response.active { display: flex; flex-direction: column; }
  #tab-content-scroll.layout-split-h #tab-response.active #response-scroll,
  #tab-content-scroll.layout-split-v #tab-response.active #response-scroll { flex: 1; overflow-y: auto; min-height: 0; }
  /* Explicit tracks so Body always sits in the first pane/row and Response
     in the second regardless of DOM order — the resizer handle occupies
     the (zero-width, expanded visually via negative margin) middle track.
     :not(.solo): these two IDs outweigh the .solo full-span rule above on
     specificity, so without the exclusion a bodyless method (only Response
     active, no Body to pair with) still got pinned to the half-width
     column here — Response squeezed to one side with the other half of
     the panel left blank instead of the .solo rule's intended full width. */
  #tab-content-scroll.layout-split-h > #tab-body.active { grid-column: 1; grid-row: 1; }
  #tab-content-scroll.layout-split-h > #tab-response.active:not(.solo) { grid-column: 3; grid-row: 1; }
  #tab-content-scroll.layout-split-v > #tab-body.active { grid-column: 1; grid-row: 1; }
  #tab-content-scroll.layout-split-v > #tab-response.active:not(.solo) { grid-column: 1; grid-row: 3; }
  .split-resizer { display: none; position: relative; z-index: 1; }
  #tab-content-scroll.layout-split-h.split-both-active > .split-resizer-h {
    display: block; grid-column: 2; grid-row: 1; width: 6px; margin: 0 -3px; cursor: col-resize;
  }
  #tab-content-scroll.layout-split-v.split-both-active > .split-resizer-v {
    display: block; grid-column: 1; grid-row: 2; height: 6px; margin: -3px 0; cursor: row-resize;
  }
  .split-resizer:hover, .split-resizer.dragging { background: var(--color-primary); }
  .tabs-wrap { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; margin: var(--space-4) 0; }
  .tabs-scroll-outer { position: relative; flex: 1; min-width: 0; }
  .tabs { display: flex; gap: var(--space-4); border-bottom: 1px solid var(--color-border); overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs-fade { position: absolute; top: 0; bottom: 1px; width: 24px; pointer-events: none; opacity: 0; transition: opacity 0.15s; }
  .tabs-fade-left { left: 0; background: linear-gradient(to right, var(--surface-void), transparent); }
  .tabs-fade-right { right: 0; background: linear-gradient(to left, var(--surface-void), transparent); }
  .tabs-scroll-outer.scrolled-left .tabs-fade-left { opacity: 0.9; }
  .tabs-scroll-outer.has-more-right .tabs-fade-right { opacity: 0.9; }
  .tab { flex-shrink: 0; }
  .settings-toggle { display: flex; align-items: center; gap: 10px; font-size: 13px; padding: 8px 0; cursor: pointer; max-width: 420px; }
  .settings-toggle input[type="checkbox"] { width: 16px; height: 16px; flex-shrink: 0; }
  .hint-box { margin-top: 18px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-recessed); }
  :root[data-theme="light"] .hint-box { background: var(--color-bg); }
  .mono-output-box { font-family: monospace; font-size: 11px; overflow: auto; background: var(--surface-void); border-radius: 4px; padding: 8px; }
  :root[data-theme="light"] .mono-output-box { background: var(--color-surface-2); }
  /* Load-test progress + result dashboard — was a flat 11-row two-column
     table with a hardcoded blue (#4f6df5, off-brand) fill; now KPI cards
     for the 5 numbers that matter at a glance (success/fail/rps/p50/p99),
     a secondary row for the rest, and colored status chips instead of a
     second table. All colors route through this app's existing tokens so
     dark/black/light all just work. */
  .lt-progress-wrap { margin: 16px 0; }
  .lt-progress-label { display: flex; justify-content: space-between; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; }
  .lt-progress-track { height: 10px; border-radius: 5px; background: var(--surface-recessed); border: 1px solid var(--border); overflow: hidden; }
  :root[data-theme="light"] .lt-progress-track { background: var(--color-surface-3); }
  .lt-progress-fill { height: 100%; background: linear-gradient(90deg, var(--btn-primary-bg), var(--accent-2)); box-shadow: 0 0 10px -2px rgba(53,208,127,0.6); transition: width 0.15s; }
  .lt-chart-card { margin: 16px 0; max-width: 640px; height: 220px; position: relative; background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 12px; }
  .lt-kpi-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin-bottom: 16px; }
  .lt-kpi-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 12px 10px; }
  .lt-kpi-card .lt-kpi-v { font-family: monospace; font-size: 19px; font-weight: 700; color: var(--text); }
  .lt-kpi-card .lt-kpi-l { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-top: 4px; font-weight: 600; }
  .lt-kpi-card.ok .lt-kpi-v { color: var(--color-success); }
  .lt-kpi-card.err .lt-kpi-v { color: var(--color-danger); }
  .lt-meta-row { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 16px; padding: 10px 14px; background: var(--surface-recessed); border: 1px solid var(--border); border-radius: 8px; }
  :root[data-theme="light"] .lt-meta-row { background: var(--color-surface-2); }
  .lt-meta-item .lt-meta-v { font-family: monospace; font-size: 12.5px; font-weight: 600; color: var(--text-secondary); }
  .lt-meta-item .lt-meta-l { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
  .lt-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
  .lt-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-family: monospace; font-size: 12px; font-weight: 600; }
  .lt-chip .lt-chip-n { opacity: 0.7; font-weight: 500; }
  .lt-section-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); font-weight: 700; margin: 0 0 8px; }
  .lt-warning-banner { background: var(--color-warning-bg); border: 1px solid var(--color-warning-border); color: var(--color-warning); border-radius: 8px; padding: 10px 14px; font-size: 12.5px; line-height: 1.5; margin-bottom: 16px; }
  /* Settings/Load-test rows pack several labeled fields side by side —
     the app-wide .row gap (6px, tuned for dense rows like kv-editor
     fields) reads as cramped here where each field carries its own
     label+input stack, not a single-line control. */
  #tab-settings .row, #tab-loadtest .row { gap: 16px; row-gap: 12px; }
  #tab-settings .row > div, #tab-loadtest .row > div { gap: 5px !important; }
  .tab { padding: var(--space-2) var(--space-1); font-size: 11px; cursor: pointer; border-bottom: 3px solid transparent; color: var(--text-muted); display: inline-flex; align-items: center; gap: var(--space-1); }
  .tab.active { color: var(--color-text); border-color: var(--btn-primary-bg); font-weight: 600; }
  /* Sub-tab'lar endi klaviatura bilan fokuslanadi (role=tab, roving tabindex) */
  .tab:focus-visible { outline: 2px solid var(--color-primary-2); outline-offset: -2px; border-radius: 2px; }
  .tab-panel:focus-visible { outline: 2px solid var(--color-primary-2); outline-offset: -2px; }
  .tab-icon { flex-shrink: 0; opacity: 0.75; }
  .tab.active .tab-icon { opacity: 1; }
  .tab-dot { display: none; flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--color-primary); }
  .tab.has-data .tab-dot { display: inline-block; }
  .tab-panel { display: none; }
  .tab-panel.active { display: block; }
  #tab-response.active { display: flex; flex-direction: column; height: 100%; min-height: 0; }
  /* height:100% ni #tab-content-scroll balandligiga bog'laydi — past
     oynada (noutbuk, brauzer yarim ekran) tepadagi tab strip + URL qatori
     + panel tablari deyarli hammasini egallab, javob uchun ~90px qolardi:
     sticky status paneli o'sha joyni to'ldirib, Body/Headers va javob
     matni pastda kesilib qolardi. Minimal balandlik qo'yilsa, joy
     yetmaganda tashqi #tab-content-scroll scroll qiladi va javob to'liq
     ko'rinadi. Split (yonma-yon) rejimlarda tegilmaydi — u yerda balandlik
     grid track'lari bilan boshqariladi. */
  #tab-content-scroll:not(.layout-split-h):not(.layout-split-v) #tab-response.active { min-height: 320px; }
  /* Body'siz metodlarda (GET/HEAD) javob paneli split grid'da yolg'iz
     qoladi (.solo) va butun grid'ni egallaydi. Bu holda split'ning
     "ikkala panel ham ekranga sig'sin" cheklovi ma'nosiz — past oynada u
     panelni ~90px'gacha siqib, javobni kesardi. Yolg'iz bo'lganda grid
     balandligini kontentga qo'yib yuboramiz va tashqi konteyner scroll
     qiladi. Ikki panel ochiq bo'lgan haqiqiy split rejimga tegilmaydi. */
  #tab-content-scroll.layout-split-h:has(> #tab-response.active.solo),
  #tab-content-scroll.layout-split-v:has(> #tab-response.active.solo) { height: auto; overflow-y: auto; grid-auto-rows: auto; }
  #tab-content-scroll.layout-split-h > #tab-response.active.solo,
  #tab-content-scroll.layout-split-v > #tab-response.active.solo { min-height: 320px; }

  /* ---------- Panel fill-height (Body / Response) ----------
     Ilgari body editor (min-height:200px) va javob .code-block
     (min-height:220px) kontentga qarab o'lchanardi — split grid (1fr
     track) yoki #tab-response (height:100%) balandligini to'ldirmay,
     ostida katta bo'sh joy qolardi. Endi editor/kod bloki panel
     balandligini egallaydi. */
  #tab-content-scroll.layout-split-h > #tab-body.active,
  #tab-content-scroll.layout-split-v > #tab-body.active,
  #tab-content-scroll:not(.layout-split-h):not(.layout-split-v) #tab-body.active {
    display: flex; flex-direction: column; min-height: 0;
  }
  #tab-content-scroll:not(.layout-split-h):not(.layout-split-v) #tab-body.active { min-height: 420px; }
  /* Faqat asosiy body editori (#body-editor-wrap) cho'ziladi — GraphQL
     ichki editorlari (.body-editor-wrap, id'siz) o'z min-height'ida qoladi. */
  #tab-body.active #body-editor-wrap {
    flex: 1 1 auto; min-height: 200px; display: flex; flex-direction: column;
  }
  #tab-body.active #body-editor-wrap > textarea { flex: 1 1 auto; height: 100%; resize: none; }

  /* Javob: #response-scroll -> #response-content -> .code-block zanjiri
     bo'ylab balandlik uzatiladi, oxirida kod bloki (yoki header ro'yxati)
     qolgan joyni egallaydi. */
  #tab-response.active #response-scroll { display: flex; flex-direction: column; }
  /* min-width:0 zanjir bo'ylab shart — aks holda flex item'lar (column
     kontekstda min-width:auto) uzun JSON qatorining intrinsic kengligiga
     cho'zilib, panelni yorib chiqadi va #resp-body gorizontal scroll
     o'rniga matn qirqiladi. */
  #tab-response.active #response-content { flex: 1 1 auto; min-height: 0; min-width: 0; display: flex; flex-direction: column; }
  #tab-response.active #response-content > .code-block,
  #tab-response.active #response-content > #resp-headers-list:not(.hidden) { flex: 1 1 auto; min-height: 220px; min-width: 0; max-width: 100%; }
  /* "Javob yo'q" holati panel markazida — ilgari to'ldirilmagan panelning
     tepa-chap burchagida yolg'iz kichik matn bo'lib turardi. */
  #tab-response.active #response-scroll > #response-empty:not(.hidden) {
    flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center; gap: var(--space-2); padding: var(--space-7); font-size: 13px;
  }
  #tab-response.active #response-scroll > #response-empty:not(.hidden)::before {
    content: ""; width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;
    background: var(--surface) no-repeat center / 18px url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23888" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>');
    border: 1px solid var(--color-border);
  }
  #tab-response.active #resp-body { overflow-y: auto; }
  /* The step-by-step flow log used to also render here, inside the Response
     tab body — redundant now that #flow-progress-strip shows the same
     steps compactly, with color, right above the URL bar on every tab. */
  #flow-timeline { display: none !important; }
  /* min-width:0: #response-scroll is itself a flex item of #tab-response.active
     (flex column) — the actual gap in the earlier #tab-content-scroll/.code-block
     fix. .code-block is a normal block child of THIS element, so its width was
     never the problem; #response-scroll's own missing min-width:0 let its
     content (ultimately .code-block's pre content) set ITS intrinsic min
     width, which is what was still forcing the page to scroll sideways. */
  #response-scroll { flex: 1; overflow-y: auto; overflow-x: auto; min-height: 0; min-width: 0; max-width: 100%; }
  /* Capped so key/value fields stay a readable width on wide screens
     instead of stretching edge-to-edge with the whole main panel. */
  .kv-row { display: grid; grid-template-columns: 24px minmax(120px, 260px) minmax(160px, 1fr) 32px; gap: 6px; margin-bottom: 6px; max-width: 700px; align-items: center; }
  .kv-row input[type=checkbox] { margin: auto; }
  /* min-width:0 overrides the grid item default of min-width:auto — without
     it, a text input's intrinsic content width floors the column wider than
     the track's own minmax() minimum, so the row overflows narrow viewports
     instead of shrinking down to the declared 120px/160px floors. */
  .kv-row input:not([type=checkbox]), .kv-row select { min-width: 0; }
  /* Key vs value are visually identical plain inputs otherwise — a subtle
     accent on the key field (matching each theme's accent color) makes the
     two columns easier to tell apart at a glance in long header/param lists. */
  .kv-row-key { color: var(--color-primary-2); font-weight: 600; }
  :root[data-theme="light"] .kv-row-key { color: var(--color-primary); }
  /* A header/param field referencing {{var}} gets a border/background tint
     instead of per-token coloring (unlike the URL bar/JSON body, these are
     plain inputs with no backdrop overlay to color individual tokens in) —
     red while any {{var}} it contains doesn't resolve, green once they all
     do. Hovering an unresolved one still offers to create it (see
     attachVarHighlighting/attachVarHoverCreate in collections.js). */
  .kv-row-key.kv-var-unresolved, .kv-row-value.kv-var-unresolved { border-color: #c23434 !important; background: #2e1c1c !important; }
  .kv-row-key.kv-var-resolved, .kv-row-value.kv-var-resolved { border-color: #2e7d5b !important; background: #1c2e22 !important; }
  :root[data-theme="light"] .kv-row-key.kv-var-unresolved, :root[data-theme="light"] .kv-row-value.kv-var-unresolved { border-color: var(--color-danger) !important; background: var(--color-danger-bg) !important; }
  :root[data-theme="light"] .kv-row-key.kv-var-resolved, :root[data-theme="light"] .kv-row-value.kv-var-resolved { border-color: #1e8a4c !important; background: #e6f7ec !important; }
  .kv-row-remove { border-radius: 50%; padding: 0; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; justify-self: end; }
  /* form-data row: checkbox | key | type | value-or-file (same cell, only one
     visible at a time) | delete — its own explicit column count/widths since
     it has one more field than a plain key-value row. */
  .multipart-row { grid-template-columns: 24px minmax(90px, 160px) 84px minmax(140px, 1fr) 32px; max-width: 900px; }
  .multipart-row > * { height: 38px; }
  .multipart-row input:not([type=checkbox]), .multipart-row select { min-width: 0; }
  .multipart-row .multipart-field-type { grid-column: 3; }
  .multipart-row input[placeholder="value"], .multipart-row .multipart-file-picker { grid-column: 4; }
  .multipart-row .kv-row-remove { grid-column: 5; }
  .multipart-file-picker { display: flex; align-items: center; gap: 8px; min-width: 0; }
  .file-picker-btn { display: inline-flex; align-items: center; justify-content: center; height: 38px; padding: 0 12px; flex-shrink: 0; white-space: nowrap; cursor: pointer; }
  .file-picker-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; min-width: 0; flex: 1; }
  .file-picker-clear { background: transparent; border: none; padding: 0 4px; flex-shrink: 0; border-radius: 50%; width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted); }
  .file-picker-clear:hover { color: #ff9c9c; background: #2e1c1c; }
  textarea { width: 100%; min-height: 140px; font-family: monospace; }
  input::placeholder, textarea::placeholder { color: #757c8c; opacity: 1; }
  :root[data-theme="light"] input::placeholder, :root[data-theme="light"] textarea::placeholder { color: #8a909c; }
  /* #response-status-bar: javob status paneli endi request bar ichida
     (index.html), URL qatori ostida alohida qator. */
  .response-meta { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-1) 0 var(--space-2); flex-wrap: wrap; }
  #resp-meta-sub, .resp-meta-sub { display: flex; flex-direction: row; align-items: center; gap: var(--space-2); margin-right: var(--space-1); }
  #resp-size { font-size: 11px; color: var(--text-muted); }
  .response-actions { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; }
  .resp-more-wrap { position: relative; display: inline-flex; }
  .resp-more-menu {
    position: absolute; top: 36px; right: 0; z-index: 50; display: flex; flex-direction: column; gap: 2px;
    background: var(--surface-recessed); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-2); min-width: 220px;
    max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); overflow-y: auto;
    box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  }
  .resp-more-menu.hidden { display: none; }
  .resp-more-menu li { list-style: none; }
  .resp-more-menu .btn { width: 100%; }
  .resp-more-menu .resp-zoom-group { width: 100%; flex-wrap: nowrap; }
  .resp-more-menu .resp-zoom-group .btn { flex: 1 1 0; min-width: 0; width: auto; padding-left: 4px; padding-right: 4px; }
  :root[data-theme="light"] .resp-more-menu { background: var(--surface); border-color: var(--color-border-strong); }
  /* Inline error state — replaces the old duplicate modal+response error
     dialog with a single alert box right where the response would be. */
  .resp-error-alert { border: 1px solid var(--color-danger-border); background: var(--color-danger-bg); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4); margin: 0; width: 100%; max-width: 480px; box-sizing: border-box; }
  .resp-error-alert-title { display: flex; align-items: center; gap: var(--space-2); font-weight: 700; color: #ff9c9c; margin-bottom: var(--space-1); white-space: pre-wrap; word-break: break-all; overflow-wrap: anywhere; }
  .resp-error-alert-message { font-size: 13px; line-height: 1.4; margin-bottom: var(--space-1); white-space: pre-wrap; word-break: break-all; overflow-wrap: anywhere; }
  .resp-error-alert-details { font-size: 12px; }
  .resp-error-alert-details summary { cursor: pointer; color: var(--text-muted); user-select: none; }
  .resp-error-alert-detail { font-family: monospace; font-size: 11px; color: var(--text-muted); white-space: pre-wrap; word-break: break-all; overflow-wrap: anywhere; margin-top: var(--space-1); }
  :root[data-theme="light"] .resp-error-alert { background: var(--color-danger-bg); border-color: #f4bcbc; }
  :root[data-theme="light"] .resp-error-alert-hint { background: rgba(0,0,0,0.03); color: #b83232; }
  :root[data-theme="light"] .resp-error-alert-detail { color: #7b828e; }
  #resp-search-bar, #resp-filter-bar, #capture-row { margin-bottom: 10px; }
  /* Sticky within #response-scroll (its scrolling ancestor) so the status row
     and search/filter controls stay reachable on a long response instead of
     scrolling out of view. Search and filter can both be open at once, so they're
     wrapped together rather than each given a fixed top offset (which would
     overlap) — the wrapper just sticks and lets its children stack normally. */
  /* Same neutral-card treatment as the sidebar sections / request bar above.
     Endi faqat search / filter / capture barlarini o'raydi (status paneli
     yuqoriga ko'chdi) — uchalasi ham yopiq bo'lsa, kartani butunlay
     yashiramiz, aks holda bo'sh chegara ko'rinib qoladi. */
  #response-toolbar-sticky { position: sticky; top: 0; z-index: 2; background: var(--surface-void); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-lg); }
  #response-toolbar-sticky:not(:has(> .row:not(.hidden))) { display: none; }
  #tab-response.response-fullscreen { position: fixed !important; inset: 0; z-index: 200; background: var(--surface-void); padding: 16px 20px; margin: 0; overflow: hidden; display: flex !important; flex-direction: column; }
  /* Status paneli #tab-response dan tashqarida — fullscreen overlay (z-index
     200) uni yopib qo'ymasligi uchun tepaga fixed qilib pin qilamiz, shunda
     kebab (undan fullscreen'dan chiqish) bosiladigan bo'lib qoladi. Esc ham
     ishlaydi. */
  body:has(#tab-response.response-fullscreen) #response-status-bar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 201;
    background: var(--color-bg);
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--color-border);
    margin: 0;
  }
  body:has(#tab-response.response-fullscreen) #tab-response.response-fullscreen #response-scroll { padding-top: 44px; }
  body:has(#tab-response.response-fullscreen) #tab-response.response-fullscreen #response-toolbar-sticky { top: 44px; }
  #tab-response.response-fullscreen #response-scroll { flex: 1; min-height: 0; overflow-y: auto; }
  #tab-response.response-fullscreen #response-toolbar-sticky { background: var(--surface-void); }
  /* Save as Variable now lives in #response-toolbar-sticky next to
     search/filter (toggled from the More menu — see capture-toggle-btn),
     so it's already sticky via that wrapper and doesn't need its own
     sticky-to-bottom positioning. */
  #capture-row { gap: 10px; max-width: 760px; justify-content: flex-start; }
  #capture-row input { min-width: 160px; flex: 0 1 220px; }
  #resp-filter-toggle-btn.active { border-color: var(--color-primary-2); color: var(--color-primary-2); }
  .resp-search-hit { background: var(--resp-hit-bg); color: var(--resp-hit-fg); border-radius: 2px; }
  .resp-search-hit.current { background: var(--resp-hit-current-bg); color: var(--resp-hit-current-fg); }
  /* Kompakt request bar ichida — ilgarigi katta 17px pill o'rniga kichikroq. */
  #resp-status {
    font-size: 13px; font-weight: 700; padding: 2px var(--space-2); border-radius: var(--radius-sm); letter-spacing: 0.2px;
  }
  #resp-time { font-size: 12px; font-weight: 600; }
  /* A slow response deserves more visual weight than a same-size number
     next to it, not just a different color — matches the loud green status
     badge's own emphasis instead of losing to it. */
  #resp-time.time-slow { font-size: 13px; font-weight: 800; }
  /* Styling now comes from DaisyUI's .badge.badge-error classes (see
     tab-response.html) instead of custom CSS here — the pilot's whole
     point is trying the component library's own look. */
  .status-ok { color: var(--status-ok-fg); background: var(--status-ok-bg); }
  .status-err { color: var(--status-err-fg); background: var(--status-err-bg); }
  /* History's status badges used status-ok/status-err's binary split, so
     every 4xx and every 5xx read as the identical "red" — no way to tell a
     client error from a server error at a glance. Four buckets instead of
     two, matching each status class's own conventional color. */
  .status-2xx { color: var(--status-ok-fg); background: var(--status-ok-bg); }
  .status-3xx { color: var(--status-3xx-fg); background: var(--status-3xx-bg); }
  .status-4xx { color: var(--status-4xx-fg); background: var(--status-4xx-bg); }
  .status-5xx { color: var(--status-err-fg); background: var(--status-err-bg); }
  .status-neutral { color: var(--status-neutral-fg); background: var(--status-neutral-bg); }
  .assertion-item { font-size: 13px; padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); margin-bottom: var(--space-1); }
  .assertion-item.pass { background: var(--result-pass-bg); color: var(--result-pass-fg); }
  .assertion-item.fail { background: var(--result-fail-bg); color: var(--result-fail-fg); }
  pre { white-space: pre-wrap; word-break: break-all; background: var(--surface-void); padding: 10px; border-radius: 4px; font-size: 12px; margin: 0; }
  /* Response body gets its own two-column block: a non-wrapping line-number
     gutter + the (also non-wrapping, horizontally scrollable) code — the
     standard code-viewer layout, and the only way to keep gutter numbers
     aligned 1:1 with logical lines (wrapped text would desync them). */
  /* min-width:0: .code-block is a flex item of #tab-response.active (flex
     column) — same trap as #tab-content-scroll above. Without it, a long
     unbroken response line sets this item's own min-content width instead
     of clipping to the available column width, and that overflow bubbles
     up into page-level horizontal scroll despite overflow:hidden here (a
     flex item's own forced min-width is resolved before this rule's
     overflow ever gets a chance to clip anything). */
  .code-block { display: flex; min-width: 0; background: var(--surface-void); border-radius: var(--radius-md); overflow: hidden; border: 1px solid var(--color-border); min-height: 220px; }
  .code-block.json-invalid { border-color: var(--color-danger); }
  /* Body/Headers ko'rinish almashtirgichi (#resp-code-block yoki
     #resp-headers-list) — javob header'lari ilgari umuman ko'rsatilmas edi. */
  #resp-view-toggle { display: flex; gap: var(--space-1); margin-bottom: var(--space-2); }
  .resp-view-btn { background: transparent; border: none; padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); font-size: 12px; font-weight: 600; color: var(--text-muted); cursor: pointer; }
  .resp-view-btn:hover { background: var(--surface-2); color: #eee; }
  .resp-view-btn.active { background: var(--surface-2); color: #eee; }
  .resp-view-count { opacity: 0.65; font-weight: 500; }
  #resp-headers-list { background: var(--surface-void); border-radius: var(--radius-md); border: 1px solid var(--color-border); min-height: 220px; padding: var(--space-2) var(--space-3); font-size: 12px; }
  .resp-header-row { display: flex; gap: var(--space-3); padding: var(--space-1) 0; border-bottom: 1px solid #1f222b; font-family: monospace; }
  .resp-header-row:last-child { border-bottom: none; }
  .resp-header-key { color: var(--color-primary-2); font-weight: 600; flex: 0 0 220px; word-break: break-all; }
  .resp-header-value { color: #ccc; word-break: break-all; flex: 1; min-width: 0; }
  :root[data-theme="light"] .resp-view-btn.active, :root[data-theme="light"] .resp-view-btn:hover { background: var(--surface-3); color: var(--color-text); }
  :root[data-theme="light"] #resp-headers-list { background: var(--color-surface-2); border-color: var(--color-border); }
  :root[data-theme="light"] .resp-header-row { border-bottom-color: #e7eaee; }
  :root[data-theme="light"] .resp-header-key { color: var(--color-primary); }
  :root[data-theme="light"] .resp-header-value { color: #616b78; }
  .code-gutter, #resp-body { padding-top: 10px; padding-bottom: 10px; }
  #resp-body { padding-left: 12px; padding-right: 12px; }
  .code-gutter, #resp-body { white-space: pre; border-radius: 0; font-size: var(--resp-font-size, 12px); line-height: 1.55; }
  #resp-body { overflow-x: auto; scrollbar-width: thin; }
  /* Gutter o'z scrollbar'ini ko'rsatmasin — u JS orqali #resp-body bilan
     sinxron aylantiriladi (chap tarafdagi ortiqcha vertikal scroll
     yo'qoladi). */
  .code-gutter {
    flex-shrink: 0; text-align: right; color: #5b6272; user-select: none; background: var(--surface-recessed);
    border-right: 1px solid var(--color-border); padding-right: 10px; margin-right: 0;
    overflow: hidden;
  }
  #resp-body { flex: 1; min-width: 0; }
  .resp-zoom-group { display: inline-flex; align-items: center; gap: 2px; margin-left: 4px; }
  .resp-zoom-group .icon-btn { width: 26px; height: 26px; }
  .muted { color: var(--text-muted); font-size: 13px; }
  .field-error { color: #ff6b6b; font-size: 11px; margin: 4px 0; min-height: 14px; }
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
  td, th { text-align: left; padding: 4px 6px; border-bottom: 1px solid var(--color-border); }

  .icon-btn { background: transparent; border: 1px solid var(--icon-btn-border); width: 34px; height: 34px; padding: 0; border-radius: var(--radius-sm); font-size: 15px; display: inline-flex; align-items: center; justify-content: center; }
  /* Topbar icon tugmalari — barchasi bir xil o'lcham/hover.
     Ilgari tools/more tugmalari DaisyUI .btn-ghost:hover dan fon olardi,
     boshqalari faqat global button:hover lift'idan; endi yagona qoida. */
  #topbar .icon-btn { flex-shrink: 0; }
  #topbar .icon-btn:hover { transform: none; background: var(--surface-2); box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 -6px 10px -6px rgba(0,0,0,0.5) inset, 0 1px 2px rgba(0,0,0,0.35); }
  #topbar .icon-btn:focus-visible { background: var(--surface-2); }
  #topbar .icon-btn:active { transform: scale(0.95); }
  #topbar .icon-btn svg { display: block; }
  .icon-btn--label-xs { width: auto; height: 24px; padding: 0 var(--space-2); font-size: 11px; }
  .icon-btn--label-sm { width: auto; padding: 2px var(--space-2); font-size: 11px; flex-shrink: 0; gap: var(--space-2); }
  .icon-btn--label { width: auto; padding: 0 var(--space-3); font-size: 11px; }
  .icon-btn--label-tight { width: auto; padding: 0 var(--space-2); font-size: 11px; }
  .icon-btn--menu-item { width: 100%; justify-content: flex-start; gap: var(--space-2); padding: 0 var(--space-3); }
  /* Topbar nav buttons: text labels instead of icon glyphs — minimal, uniform
     padding/size, consistent with the rest of the topbar's compact controls. */
  /* Explicit color, not just inherited from .btn-ghost — DaisyUI's own
     .btn-ghost text color isn't theme-aware here and read as near-invisible
     light gray in light mode; --btn-ghost-color already flips correctly
     between themes (see :root[data-theme="light"] above). */
  .topbar-nav-btn { background: transparent; border: 1px solid var(--border); color: var(--btn-ghost-color); padding: var(--space-2) var(--space-3); height: 34px; border-radius: var(--radius-md); font-size: 12px; font-weight: 500; white-space: nowrap; transition: background-color 0.15s ease; }
  .topbar-nav-btn:hover { background: var(--surface-2); }
  .topbar-nav-btn[aria-pressed="true"] { border-color: var(--btn-primary-bg); color: var(--color-primary-2); }
  .topbar-more-wrap { position: relative; display: inline-flex; }
  .topbar-more-menu { position: absolute; top: 40px; right: 0; z-index: 50; display: flex; flex-direction: column; gap: var(--space-1); background: var(--surface-recessed); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-2); min-width: 160px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); }
  .topbar-more-menu.hidden { display: none; }
  .topbar-more-menu .topbar-nav-btn { width: 100%; text-align: left; border: none; }
  .topbar-more-menu .topbar-nav-btn:hover { background: var(--surface-2); }
  /* Bo'lim ajratuvchi chiziq — More menyu endi tekis ro'yxat emas, guruhlangan
     (Workspace / Admin / Preferences / Session). */
  .topbar-more-divider { height: 1px; background: var(--border); margin: var(--space-1) var(--space-2); }
  .topbar-more-divider.hidden { display: none; }
  /* Logout boshqa item'lardan vizual ajralib tursin — danger ranglar bilan,
     confirmModal'dagi .danger tugma pattern'iga mos. */
  .topbar-nav-btn-danger { color: var(--color-danger); }
  .topbar-nav-btn-danger:hover { background: var(--color-danger-bg); }
  :root[data-theme="light"] .topbar-nav-btn:hover { background: var(--surface-3); }
  /* .topbar-more-menu had no light-theme override — it kept its dark
     background in every theme, so light-mode dark-on-dark text was nearly
     unreadable (WCAG contrast failure). */
  :root[data-theme="light"] .topbar-more-menu { background: var(--surface); border-color: var(--color-border-strong); box-shadow: 0 8px 24px rgba(0,0,0,0.12); }
  .history-item { cursor: pointer; padding: 3px var(--space-2); border-radius: var(--radius-sm); display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
  .history-item:hover { background: var(--surface-2); }
  /* URL'ning OXIRI ko'rinsin (history qatorlari ko'pincha bir xil
     {{base_url}}/prefix bilan boshlanadi — farq oxirida). `direction: rtl` +
     `text-overflow: ellipsis` ellipsisni boshiga qo'yadi; matn <bdi> ichida
     bo'lgani uchun URL o'zi LTR tartibda qoladi. Ilgari bu JS'da
     har qator uchun binary-search bilan o'lchanardi (fitHistoryUrlTail) —
     50 qatorda forced-reflow bo'lib "qotib qolish"ga sabab bo'lardi. */
  .history-url { color: var(--text-muted); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; min-width: 0; flex: 1; direction: rtl; text-align: left; }
  .history-meta { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; min-width: 100px; font-size: 11px; }
  .history-size { color: var(--text-muted); font-family: monospace; }
  .history-time { color: var(--color-text-muted); font-size: 10px; flex-shrink: 0; }
  .history-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; opacity: 0; transition: opacity 0.12s ease; }
  .history-item:hover .history-actions,
  .history-item:has(.history-pin-btn.pinned) .history-actions { opacity: 1; }
  .history-pin-btn { font-size: 12px; line-height: 1; padding: 2px 5px; }
  .history-pin-btn.pinned { opacity: 1; color: #f0cd6e; }
  /* Modal kengroq — URL qidiruvi butun qatorni oladi, method/status/date esa
     bir qatorda yonma-yon sig'adi. */
  /* split-shell: filtrlar body scroll'ining tepasiga yopishadi, ro'yxat
     body bilan birga scroll bo'ladi (o'z max-height'i shart emas). */
  #history-filters {
    display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px;
    position: sticky; top: -18px; z-index: 1; background: var(--surface);
    margin: -18px -22px 10px; padding: 12px 22px 10px;
  }
  #history-filters input, #history-filters select { min-width: 0; }
  #history-search { grid-column: 1 / -1; }
  #history-date-filter { color-scheme: dark; }
  :root[data-theme="light"] #history-date-filter { color-scheme: light; }
  #history-pager { display: flex; align-items: center; justify-content: center; gap: var(--space-3); margin-top: 10px; }
  @media (max-width: 560px) {
    #history-filters { grid-template-columns: 1fr 1fr; }
    #history-search { grid-column: 1 / -1; }
  }

  .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); align-items: center; justify-content: center; z-index: 100; }
  /* #flows-view sits at z-index:300 (own full-viewport shell). confirm-modal
     is triggered from inside it too (delete node/edge/flow) — without this it
     rendered behind flows-view: invisible, unclickable, "delete tugmasi
     ishlamayapti". Match flow-node-config-popup/flow-edge-popup's 310. */
  #confirm-modal { z-index: 310; }
  .modal-overlay.active { display: flex; }
  /* Modal cheti har mavzuda ko'rinsin — ilgari border yo'q edi, dark/black'da
     modal fon bilan qo'shilib ketardi. Border + soya uchala mavzuda. */
  .reqmodal { background: var(--surface); border-radius: 8px; padding: 20px; width: 480px; max-width: 90vw; max-height: 80vh; overflow-y: auto; overflow-x: hidden; }
  /* Modal cheti har mavzuda ko'rinsin (ilgari border yo'q edi, dark/black'da
     fonga singib ketardi). Doim full-screen shell modallar (monitoring/grpc)
     va cmdk palitrasi bundan chiqarilgan. split/uptime/pdf shell'lari
     desktopда oddiy modal — border oladi, mobil full-screen'da esa quyida
     (@media 768px) border/soya olib tashlanadi. */
  .reqmodal:not(.cmdk-modal):not(.monitoring-shell):not(.grpc-shell) {
    border: 1px solid var(--color-border-strong);
    box-shadow: var(--modal-shadow, 0 12px 40px rgba(0,0,0,0.45));
  }
  /* Modal ichidagi tugma chetlari ko'rinsin — global --btn-border
     o'zgarishi (grill audit) juda keng edi, shu sabab modal bilan
     cheklandi. border-color: ghost tugmada `transparent` -> ko'rinadi,
     solid tugmada mavjud 1px width'ni bo'yaydi. */
  .reqmodal button:not(.icon-btn), .reqmodal .btn { border-color: var(--color-border-strong); }
  .reqmodal .btn-primary, .reqmodal button.primary { border-color: var(--btn-primary-bg); }
  .reqmodal .btn-error, .reqmodal button.danger { border-color: var(--btn-danger-border); }
  .reqmodal input, .reqmodal select, .reqmodal button:not(.icon-btn):not(.round-add-btn):not(.modal-x) { height: 38px; }
  /* Admin/Organizations content is tabular and gets cramped fast at the
     default 420px modal width — these get noticeably more room. */
  .reqmodal.modal-wide { width: 780px; max-width: 94vw; max-height: 86vh; }
  /* Kontent-og'ir modallar (History, Manage Variables/Globals, Net Speed,
     Admin, Codegen) uchun yagona keng o'lcham — ekranga qarab cho'ziladi,
     max-width bilan cheklangan, ichki kontent o'zi scroll bo'ladi. */
  .reqmodal.modal-xl { width: 95vw; max-width: 1200px; height: 85vh; max-height: 85vh; }
  /* Every modal used to scroll as one block — a long form's header/title and
     its bottom action row (Save/Cancel) scrolled out of view along with the
     content between them, so a user mid-scroll lost both "what am I editing"
     and "how do I confirm it" at the same time. Pinning header/close-row via
     sticky (offset to cover the modal's own padding, background copied from
     the modal via `inherit` so it works unmodified in dark/light/netspeed's
     own palette) means only the actual body content between them scrolls —
     no per-modal HTML changes needed, since every modal already shares these
     two class names. cmdk/pdf-viewer are excluded: cmdk already has its own
     non-scrolling shell around a dedicated `#cmdk-results` scroll area, and
     pdf-viewer's frame fills the modal via flex instead of this padding-box
     scroll model. */
  .reqmodal:not(.cmdk-modal):not(.pdf-viewer-box):not(.monitoring-shell):not(.grpc-shell):not(.uptime-shell):not(.split-shell) > .modal-header {
    position: sticky; top: -20px; z-index: 1;
    margin: -20px -20px 12px; padding: 20px 20px 12px; background: inherit;
  }
  /* Tab bars (Admin's Users/Organizations/Audit log/... row, and any other
     modal built the same way) used to scroll away with the body content
     under them — on a long list (audit log especially) that meant losing
     the only way to switch tabs or reach a tab-specific filter row without
     scrolling all the way back up first. Sticks right below the header
     (`top` = the header's own rendered height, 51px) as its own band. */
  .reqmodal:not(.cmdk-modal):not(.pdf-viewer-box):not(.monitoring-shell):not(.grpc-shell):not(.uptime-shell):not(.split-shell) > .tabs {
    position: sticky; top: 36px; z-index: 1; background: inherit;
    margin: 0 -20px 12px; padding: 0 20px 10px;
  }
  /* Audit log's own user/org filter row + Refresh button sticks to the top
     of the scrolling content pane (`.split-body`), covering its padding. */
  #admin-tab-audit > .row:first-child {
    position: sticky; top: -18px; z-index: 1; background: var(--surface);
    margin: -18px -22px 8px; padding: 10px 22px; border-bottom: 1px solid var(--color-border);
  }
  :root[data-theme="light"] #admin-tab-audit > .row:first-child { border-color: var(--color-border); }
  .reqmodal:not(.cmdk-modal):not(.pdf-viewer-box):not(.monitoring-shell):not(.grpc-shell):not(.uptime-shell):not(.split-shell) > .close-row {
    position: sticky; bottom: -20px; z-index: 1;
    margin: 16px -20px -20px; padding: 12px 20px 20px; background: inherit;
  }
  /* Net speed modal: deliberately darker/more rounded than the app's usual
     modal chrome, per its own design spec (slate-900/800, rounded-2xl). */
  #netspeed-modal .reqmodal { background: #0f172a; border-radius: 16px; border: 1px solid #334155; }
  #netspeed-modal .data-table { border: 1px solid #334155; border-radius: 8px; overflow: hidden; }
  #netspeed-modal .data-table th, #netspeed-modal .data-table td { border-color: #334155; background: #1e293b; }
  #netspeed-modal .data-table thead th { background: #1e293b; }
  #netspeed-modal #netspeed-target-select { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; border-radius: 8px; }
  #netspeed-modal #netspeed-start-btn { border-radius: 8px; }
  .reqmodal.modal-xwide { width: 1080px; max-width: 96vw; max-height: 86vh; }
  .settings-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; }
  @media (max-width: 640px) {
    .settings-2col { grid-template-columns: 1fr; gap: 14px 0; }
  }
  /* Webhook/import-export rows use `.row` (flex) with fixed-flex inputs —
     on narrow phones two `flex:1` inputs side by side get crushed below a
     usable tap width, so let them wrap into a stack instead. */
  @media (max-width: 480px) {
    #settings-modal .row { flex-wrap: wrap; }
    #settings-modal .row > input, #settings-modal .row > select, #settings-modal .row > button { flex: 1 1 100%; }
    #settings-modal .hint-box code { word-break: break-word; }
  }
  /* Same crush as settings' webhook row (see above) — admin's Match & Replace,
     Blocked targets, Trash retention, Rate limits, and System rows all mix
     fixed-width inputs/selects on one `.row`, plus its 8-item tab bar and
     data-tables need to stay usable at phone widths. */
  /* Admin jadval ustun kengliklari + matn o'ralishi quyida (2300-atrofda,
     .admin-users-table / .admin-audit-table) — colgroup bilan boshqariladi. */
  #admin-modal .data-table td { white-space: nowrap; }
  #admin-modal .data-table td.cell-wrap,
  #admin-modal .data-table .actions-cell { white-space: normal; }
  @media (max-width: 480px) {
    #admin-modal .row > input, #admin-modal .row > select, #admin-modal .row > button { flex: 1 1 100%; width: auto; }
    #admin-modal .tab { font-size: 12px; padding: 6px 8px; }
  }
  .pdf-viewer-box { width: min(900px, 92vw); height: min(90vh, 1000px); display: flex; flex-direction: column; }
  .pdf-viewer-frame { flex: 1; border: 0; border-radius: 4px; background: #fff; -webkit-overflow-scrolling: touch; min-height: 0; }
  @media (min-width: 769px) and (max-width: 1024px) {
    .pdf-viewer-box { width: min(94vw, 900px); height: min(88vh, 1000px); }
  }
  @media (max-width: 768px) {
    /* Full-screen sheet instead of the generic bottom-sheet cap (88vh) —
       a PDF needs all the vertical room it can get on a phone; a partial
       sheet just adds an extra scroll boundary around the iframe's own.
       !important: `.pdf-viewer-box` alone (specificity 0,0,1,0) loses to
       the generic `.modal-overlay:not(#cmdk-modal) .reqmodal` bottom-sheet
       rule (0,1,2,0 — the :not(#id) counts as an id) on these same
       properties regardless of source order, so they need to win outright. */
    .pdf-viewer-box { height: 100dvh; max-height: 100dvh !important; width: 100vw !important; max-width: 100vw !important; border-radius: 0 !important; padding-bottom: max(12px, env(safe-area-inset-bottom)) !important; }
    .pdf-viewer-box .modal-header { flex-wrap: wrap; gap: 6px; }
    .pdf-viewer-box .modal-header h2 { font-size: 15px; }
  }
  /* Command palette: top-aligned (not centered) and no title/padding above
     the search field, matching the usual Ctrl+K UX rather than a form modal. */
  #cmdk-modal { align-items: flex-start; padding-top: 12vh; }
  .reqmodal.cmdk-modal { width: 560px; max-width: 90vw; padding: 0; overflow: hidden; }
  .cmdk-modal .modal-header { padding: 10px 14px; border-bottom: 1px solid var(--color-border); gap: 8px; }
  #cmdk-input { background: transparent; border: none; padding: 4px 0; }
  #cmdk-input:focus { outline: none; }
  #cmdk-results { max-height: 55vh; overflow-y: auto; padding: 6px; }
  .cmdk-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
  .cmdk-item.active, .cmdk-item:hover { background: var(--color-selected-bg); }
  .cmdk-item-icon { width: 20px; flex-shrink: 0; text-align: center; font-size: 13px; }
  .cmdk-item-icon .method-badge { width: auto; font-size: 9px; }
  .cmdk-item-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cmdk-item-subtitle { color: var(--text-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%; flex-shrink: 0; }
  :root[data-theme="light"] .cmdk-modal .modal-header { border-color: var(--color-border); }
  .ws-log { height: 280px; overflow-y: auto; margin-top: 8px; padding: 8px 10px; background: var(--surface-void); border: 1px solid #333; border-radius: 4px; font-family: monospace; font-size: 13px; line-height: 1.5; }
  /* :empty rather than a JS-managed placeholder — clears itself the instant
     the first real line is appended, no extra bookkeeping needed. */
  .ws-log:empty::before { content: attr(data-empty-hint); display: block; font-family: inherit; color: var(--color-text-muted); padding-top: 4px; }
  /* URL + Connect merged into one visual input-group: shared border, no gap
     or radius break at the seam. */
  .ws-url-group { display: flex; }
  .ws-url-group input { border-radius: 4px 0 0 4px; border-right: none; }
  .ws-url-group button { border-radius: 0 4px 4px 0; flex-shrink: 0; }
  #ws-test-message { border: 1px solid var(--border); }
  .ws-log-line { padding: 1px 0; white-space: pre-wrap; word-break: break-all; }
  .ws-log-sent { color: var(--color-primary-2); }
  .ws-log-received { color: #9fd88c; }
  .ws-log-system { color: var(--text-muted); }
  .ws-log-error { color: #ff9c9c; }
  :root[data-theme="light"] .ws-log { background: var(--color-surface-2); border-color: var(--color-border); }
  .reqmodal h2 { margin-top: 0; font-size: 16px; }
  .modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
  .modal-header h2 { flex: 1; }
  .modal-x { background: transparent; border: none; line-height: 1; padding: 6px; color: var(--modal-x-color); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; }
  .modal-x:hover, .modal-x:focus-visible { color: var(--modal-x-hover-color); background: var(--modal-x-hover-bg); }
  .reqmodal h3 { font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); margin: 16px 0 6px; }
  .reqmodal .close-row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }

  #request-tabs-bar { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); padding-bottom: var(--space-2); border-bottom: 1px solid var(--color-border); }
  #request-tabs-scroll-outer { position: relative; flex: 1; min-width: 0; }
  /* Left/right edge fades only made sense for horizontal scroll — the list
     below shrinks its tabs and folds overflow into #tabs-overflow-btn
     instead now, so these are permanently hidden rather than deleted
     outright (keeps updateRequestTabsFade() in topbar.js a harmless no-op
     instead of needing its own removal). */
  .request-tabs-fade { display: none; }
  /* Neither wrap nor scroll — a single fixed-height row, always. Past
     tabOverflowThreshold render units, tabs.js itself stops rendering more
     of them and folds the rest into #tabs-overflow-btn's "+N more" dropdown
     instead, so this row's width need never grow past its container. Tabs
     between min/max-width shrink to fit whatever's left (flex-shrink now
     allowed, was 0) down to 90px — icon+short name+close still readable —
     before the overflow button would ever need to kick in at the default
     threshold of 12. */
  #request-tabs-list { display: flex; align-items: center; flex-wrap: nowrap; gap: var(--space-1); flex: 1; min-width: 0; overflow: hidden; }
  .request-tab { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md) var(--radius-md) 0 0; background: transparent; border: 1px solid transparent; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer; font-size: 13px; flex: 1 1 150px; min-width: 90px; max-width: 260px; color: var(--text-muted); transition: border-bottom-color 0.15s ease; }
  .request-tab:hover { background: var(--surface); color: var(--color-text); }
  .request-tab.active { background: var(--surface); border-bottom-color: var(--btn-primary-bg); color: var(--color-text); font-weight: 600; }
  .request-tab.has-tint { background: color-mix(in srgb, var(--tab-tint) 16%, transparent); }
  .request-tab.has-tint:hover { background: color-mix(in srgb, var(--tab-tint) 28%, var(--surface)); }
  .request-tab.has-tint.active { background: color-mix(in srgb, var(--tab-tint) 40%, var(--surface)); }
  /* Loading: so'rov shu tab uchun jo'natilmoqda (setTabLoading, tabs.js) —
     accent border pulslaydi, spinner dirty-dot o'rnini egallaydi (yuqorida
     tabHtml'da). Tab boshqasiga o'tilsa ham to'g'ri chipda qoladi. */
  .request-tab.is-loading { border-bottom-color: var(--btn-primary-bg); animation: request-tab-loading-pulse 1.2s ease-in-out infinite; }
  @keyframes request-tab-loading-pulse { 0%, 100% { border-bottom-color: var(--btn-primary-bg); } 50% { border-bottom-color: transparent; } }
  .request-tab-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto; margin-right: var(--space-1); }
  .request-tab-dirty { width: 6px; height: 6px; border-radius: 50%; background: #e0a634; flex-shrink: 0; }
  .request-tab-group-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
  .tab-group-chip { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); background: var(--surface); border: 1px solid var(--border); cursor: pointer; font-size: 13px; flex-shrink: 0; color: var(--text-muted); }
  .tab-group-chip:hover { color: var(--color-text); border-color: var(--color-border-strong); }
  .tab-group-chip.active { border-color: var(--btn-primary-bg); color: var(--color-text); }
  .tab-group-chip-name { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tab-group-chip-count { font-size: 11px; opacity: .65; background: var(--color-border); border-radius: 10px; padding: 1px var(--space-2); }
  :root[data-theme="light"] .tab-group-chip { background: #eef0f3; border-color: var(--color-border-strong); }
  :root[data-theme="light"] .tab-group-chip.active { background: var(--color-selected-bg); color: var(--color-text); }
  :root[data-theme="light"] .tab-group-chip-count { background: #e5e7eb; }
  /* Pin qilingan tab: tor, faqat method badge + pin belgisi ko'rinadi,
     nom va yopish tugmasi hover'da/aktivda chiqadi — brauzer tab
     konvensiyasi (ko'p tab ochilganda joy tejaydi). */
  .request-tab.pinned { flex: 0 0 auto; min-width: 0; max-width: 120px; }
  .request-tab.pinned .request-tab-name { display: none; }
  .request-tab.pinned:hover .request-tab-name, .request-tab.pinned.active .request-tab-name { display: block; }
  .request-tab.pinned .request-tab-close { display: none; }
  .request-tab.pinned:hover .request-tab-close { display: flex; }
  .request-tab-pin { font-size: 10px; line-height: 1; flex-shrink: 0; }
  /* Drag-reorder: sudralayotgan tab so'niq, drop nishoni chap chekkasida
     accent chiziq (element o'sha joyga surilishini bildiradi). */
  .request-tab.dragging { opacity: .45; }
  .request-tab.drag-over { box-shadow: inset 2px 0 0 0 var(--btn-primary-bg); }
  .request-tab-close { background: transparent; border: none; color: var(--text-muted); font-size: 15px; line-height: 1; width: 22px; height: 22px; padding: 0; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
  .request-tab-close:hover { color: var(--btn-color); background: var(--surface-3); }
  #new-tab-btn { display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-md); width: 36px; height: 36px; font-size: 18px; flex-shrink: 0; }
  .round-add-btn { border-radius: 50%; width: 26px; height: 26px; padding: 0; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; }
  .round-add-btn--primary { background: var(--btn-primary-bg); border-color: var(--btn-primary-bg); color: var(--btn-primary-color); }
  .round-add-btn--primary:hover { background: var(--btn-primary-hover-bg); }
  :root[data-theme="light"] .request-tab:hover { background: var(--surface-3); color: var(--color-text); }
  :root[data-theme="light"] .request-tab.active { background: var(--surface-3); border-bottom-color: var(--btn-primary-bg); color: var(--color-text); }
  :root[data-theme="light"] .request-tab.has-tint:hover { background: color-mix(in srgb, var(--tab-tint) 28%, var(--surface-3)); }
  :root[data-theme="light"] .request-tab.has-tint.active { background: color-mix(in srgb, var(--tab-tint) 40%, var(--surface-3)); }
  :root[data-theme="light"] .request-tab-close:hover { color: var(--color-text); background: var(--color-border); }
  :root[data-theme="light"] #request-tabs-bar { border-bottom-color: var(--color-border); }

  #flows-view { display: none; position: fixed; inset: 0; background: var(--surface-void); z-index: 300; flex-direction: column; }
  #flows-view.active { display: flex; }
  #flows-view-header { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--color-border); flex-shrink: 0; }
  /* Stronger border than the plain default input — this field sits in a
     row of icon buttons and a select, all similarly muted, and read as
     just another low-contrast control instead of the primary "name your
     flow" input. */
  #new-flow-name { border-color: var(--color-primary); }
  :root[data-theme="light"] #new-flow-name { border-color: var(--color-primary); }
  .flow-stop-on-error-label { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-muted); cursor: pointer; user-select: none; white-space: nowrap; }
  .flow-stop-on-error-label input { cursor: pointer; }
  #flows-view-body { flex: 1; display: flex; min-height: 0; position: relative; }
  #flow-palette { width: 220px; flex-shrink: 0; border-right: 1px solid var(--color-border); padding: 10px; overflow-y: auto; }
  #flow-palette-backdrop { display: none; }
  #flow-palette h3 { font-size: 11px; text-transform: uppercase; color: var(--text-muted); margin: 0 0 8px; }
  #flow-palette-search { width: 100%; margin-bottom: 8px; font-size: 12px; padding: 5px 8px; }
  #flow-palette-method-filter { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
  .method-chip { font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 10px; border: 1px solid var(--border); background: transparent; cursor: pointer; }
  .method-chip.active { background: var(--color-selected-bg); border-color: var(--color-primary); }
  .flow-palette-group-header { padding: 5px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 600; color: var(--text-secondary); display: flex; gap: 4px; align-items: center; user-select: none; }
  .flow-palette-group-header:hover { background: var(--surface-3); }
  .flow-palette-group-header .caret { display: inline-block; transition: transform 0.1s; font-size: 9px; width: 10px; }
  .flow-palette-group-header.collapsed .caret { transform: rotate(-90deg); }
  .flow-palette-group-body.collapsed { display: none; }
  .flow-palette-item { padding: 6px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; display: flex; gap: 6px; align-items: center; }
  .flow-palette-item:hover { background: var(--surface-3); }
  .flow-palette-item.indent-folder { padding-left: 32px; }
  .flow-palette-group-header.indent-folder { padding-left: 20px; }
  .flow-palette-category-header { padding: 6px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); display: flex; gap: 4px; align-items: center; user-select: none; margin-top: 6px; }
  .flow-palette-category-header:hover { background: var(--surface-3); }
  .flow-palette-category-header .caret { display: inline-block; transition: transform 0.1s; font-size: 9px; width: 10px; }
  .flow-palette-category-header.collapsed .caret { transform: rotate(-90deg); }
  .flow-palette-category-body.collapsed { display: none; }
  .flow-palette-node-card { display: flex; gap: 8px; align-items: flex-start; padding: 7px 8px; border-radius: 6px; cursor: pointer; }
  .flow-palette-node-card:hover { background: var(--surface-3); }
  .flow-palette-node-card.disabled { opacity: 0.4; cursor: not-allowed; }
  .flow-palette-node-icon { flex-shrink: 0; width: 22px; height: 22px; border-radius: 5px; background: var(--surface-3); display: flex; align-items: center; justify-content: center; color: var(--color-primary-2); }
  .flow-palette-node-text { min-width: 0; }
  .flow-palette-node-title { font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .flow-palette-node-desc { font-size: 10.5px; color: var(--text-muted); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #flow-canvas-outer { flex: 1; position: relative; min-width: 0; }
  /* Centered over the dotted canvas instead of a small line of text tucked
     into the left palette — the canvas itself is the dominant visual
     element when no flow is open, so the "what do I do here" prompt should
     live where the eye actually lands. */
  #flow-canvas-empty {
    position: absolute; inset: 0; z-index: 5; display: none; flex-direction: column; align-items: center;
    justify-content: center; gap: 12px; text-align: center; pointer-events: none; color: var(--color-text-muted);
  }
  #flow-canvas-empty.visible { display: flex; }
  :root[data-theme="light"] #flow-canvas-empty { color: var(--color-text-muted); }
  /* Postman Flows uslubidagi boshlang'ich sahna: "Start" kartochkasi +
     egri rangli konnektorlar + 4 yaratish varianti. */
  .flow-start-scene { display: flex; align-items: center; gap: 0; pointer-events: auto; }
  .flow-start-card { width: 190px; background: var(--color-surface); border: 2px solid var(--color-border); border-left: 3px solid var(--color-primary); border-radius: 10px; text-align: left; flex-shrink: 0; }
  .flow-start-card-head { display: flex; align-items: center; gap: 6px; padding: 8px 10px; border-bottom: 1px solid var(--color-border); font-weight: 700; font-size: 12px; color: var(--color-text); }
  .flow-start-card-input { display: flex; align-items: center; gap: 5px; padding: 8px 10px; font-size: 11px; color: var(--color-text-muted); }
  .flow-start-connectors { width: 88px; height: 220px; flex-shrink: 0; overflow: visible; }
  .flow-start-link { fill: none; stroke-width: 2; }
  .flow-start-link-1 { stroke: var(--flow-start-link-a); }
  .flow-start-link-2 { stroke: var(--flow-start-link-b); }
  .flow-start-link-3 { stroke: var(--flow-start-link-c); }
  /* 4-konnektor "Generate with AI" varianti bilan birga — u hozircha
     yashirin, shuning uchun default holatda ko'rinmaydi. */
  .flow-start-link-4 { stroke: var(--flow-start-link-d); display: none; }
  #flow-canvas-empty:has([data-flow-start="ai-generate"]:not([hidden])) .flow-start-link-4 { display: inline; }
  .flow-start-options { display: flex; flex-direction: column; gap: 12px; }
  .flow-start-option { display: flex; align-items: center; gap: 8px; width: 210px; padding: 9px 12px; font-size: 12px; font-weight: 600; text-align: left; background: var(--color-surface); border: 1.5px solid var(--color-border); border-radius: 8px; color: var(--color-text); cursor: pointer; transition: border-color 0.12s, background 0.12s; }
  .flow-start-option:hover { background: var(--color-selected-bg); }
  .flow-start-option[hidden] { display: none; }
  .flow-start-option-1 { border-left: 3px solid var(--flow-start-link-a); }
  .flow-start-option-1 svg { color: var(--flow-start-link-a); }
  .flow-start-option-2 { border-left: 3px solid var(--flow-start-link-b); }
  .flow-start-option-2 svg { color: var(--flow-start-link-b); }
  .flow-start-option-3 { border-left: 3px solid var(--flow-start-link-c); }
  .flow-start-option-3 svg { color: var(--flow-start-link-c); }
  .flow-start-option-4 { border-left: 3px solid var(--flow-start-link-d); }
  .flow-start-option-4 svg { color: var(--flow-start-link-d); }
  @media (max-width: 640px) {
    .flow-start-connectors { display: none; }
    .flow-start-scene { flex-direction: column; gap: 16px; }
  }
  #flow-canvas-wrap { position: absolute; inset: 0; overflow: auto; scrollbar-width: none; background-image: radial-gradient(circle, var(--color-border) 1px, transparent 1px); background-size: 22px 22px; cursor: grab; }
  #flow-canvas-wrap::-webkit-scrollbar { display: none; }
  #flow-canvas-wrap.panning { cursor: grabbing; }
  #flow-canvas-world { position: absolute; top: 0; left: 0; width: 3000px; height: 2000px; transform-origin: 0 0; }
  #flow-edges-svg { position: absolute; top: 0; left: 0; width: 3000px; height: 2000px; pointer-events: none; }
  /* Shown only with 2+ nodes selected (see applyFlowSelectionClasses) —
     align/distribute the selection, same idea as the toolbar Postman
     Flows shows for a multi-selection, built with this app's own
     surface/border/accent tokens rather than copying that chrome. */
  #flow-align-toolbar { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 6; display: flex; gap: 2px; align-items: center; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 4px; box-shadow: 0 4px 14px rgba(0,0,0,0.3); }
  #flow-align-toolbar.hidden { display: none; }
  #flow-align-toolbar button { width: 28px; height: 28px; padding: 0; display: flex; align-items: center; justify-content: center; background: transparent; border: none; color: var(--text-muted); border-radius: 5px; }
  #flow-align-toolbar button:hover { background: var(--surface-3); color: var(--text-secondary); }
  .flow-align-sep { width: 1px; height: 18px; background: var(--border); margin: 0 2px; }
  #flow-zoom-controls { position: absolute; bottom: 12px; left: 12px; z-index: 5; display: flex; gap: 4px; align-items: center; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 4px; }
  #flow-zoom-controls .icon-btn { width: 26px; height: 26px; font-size: 15px; }
  #flow-zoom-label { font-size: 11px; color: var(--text-muted); width: 38px; text-align: center; flex-shrink: 0; }
  #flow-minimap { position: absolute; top: 12px; right: 12px; z-index: 5; width: 170px; height: 110px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; touch-action: none; }
  #flow-minimap.hidden { display: none; }
  #flow-minimap-nodes { position: absolute; inset: 0; }
  .flow-minimap-node { position: absolute; background: var(--color-primary); border-radius: 1px; min-width: 3px; min-height: 2px; }
  #flow-minimap-viewport { position: absolute; border: 1px solid var(--color-primary-2); background: rgba(94, 235, 160, 0.15); pointer-events: none; }
  #flow-console-drawer { position: absolute; left: 0; right: 0; bottom: 0; height: 280px; background: var(--surface); border-top: 1px solid var(--border); display: flex; flex-direction: column; transform: translateY(100%); transition: transform 0.2s ease; z-index: 10; }
  #flow-console-drawer.open { transform: translateY(0); }
  #flow-console-header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--color-border); font-size: 12px; font-weight: 600; color: var(--text-secondary); flex-shrink: 0; }
  #flow-console-log { flex: 1; margin: 8px 12px 12px; height: auto; }
  #flow-run-history-drawer { position: absolute; right: 0; top: 0; bottom: 0; width: min(340px, 90vw); background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; transform: translateX(100%); transition: transform 0.2s ease; z-index: 10; }
  #flow-run-history-drawer.open { transform: translateX(0); }
  #flow-run-history-header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--color-border); font-size: 12px; font-weight: 600; color: var(--text-secondary); flex-shrink: 0; }
  #flow-run-history-list { flex: 1; overflow-y: auto; padding: 8px 12px 12px; font-size: 12px; }
  .flow-run-item { border: 1px solid var(--border); border-radius: 6px; padding: 8px; margin-bottom: 8px; }
  .flow-run-item-top { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
  .flow-run-status-badge { font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; }
  .flow-run-status-badge.success { background: rgba(52,199,89,0.15); color: #34c759; }
  .flow-run-status-badge.failed { background: rgba(255,69,58,0.15); color: #ff453a; }
  .flow-run-status-badge.error { background: rgba(255,159,10,0.15); color: #ff9f0a; }
  .flow-run-item-meta { color: var(--text-muted); font-size: 11px; }
  :root[data-theme="light"] #flow-run-history-drawer { background: var(--surface); border-color: var(--color-border-strong); }
  :root[data-theme="light"] #flow-run-history-header { border-color: var(--color-border); }
  .flow-console-body { margin: 4px 0 4px 14px; }
  .flow-console-body summary { cursor: pointer; color: var(--color-primary-2); }
  .flow-console-body pre { margin: 4px 0 0; white-space: pre-wrap; word-break: break-all; }
  .flow-edge-path { stroke: var(--color-primary-2); stroke-width: 2.5; fill: none; pointer-events: stroke; cursor: pointer; transition: stroke 150ms ease-out; }
  .flow-edge-path:hover { stroke: #7ff0b0; stroke-width: 3.5; }
  .flow-edge-path.branch-true { stroke: #7fdba0; }
  .flow-edge-path.branch-false { stroke: #ff9c9c; }
  .flow-edge-path.branch-success { stroke: #7fdba0; }
  .flow-edge-path.branch-fail { stroke: #ff9c9c; }
  /* Data flow holati - ma'lumot uzatilayotgan tugunga kiruvchi chiziq rangi. */
  .flow-edge-path.flow-running { stroke: var(--color-primary); stroke-width: 3; stroke-dasharray: 6 6; animation: flowEdgeDash 700ms linear infinite; }
  .flow-edge-path.flow-pass { stroke: #4ade80; }
  .flow-edge-path.flow-fail { stroke: #f87171; }
  @keyframes flowEdgeDash { to { stroke-dashoffset: -24; } }
  .flow-edge-rubber { stroke: var(--color-primary-2); stroke-width: 2; fill: none; stroke-dasharray: 5 4; }
  .flow-node { position: absolute; width: 200px; background: var(--surface); border: 2px solid var(--border); border-radius: 10px; padding: 0; cursor: move; user-select: none; touch-action: none; }
  /* Postman Flows uslubidagi header/tana ajratmasi — ikon+sarlavha bir
     qatorda, tagida ingichka chiziq; tana matni pastda. */
  .flow-node-header { display: flex; align-items: center; gap: 6px; padding: 7px 10px; border-bottom: 1px solid var(--color-border); }
  .flow-node-header .flow-node-title { margin: 0; flex: 1; min-width: 0; }
  .flow-node-body { padding: 7px 10px; }
  .flow-node-body:empty { display: none; }
  /* Chiqish portlari o'ng qirrada, vertikal markazda stack bo'lib turadi.
     Har port qatori: yorliq (karta ichida) + nuqta (qirrada). */
  .flow-node-ports { position: absolute; right: 0; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 6px; align-items: flex-end; z-index: 3; }
  .flow-port { position: relative; display: flex; align-items: center; gap: 6px; padding-right: 12px; }
  .flow-port-label { font-size: 9.5px; font-weight: 600; letter-spacing: 0.2px; color: var(--flow-port-label); background: var(--flow-node-body-bg); padding: 1px 5px; border-radius: 4px; white-space: nowrap; }
  .flow-port-dot { position: absolute; right: -8px; top: 50%; }
  .flow-node.running { border-color: #e0a634; background: #201d16; }
  .flow-node.pass { border-color: #7fdba0; background: #152219; }
  /* A fail's error text (raw error string, sometimes a full URL + a long
     validation message) badly overflowed the node's normal 180px width —
     ugly mid-word/mid-URL wraps, no visual room. Widened just for .fail,
     paired with .flow-node-status's own overflow-wrap/line-clamp below. */
  .flow-node.fail { border-color: #ff9c9c; background: #231515; width: 260px; }
  /* Request node qaysi HTTP metodni chaqirishini node'ning o'zida ham
     (badge matnidan tashqari) ajratib ko'rsatish - chap tomonda metod rangida
     chiziq. box-shadow (border emas) ishlatiladi, chunki running/pass/fail
     holatlari .flow-node border-color'ni o'zgartiradi - ikkalasi mustaqil. */
  .flow-node.method-GET { box-shadow: inset 3px 0 0 var(--method-get); }
  .flow-node.method-POST { box-shadow: inset 3px 0 0 var(--method-post); }
  .flow-node.method-PUT { box-shadow: inset 3px 0 0 var(--method-put); }
  .flow-node.method-DELETE { box-shadow: inset 3px 0 0 var(--method-delete); }
  .flow-node.method-PATCH { box-shadow: inset 3px 0 0 var(--method-patch); }
  .flow-node.method-HEAD { box-shadow: inset 3px 0 0 var(--method-head); }
  .flow-node.skipped { opacity: 0.4; }
  .flow-node-title { font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
  .flow-node-sub { font-size: 10.5px; color: var(--text-muted); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Persistent "what this node captures" chip row — see flowNodeVarsHtml.
     Distinct from .flow-node-status (which only exists while/after a run). */
  .flow-node-vars { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; }
  .flow-node-var-chip { font-family: monospace; font-size: 9.5px; padding: 1px 5px; border-radius: 3px; background: var(--surface-3); color: var(--accent-2); }
  .flow-node-countdown { font-size: 10px; color: var(--accent, #6d8dfd); margin-top: 2px; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .flow-node.node-disabled { opacity: 0.45; }
  /* Canvas annotation — dashed border (not the solid method/status border
     every executable node uses) signals at a glance that this one isn't
     part of the run graph. Wider than a normal 180px node since it's
     meant to hold a sentence or two, not a one-line title. */
  .flow-node.flow-node-text { width: 220px; background: var(--surface-2); border: 1.5px dashed var(--border); cursor: default; padding: 8px 10px; }
  .flow-node-text-input {
    width: 100%; min-height: 60px; resize: vertical; background: transparent; border: none; color: var(--text-secondary);
    font-family: inherit; font-size: 12.5px; line-height: 1.5; padding: 0; cursor: text;
  }
  .flow-node-text-input:focus { outline: none; }
  .flow-node-text-input::placeholder { color: var(--text-muted); }
  .node-type-badge.t-disabled { background: #5a5f6a; color: #fff; margin-left: 4px; }
  .flow-node-config-btn { position: absolute; top: -9px; left: -9px; width: 20px; height: 20px; border-radius: 50%; background: var(--surface-3); color: var(--text-secondary); border: 1px solid var(--border); line-height: 1; cursor: pointer; display: none; align-items: center; justify-content: center; padding: 0; font-size: 11px; }
  .flow-node:hover .flow-node-config-btn { display: flex; }
  @media (hover: none) { .flow-node-config-btn { display: flex; } }
  .node-type-badge { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 3px; flex-shrink: 0; }
  .node-type-badge.t-if { background: #2b2242; color: #b28fff; }
  .node-type-badge.t-loop { background: #1e2b42; color: #7fb8ff; }
  .node-type-badge.t-delay { background: #2e2618; color: #e0b45a; }
  .node-type-badge.t-trigger { background: #1a3324; color: #5adf8a; }
  .node-type-badge.t-subflow { background: #1c2438; color: #7fa8ff; }
  .node-type-badge.t-evaluate { background: var(--color-selected-bg); color: var(--syntax-boolean); }
  .node-type-badge.t-display { background: var(--color-surface-3); color: var(--color-text-secondary); }
  /* evaluate: node-ichi kod editori; display: test natijalari — ikkalasi
     ham keng karta. */
  .flow-node.flow-node-evaluate, .flow-node.flow-node-display-node { width: 300px; }
  /* Chiqish port yorliqlari o'ng qirrada — evaluate/display tanasiga
     o'ng bo'shliq qo'shib ustma-ust tushmasligini ta'minlaymiz. */
  .flow-node-evaluate .flow-node-body, .flow-node-display-node .flow-node-body { padding-right: 58px; }
  .flow-node-code {
    width: 100%; min-height: 78px; resize: vertical; margin-top: 5px;
    background: var(--flow-node-code-bg); border: 1px solid var(--color-border); border-radius: 5px;
    color: var(--color-text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px; line-height: 1.45; padding: 6px 7px; cursor: text;
  }
  .flow-node-code:focus { outline: none; border-color: var(--color-focus-ring); }
  .flow-node-display { margin-top: 5px; max-height: 160px; overflow-y: auto; }
  .flow-display-value { margin: 4px 0 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; line-height: 1.4; white-space: pre-wrap; word-break: break-all; color: var(--color-text-secondary); background: var(--flow-node-code-bg); border-radius: 4px; padding: 5px 6px; }
  .flow-test-empty { font-size: 10.5px; color: var(--color-text-muted); margin-top: 4px; font-style: italic; }
  .flow-test-head { font-size: 10.5px; font-weight: 700; color: var(--color-text-secondary); margin: 4px 0 3px; }
  .flow-test-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; font-size: 10.5px; }
  .flow-test-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text); }
  .flow-test-pill { flex-shrink: 0; font-size: 8.5px; font-weight: 700; letter-spacing: 0.3px; padding: 1px 5px; border-radius: 4px; text-transform: uppercase; }
  .flow-test-pill.pass { background: var(--flow-test-pass-bg); color: var(--flow-test-pass-fg); }
  .flow-test-pill.fail { background: var(--flow-test-fail-bg); color: var(--flow-test-fail-fg); }
  /* Node config canvas ustida qalqib chiqadigan modal emas, o'ng tomondan
     ochiluvchi doimiy drawer - orqadagi tugunlarni to'smaydi, canvas bilan
     ishlashni davom ettirish mumkin (qarang #flow-run-history-drawer'ning
     xuddi shu patterni). */
  /* #flow-node-config-popup DOM'da #flows-view'ning tashqarisida (sibling,
     index.html) - shu sababli position:fixed kerak (#flows-view'ning
     ichidagi containing block emas) va z-index #flows-view'ning 300'idan
     baland bo'lishi shart, aks holda flows-view uni bosib qoladi. */
  #flow-node-config-popup { position: fixed; right: 0; top: 0; bottom: 0; z-index: 310; width: min(340px, 90vw); max-width: 90vw; overflow-y: auto; background: var(--surface); border-left: 1px solid var(--border); box-shadow: -6px 0 20px rgba(0,0,0,0.25); padding: 12px; box-sizing: border-box; transform: translateX(100%); transition: transform 0.2s ease; }
  #flow-node-config-popup.active { transform: translateX(0); }
  #flow-node-config-body .row { margin-bottom: 8px; }
  #flow-node-config-body input, #flow-node-config-body select { width: 100%; }
  #flow-node-config-body label { display: block; font-size: 11px; color: var(--text-muted); margin-bottom: 3px; }
  /* Input yonidagi "!" hint icon — hover/focus da tooltip title chiqadi. */
  .field-hint { display: inline-flex; align-items: center; justify-content: center; width: 13px; height: 13px; margin-left: 4px; border-radius: 50%; background: var(--border, #2a2f3a); color: var(--text-muted); font-size: 9px; font-weight: 700; font-style: normal; cursor: help; vertical-align: middle; }
  #flow-node-config-body label, #flow-node-config-common label { display: flex; align-items: center; }
  /* Node almashganda content chapdan o'ngga sirg'alib kirishi ("chaqmoq" effekti) - 200ms, tez lekin sekin emas. */
  @keyframes flowNodeCfgSlideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
  #flow-node-config-common.flow-node-cfg-anim, #flow-node-config-body.flow-node-cfg-anim { animation: flowNodeCfgSlideIn 200ms ease-out; }
  /* Tablet: kenglikni ekranga moslashtirish, min-width'ni kamaytirish. */
  @media (max-width: 1024px) {
    #flow-edge-popup { min-width: 240px; max-width: min(360px, calc(100vw - 16px)); }
    #flow-node-config-popup { width: min(300px, 90vw); }
  }
  /* Mobile: edge popup - node yonidagi popup emas, ekran tagidan chiquvchi
     bottom-sheet; node config drawer esa to'liq kenglikda o'ngdan keladi. */
  @media (max-width: 640px) {
    #flow-edge-popup {
      left: 0 !important; right: 0; bottom: 0; top: auto !important;
      width: 100%; min-width: 0; max-width: 100vw; max-height: 85vh;
      border-radius: 12px 12px 0 0; box-sizing: border-box;
    }
    #flow-node-config-popup { width: 100%; max-width: 100vw; }
  }
  /* Real-time per-node execution detail during a flow run — not just the
     border color, but what's actually happening (running/status/timing/error
     or which variables it captured for the next node), updated live from the
     flow-run websocket's node_started/node_done messages. */
  .flow-node-status { font-size: 11px; margin-top: 4px; min-height: 14px; color: var(--text-muted); display: flex; align-items: center; gap: 5px; overflow-wrap: anywhere; }
  .flow-node.running .flow-node-status { color: #e0a634; }
  .flow-node.pass .flow-node-status { color: #7fdba0; }
  /* Long error strings (a full request URL + a validation message) clamp to
     3 lines instead of running the node's height out indefinitely — the
     full text is still on the element's `title` (see flows.js), so nothing
     is actually lost, just not all shown inline. */
  .flow-node.fail .flow-node-status { color: #ff9c9c; align-items: flex-start; }
  .flow-node.fail .flow-node-status span:last-child {
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  }
  .mini-spinner { width: 10px; height: 10px; flex-shrink: 0; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; animation: mini-spin 0.6s linear infinite; }
  @keyframes mini-spin { to { transform: rotate(360deg); } }
  .flow-node-handle { position: absolute; right: -8px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; border-radius: 50%; background: var(--color-primary); border: 2px solid var(--surface-void); cursor: crosshair; transition: transform 0.1s ease, background 0.1s ease; z-index: 2; touch-action: none; }
  .flow-node-handle:hover { transform: translateY(-50%) scale(1.35); background: var(--color-primary-2); }
  /* Left handle marks the incoming/target side — clicking it (or the node
     body) while a connection is pending connects the two dots. */
  .flow-node-handle-left { left: -8px; right: auto; cursor: pointer; background: var(--border); }
  .flow-node-handle-left:hover { transform: translateY(-50%) scale(1.35); background: var(--color-primary-2); }
  .flow-node.linking { border-color: #e0a634; box-shadow: 0 0 0 2px rgba(224,166,52,0.4); }
  .flow-node.linking .flow-node-handle:not(.flow-node-handle-left) { background: #e0a634; box-shadow: 0 0 0 4px rgba(224,166,52,0.25); }
  /* Ulanish mavjud bo'lgan tomonning nuqtasi rangi mos keladi - output
     ulangan bo'lsa ko'k, input ulangan bo'lsa ham xuddi shu ko'k (bog'langan
     holatni bildiradi), ulanmagan holat neytral kulrang/ko'k qoladi. */
  .flow-node-handle.connected { background: var(--color-primary); }
  .flow-node-handle-left.connected { background: var(--color-primary); }
  .flow-node.selected { border-color: var(--color-primary-2); box-shadow: 0 0 0 2px rgba(94,235,160,0.5); }
  .flow-node-delete { position: absolute; top: -9px; right: -9px; width: 22px; height: 22px; border-radius: 50%; background: #2e1c1c; color: #ff9c9c; border: none; line-height: 1; cursor: pointer; display: none; align-items: center; justify-content: center; padding: 0; }
  .flow-node:hover .flow-node-delete { display: flex; }
  @media (hover: none) { .flow-node-delete { display: flex; } }
  /* Chiziq ustida hover qilganda o'rtasida chiqadigan tezkor o'chirish
     tugmasi - popup ochmasdan bitta ulanishni uzish uchun. */
  .flow-edge-del-group { opacity: 0; pointer-events: none; transition: opacity 0.12s ease; cursor: pointer; }
  .flow-edge-group:hover .flow-edge-del-group { opacity: 1; pointer-events: auto; }
  .flow-edge-del-circle { fill: #2e1c1c; stroke: #ff9c9c; stroke-width: 1.5; }
  .flow-edge-del-group:hover .flow-edge-del-circle { fill: #ff9c9c; stroke: #2e1c1c; }
  .flow-edge-del-icon { fill: #ff9c9c; font-size: 12px; font-weight: 700; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
  .flow-edge-del-group:hover .flow-edge-del-icon { fill: #2e1c1c; }
  #flow-edge-popup { display: none; position: fixed; z-index: 310; min-width: 260px; max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); overflow-y: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,0.35); padding: 12px; }
  #flow-edge-popup.active { display: block; }
  .flow-edge-label { font-size: 10px; font-weight: 600; fill: #8a90a0; pointer-events: none; paint-order: stroke; stroke: #15171d; stroke-width: 3px; }
  .flow-edge-label.branch-true { fill: #4ec17a; }
  .flow-edge-label.branch-false { fill: #f0966b; }
  .flow-edge-label.branch-success { fill: #4ec17a; }
  .flow-edge-label.branch-fail { fill: #f0966b; }
  #flow-run-summary { display: none; position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 60; gap: 8px; padding: 6px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; box-shadow: 0 4px 14px rgba(0,0,0,0.3); }
  #flow-run-summary.active { display: flex; }
  .flow-run-summary-badge { font-size: 12px; font-weight: 700; }
  .flow-run-summary-badge.ok { color: var(--flow-summary-ok); }
  .flow-run-summary-badge.err { color: var(--flow-summary-err); }
  .flow-edge-preview { border-top: 1px solid #2c313c; margin-top: 10px; padding-top: 4px; }
  .flow-edge-preview-row { display: flex; gap: 6px; }
  .flow-edge-preview-row .form-input { flex: 1; }
  #flow-edge-test-result { font-size: 11px; margin-top: 6px; word-break: break-all; }
  #flow-edge-test-result.flow-edge-test-ok { color: var(--flow-summary-ok); }
  #flow-edge-test-result.flow-edge-test-err { color: var(--flow-summary-err); }
  :root[data-theme="light"] #flows-view { background: var(--color-surface-2); }
  :root[data-theme="light"] #flows-view-header { border-color: var(--color-border); }
  :root[data-theme="light"] #flow-palette { border-color: var(--color-border); }
  :root[data-theme="light"] .flow-palette-item:hover { background: #e6e9ed; }
  :root[data-theme="light"] .flow-palette-group-header:hover { background: var(--color-surface-3); }
  :root[data-theme="light"] .method-chip { border-color: var(--color-border-strong); }
  :root[data-theme="light"] #flow-canvas-wrap { background-image: radial-gradient(circle, var(--color-border-strong) 1px, transparent 1px); }
  :root[data-theme="light"] .flow-node { background: var(--surface); border-color: var(--color-border-strong); }
  :root[data-theme="light"] #flow-zoom-controls , :root[data-theme="light"] #flow-minimap, :root[data-theme="light"] #flow-align-toolbar { background: var(--surface); border-color: var(--color-border-strong); }
  :root[data-theme="light"] #flow-console-drawer { background: var(--surface); border-color: var(--color-border-strong); }
  :root[data-theme="light"] #flow-console-header { border-color: var(--color-border); }
  :root[data-theme="light"] .flow-node.running { background: #f0f4f8; }
  :root[data-theme="light"] .flow-node.pass { background: #eaf7ee; }
  :root[data-theme="light"] .flow-node.fail { background: #fdeaea; }
  :root[data-theme="light"] #flow-edge-popup { background: var(--surface); border-color: var(--color-border-strong); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }
  :root[data-theme="light"] #flow-node-config-popup { background: var(--surface); border-color: var(--color-border-strong); box-shadow: -6px 0 20px rgba(0,0,0,0.1); }
  :root[data-theme="light"] .flow-palette-category-header:hover { background: var(--color-surface-3); }
  :root[data-theme="light"] .flow-palette-node-card:hover { background: #e6e9ed; }
  :root[data-theme="light"] .flow-palette-node-icon { background: var(--color-surface-3); }
  :root[data-theme="light"] .flow-node-config-btn { background: var(--surface); color: var(--color-text-secondary); }
  :root[data-theme="light"] .flow-node-delete { background: #fdeaea; color: #c92a2a; }

  .admin-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 18px; }
  .admin-stat-card { background: var(--surface-3); border: 1px solid #2f333d; border-radius: 8px; padding: 12px; text-align: center; }
  .admin-stat-value { font-size: 22px; font-weight: 700; }
  .admin-stat-label { font-size: 11px; color: var(--text-muted); margin-top: 2px; text-transform: uppercase; letter-spacing: 0.3px; }
  :root[data-theme="light"] .admin-stat-card { background: var(--color-surface-2); border-color: var(--color-border); }

  /* Monitoring: full-screen dashboard shell, not a centered modal box — a
     dashboard meant to grow more sections over time needs real width, and a
     left-nav + independently-scrolling content pane is the standard shape
     for that (nothing here is specific to the two sections that exist today;
     a third is just one more nav button + one more .monitoring-section). */
  .monitoring-shell {
    width: 100vw; height: 100vh; max-width: 100vw; max-height: 100vh;
    border-radius: 0; padding: 0; display: flex; flex-direction: column; overflow: hidden;
  }
  .monitoring-shell .modal-header { padding: 14px 20px; border-bottom: 1px solid var(--color-border); flex-shrink: 0; }
  .monitoring-body { flex: 1; display: flex; min-height: 0; }
  .monitoring-nav {
    width: 200px; flex-shrink: 0; border-right: 1px solid var(--color-border);
    padding: 14px 10px; display: flex; flex-direction: column; gap: 2px; overflow-y: auto;
  }
  .monitoring-nav-item {
    text-align: left; padding: 9px 12px; border-radius: 6px; background: transparent;
    border: none; color: var(--text-muted); cursor: pointer; font-size: 13px; height: auto;
  }
  .monitoring-nav-item:hover { background: var(--surface-3); color: var(--btn-color); }
  .monitoring-nav-item.active { background: var(--surface-3); color: var(--btn-color); font-weight: 600; }
  /* Content pane scrolls on its own — the nav stays put, the shell itself
     never scrolls (overflow:hidden above), so only this one region moves. */
  .monitoring-content { flex: 1; overflow-y: auto; padding: 20px 24px 32px; min-width: 0; }
  .monitoring-section.hidden { display: none; }
  .monitoring-filter-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
  .monitoring-filter-row input, .monitoring-filter-row select { height: 32px; }
  #monitoring-filter-path { flex: 1; min-width: 160px; }
  .monitoring-filter-checkbox { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-muted); white-space: nowrap; }
  .monitoring-heatmap-header { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
  .monitoring-heatmap-header h3 { margin: 0; }
  #monitoring-heatmap-metric { height: 30px; }
  .monitoring-heatmap { overflow-x: auto; margin-bottom: 20px; }
  .monitoring-heatmap-grid {
    display: grid;
    grid-template-columns: 44px repeat(24, minmax(18px, 1fr));
    gap: 2px;
    min-width: 620px;
  }
  .monitoring-heatmap-corner { height: 20px; }
  .monitoring-heatmap-hour-label, .monitoring-heatmap-day-label {
    font-size: 10px;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .monitoring-heatmap-day-label { justify-content: flex-start; }
  .monitoring-heatmap-hour-label { height: 20px; }
  .monitoring-heatmap-cell {
    aspect-ratio: 1;
    border-radius: 2px;
    background-color: rgba(255, 255, 255, 0.06);
    cursor: pointer;
  }
  .monitoring-heatmap-cell:hover { outline: 1px solid rgba(124, 140, 255, 0.6); outline-offset: -1px; }
  :root[data-theme="light"] .monitoring-heatmap-cell { background-color: rgba(0, 0, 0, 0.05); }
  :root[data-theme="light"] .monitoring-shell .modal-header { border-color: var(--color-border); }
  :root[data-theme="light"] .monitoring-nav { border-color: var(--color-border); }
  :root[data-theme="light"] .monitoring-nav-item { color: #616b78; }
  :root[data-theme="light"] .monitoring-nav-item:hover { background: var(--color-surface-3); color: var(--color-text); }
  :root[data-theme="light"] .monitoring-nav-item.active { background: var(--surface); color: var(--color-text); }
  /* Same full-viewport shell as .monitoring-shell (see its own comment
     above) — a proto editor + service/method pickers + a JSON request on
     the left and a response that can run to any length on the right don't
     fit in a fixed-size centered modal without either clipping content or
     scrolling the whole modal (header included) to reach the response.
     Two independently-scrolling panes instead: the shell itself never
     scrolls (overflow:hidden), each pane scrolls on its own. */
  .grpc-shell {
    width: 100vw; height: 100vh; max-width: 100vw; max-height: 100vh;
    border-radius: 0; padding: 0; display: flex; flex-direction: column; overflow: hidden;
  }
  .grpc-shell .modal-header { padding: 14px 20px; border-bottom: 1px solid var(--color-border); flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; }
  .grpc-body { flex: 1; display: flex; min-height: 0; }
  /* Config pane — o'lchamli, response pane qolgan joyni oladi. Ilgari
     qat'iy 46%/54% edi: config siqilardi, response doim bo'sh turardi. */
  .grpc-config-pane {
    flex: 0 1 480px; min-width: 380px;
    border-right: 1px solid var(--color-border); padding: 18px 22px; overflow-y: auto;
    display: flex; flex-direction: column; gap: 4px;
  }
  .grpc-response-pane { flex: 1 1 0; min-width: 0; padding: 18px 22px; overflow-y: auto; }

  .grpc-hint { margin: 0 0 12px; font-size: 12px; line-height: 1.5; color: var(--color-text-muted); }
  /* Yagona label uslubi — ilgari har birida inline style="font-size:12.5px..." takrorlanardi. */
  .grpc-label { display: block; font-size: 12px; font-weight: 500; color: var(--color-text-secondary); margin: 14px 0 6px; }
  .grpc-label:first-of-type { margin-top: 4px; }
  .grpc-label-with-icon { display: flex; align-items: center; gap: 6px; }

  .field-info-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--color-surface-3); color: var(--color-text-muted);
    font-size: 10px; font-style: normal; line-height: 1;
    cursor: help; user-select: none; flex-shrink: 0;
  }
  .field-info-icon:hover, .field-info-icon:focus-visible { background: var(--color-primary); color: var(--color-primary-fg); outline: none; }

  .grpc-target-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin-bottom: 4px; }
  .grpc-target-row .input { flex: 1; min-width: 140px; }
  .grpc-tls-label { display: flex; align-items: center; gap: 6px; font-size: 12px; white-space: nowrap; }

  .grpc-code-editor { margin-bottom: 2px; }
  /* .proto va JSON xabar editorlari — matn oqib ketmasin, gorizontal
     scroll (wrap emas), shunda backdrop bilan textarea aynan mos keladi. */
  .grpc-code-editor .body-editor-backdrop, .grpc-code-editor textarea { white-space: pre; word-break: normal; overflow-wrap: normal; }
  #grpc-proto, #grpc-proto-highlight { min-height: 200px; }
  #grpc-message, #grpc-message-highlight { min-height: 110px; }

  .grpc-load-btn { align-self: flex-start; margin-top: 8px; }
  .grpc-service-method-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; margin-top: 8px; }
  .grpc-select { width: 100%; }
  .grpc-method-info { margin-top: 6px; font-size: 11px; color: var(--color-text-muted); min-height: 14px; }
  .grpc-add-meta-btn { align-self: flex-start; }

  .grpc-send-row { display: flex; align-items: center; gap: 12px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--color-border); }
  .grpc-send-status { font-size: 12px; }
  .grpc-send-status.status-err { color: var(--color-danger); }
  .grpc-send-status.status-ok { color: var(--color-success); }

  /* Bo'sh javob holati — markazda, ilgari tepa-chapda osilib turardi. */
  .grpc-response-empty {
    height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 12px; text-align: center; color: var(--color-text-muted); font-size: 13px; padding: 24px;
  }
  .grpc-response-empty svg { opacity: 0.5; }
  .grpc-response-content { display: flex; flex-direction: column; gap: 4px; }
  .grpc-response-meta { font-size: 11px; font-family: monospace; margin-bottom: 8px; white-space: pre-wrap; word-break: break-word; color: var(--color-text-muted); }
  .grpc-response-code { min-height: 0; }
  .grpc-response-body { padding: 12px; white-space: pre; overflow-x: auto; word-break: normal; font-family: monospace; font-size: 12px; line-height: 18px; margin: 0; }

  @media (max-width: 900px) {
    .grpc-body { flex-direction: column; }
    .grpc-config-pane { flex: 0 0 auto; width: auto; min-width: 0; border-right: none; border-bottom: 1px solid var(--color-border); max-height: 58vh; }
    .grpc-response-pane { flex: 1 1 auto; }
    .grpc-response-empty { min-height: 160px; height: auto; }
    .grpc-service-method-grid { grid-template-columns: 1fr; gap: 12px 0; }
  }
  @media (max-width: 480px) {
    .grpc-config-pane { padding: 14px 16px; }
    .grpc-response-pane { padding: 14px 16px; }
    .grpc-target-row .input { min-width: 100%; }
    .grpc-tls-label { min-height: 32px; }
  }

  @media (max-width: 768px) {
    .monitoring-body { flex-direction: column; }
    .monitoring-nav { width: auto; flex-direction: row; border-right: none; border-bottom: 1px solid var(--color-border); padding: 8px 10px; overflow-x: auto; }
    .monitoring-content { padding: 14px 14px 24px; }
  }

  /* System panel: two Chart.js doughnut gauges side by side, the percent
     centered over the ring via an absolutely-positioned div rather than a
     Chart.js center-text plugin — one fewer moving part for a single
     number that never needs to interact with the chart itself. */
  .monitoring-gauge-row { display: flex; gap: 24px; margin-bottom: 20px; flex-wrap: wrap; }
  .monitoring-gauge-card { flex: 1; min-width: 160px; text-align: center; }
  .monitoring-gauge-canvas-wrap { position: relative; height: 150px; }
  .monitoring-gauge-value { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 700; }
  .monitoring-gauge-label { font-size: 12px; color: var(--text-muted); margin-top: 6px; }

  .data-table { width: 100%; border-collapse: collapse; font-size: 11px; }
  .data-table th { text-align: left; padding: 5px 10px; color: var(--text-muted); font-weight: 600; font-size: 9px; text-transform: uppercase; letter-spacing: 0.3px; border-bottom: 1px solid #333; }
  .data-table td { padding: 5px 10px; border-bottom: 1px solid var(--color-border); vertical-align: middle; line-height: 1.35; }
  .data-table tbody tr:hover { background: var(--surface-3); }
  /* Amal tugmalari qatorga sig'masa keyingi qatorga o'ralsin — nowrap +
     180px qat'iy role-btn kengligi jadvalni modal doirasidan chiqarib
     yuborardi (gorizontal scroll, tugmalar ko'rinmay qolardi). */
  .data-table .actions-cell { text-align: right; white-space: normal; }
  .data-table .actions-cell { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; align-items: center; }
  .data-table .actions-cell button { font-size: 11px; padding: 3px 8px; margin: 0; flex: 0 0 auto; }
  .data-table .role-btn { min-width: 108px; text-align: center; }
  /* Jadval baribir sig'masa — o'zi ichida scroll bo'ladi, butun modal emas. */
  .data-table-scroll { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }
  .pager { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 8px 0 2px; }
  .pager-info { font-size: 11px; min-width: 44px; text-align: center; }
  .role-badge { display: inline-block; padding: 3px 9px; border-radius: 10px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; }
  .user-name-cell { display: flex; align-items: center; gap: 8px; }
  .user-avatar {
    display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
    border-radius: 50%; font-size: 11px; font-weight: 700; color: #fff; flex-shrink: 0;
    text-shadow: 0 1px 1px rgba(0,0,0,0.3);
  }
  .role-badge.admin { background: #3a3320; color: #f5d98a; }
  /* Oldingi #b7bdc9 #262b36 ustida ~3.4:1 edi (WCAG AA'dan past) — yoritildi. */
  .role-badge.user { background: #2b303c; color: #d7dce4; }
  .status-badge { display: inline-block; padding: 3px 9px; border-radius: 10px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; }
  .status-badge.approved { background: var(--badge-approved-bg); color: var(--badge-approved-fg); }
  .status-badge.pending { background: var(--badge-pending-bg); color: var(--badge-pending-fg); }
  .status-badge.rejected { background: var(--badge-rejected-bg); color: var(--badge-rejected-fg); }
  .status-badge.suspended { background: var(--badge-suspended-bg); color: var(--badge-suspended-fg); }
  .org-detail-row > td { padding: 12px 10px; background: var(--surface-recessed); }
  :root[data-theme="light"] .org-detail-row > td { background: var(--color-surface-2); }
  :root[data-theme="light"] .data-table th { border-color: var(--color-border); color: var(--color-text-muted); }
  :root[data-theme="light"] .data-table td { border-color: #e6e9ed; }
  :root[data-theme="light"] .data-table tbody tr:hover { background: #eef0f3; }

  /* ---------- Admin modal jadvallar: Users + Audit log ---------- */
  /* table-layout:fixed + colgroup foizli kengliklar — uzun email/endpoint
     ustunni cho'zib modal doirasidan chiqarmaydi. Ikkala jadval bir xil
     qator ritmi, tekislash, tipografiya. */
  #admin-modal .data-table { table-layout: fixed; min-width: 640px; border-collapse: separate; border-spacing: 0; }
  #admin-modal .data-table thead th {
    padding: 6px 12px 8px; font-size: 10px; font-weight: 700; letter-spacing: 0.5px;
    color: var(--text-muted); border-bottom: 1px solid var(--border);
  }
  #admin-modal .data-table tbody td {
    padding: 11px 12px; vertical-align: middle; line-height: 1.4;
    border-bottom: 1px solid var(--color-border);
  }
  #admin-modal .data-table tbody tr:last-child td { border-bottom: 0; }
  #admin-modal .data-table tbody tr:hover { background: var(--color-surface-2); }
  #admin-modal .data-table tr.is-self { background: color-mix(in srgb, var(--color-primary) 6%, transparent); }
  #admin-modal .data-table td.cell-wrap { white-space: normal; overflow-wrap: anywhere; word-break: normal; }
  #admin-modal .data-table td.cell-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
  #admin-modal .data-table td.cell-dim { color: var(--text-muted); font-size: 12px; }
  #admin-modal .data-table td.actions-cell { padding-right: 8px; text-align: right; white-space: nowrap; }

  /* Nom katagi: avatar + nom, vertikal markaz, uzun nom o'raladi */
  .user-name-cell { display: flex; align-items: center; gap: 9px; min-width: 0; }
  .user-name-cell .user-name-text { overflow-wrap: anywhere; font-weight: 500; }
  .user-name-cell .you-tag {
    margin-left: 4px; font-size: 10px; font-weight: 600; color: var(--text-muted);
    text-transform: lowercase; letter-spacing: 0.3px;
  }

  /* Qator amal tugmasi (kebab) — daisyUI .btn / .icon-btn'siz, toza va ixcham.
     #id prefiksi bilan .data-table .actions-cell button base qoidasidan ustun. */
  #admin-modal .row-menu-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; padding: 0; margin: 0; border: 0; border-radius: 7px;
    background: transparent; color: var(--text-muted); cursor: pointer; font-size: 0;
    transition: background 0.12s ease, color 0.12s ease;
  }
  #admin-modal .row-menu-btn:hover { background: var(--color-surface-3); color: var(--color-text); }
  #admin-modal .row-menu-btn:focus-visible { outline: 2px solid var(--color-primary-2); outline-offset: 1px; }
  #admin-modal tr:hover .row-menu-btn { color: var(--color-text); }

  .admin-users-table .col-name { width: 26%; }
  .admin-users-table .col-login { width: 24%; }
  .admin-users-table .col-role { width: 13%; }
  .admin-users-table .col-status { width: 13%; }
  .admin-users-table .col-created { width: 15%; }
  .admin-users-table .col-actions { width: 46px; }

  .admin-audit-table .col-time { width: 15%; }
  .admin-audit-table .col-user { width: 18%; }
  .admin-audit-table .col-method { width: 66px; }
  .admin-audit-table .col-endpoint { width: 38%; }
  .admin-audit-table .col-ip { width: 12%; }
  .admin-audit-table .col-status { width: 62px; }

  /* Audit method belgisi — rangli matn o'rniga nozik tinted pill (status
     belgisi bilan bir xil vizual og'irlik). currentColor = m-* rangi. */
  .admin-audit-table .method-badge {
    display: inline-block; width: auto; padding: 2px 7px; border-radius: 6px;
    font-size: 10px; letter-spacing: 0.4px;
    background: color-mix(in srgb, currentColor 15%, transparent);
  }

  /* Kichik holat pill'i (masalan "parol tiklash so'ralgan") */
  .pill {
    display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 10px;
    font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; vertical-align: middle;
  }
  .pill-warn { background: var(--badge-pending-bg); color: var(--badge-pending-fg); }

  /* Toolbar: filtr select'lari o'z yorlig'ini to'liq ko'rsatsin */
  #admin-tab-users .select, #admin-tab-audit .select { min-width: 128px; }
  .var-row { display: grid; grid-template-columns: 1fr 1fr 24px 28px 28px; gap: 6px; align-items: center; margin-bottom: 6px; min-width: 0; }
  .var-row input[type=checkbox] { margin: auto; }
  .var-row input { min-width: 0; }
  .secret-check { display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-muted); white-space: nowrap; cursor: pointer; }
  .secret-check input[type=checkbox] { margin: 0; }
  .var-row button { padding: 6px; }

  /* min-width:0 overrides the flex item's default auto min-width, which
     would otherwise be driven by the backdrop's unwrapped (white-space:pre)
     text — a long URL could force this box past the row's available width
     and wrap the Send button onto its own line. */
  .url-input-wrap { position: relative; min-width: 0; }
  /* Shown briefly while a pasted "curl ..." command is parsed into the
     request fields (see the req-url paste handler in codegen-curl.js) —
     dims the URL field and spins a small loader over it so pasting reads
     as "importing", not as "nothing happened" or "the paste got eaten". */
  .curl-paste-loading #req-url, .curl-paste-loading .url-input-backdrop { opacity: 0.35; }
  .curl-paste-loading::after {
    content: ''; position: absolute; top: 50%; right: 8px; transform: translateY(-50%);
    width: 14px; height: 14px; border: 2px solid var(--color-primary-2); border-top-color: transparent;
    border-radius: 50%; animation: mini-spin 0.6s linear infinite; pointer-events: none;
  }
  #send-btn { flex-shrink: 0; }
  .url-input-backdrop, #req-url {
    box-sizing: border-box; width: 100%; height: 32px; margin: 0; padding: 6px 8px;
    font-family: inherit; font-size: 13px; line-height: 18px; white-space: pre; overflow-x: auto; overflow-y: hidden;
    border-radius: 4px; scrollbar-width: none;
  }
  .url-input-backdrop::-webkit-scrollbar, #req-url::-webkit-scrollbar { display: none; }
  .url-input-backdrop {
    border: 1px solid #333; background: var(--surface); color: #eee; pointer-events: none; user-select: none;
  }
  #req-url {
    position: absolute; inset: 0; border: 1px solid transparent; background: transparent; color: transparent; caret-color: #eee;
  }
  #req-url::placeholder { color: #666; }
  /* {{var}} token — URL paneli, JSON/raw tana va umumiy backdrop overlay
     (.var-token, mountVarHighlight) bir xil ko'rinishda. Ranglar
     --syntax-var-* tokenlari orqali mavzuga moslashadi (semantic.css). */
  .url-var-token.resolved, .var-token.resolved { background: var(--syntax-var-resolved-bg); color: var(--syntax-var-resolved-fg); border-radius: 3px; }
  .url-var-token.unresolved, .var-token.unresolved { background: var(--syntax-var-unresolved-bg); color: var(--syntax-var-unresolved-fg); border-radius: 3px; }
  .url-query-key { color: var(--color-primary-2); font-weight: 600; }

  /* ---------- Umumiy {{var}} backdrop overlay ----------
     URL paneli va JSON tana o'z boyroq highlighter'iga ega; bu esa {{var}}
     dan boshqa sintaksisi yo'q maydonlar uchun: kv qatorlar (header/param/
     urlencoded/multipart), auth maydonlari, pre/post-request script va
     uptime custom-headers textarea. Geometriya (shrift, padding, border)
     JS'da input'ning hisoblangan uslubidan ko'chiriladi (mountVarHighlight),
     shuning uchun bu yerda faqat joylashuv skeleti. */
  .var-hl-wrap { position: relative; display: block; }
  .var-hl-backdrop {
    position: absolute; inset: 0; margin: 0; box-sizing: border-box;
    pointer-events: none; user-select: none; overflow: hidden;
    white-space: pre; color: var(--color-text);
    border-style: solid; border-color: transparent; background: transparent;
  }
  .var-hl-multiline .var-hl-backdrop { white-space: pre-wrap; word-break: break-word; }
  .var-hl-wrap > input, .var-hl-wrap > textarea { position: relative; width: 100%; margin: 0; }
  /* Faqat matnda {{var}} bo'lganda (.var-hl-active) input shaffof bo'lib
     ostidagi backdrop ko'rinadi — aks holda oddiy input, hech qanday
     geometriya nomuvofiqligi ko'rinmaydi. Selektorlar light mavzudagi
     `:root[data-theme="light"] input:not(...)` qoidasidan (0,3,2) kuchliroq
     bo'lishi kerak. */
  .var-hl-wrap.var-hl-active > input,
  .var-hl-wrap.var-hl-active > textarea {
    /* !important — light mavzudagi `:root[data-theme="light"] input:not(...)`
       (0,4,2) qoidasini har qanday selektor-specificity o'yinisiz yengish
       uchun; body editor id-selektor bilan qutuladi, bu yerda klass. */
    background: transparent !important; color: transparent !important; caret-color: var(--color-text);
  }
  .var-hl-wrap.kv-var-unresolved { outline: 1px solid var(--syntax-var-unresolved-fg); outline-offset: -1px; border-radius: 6px; }
  .var-hl-wrap.kv-var-resolved { outline: 1px solid var(--syntax-var-resolved-fg); outline-offset: -1px; border-radius: 6px; }
  /* Grid xujayrasidagi (kv/multipart qatori) wrap track balandligini to'ldirsin,
     ichidagi input esa wrap'ni — aks holda backdrop input bilan mos kelmaydi. */
  .kv-row > .var-hl-wrap, .multipart-row > .var-hl-wrap { align-self: stretch; height: 100%; }
  .kv-row > .var-hl-wrap > input, .multipart-row > .var-hl-wrap > input { height: 100%; box-sizing: border-box; }

  /* Hover popup for an unresolved {{var}} in the URL bar (see the
     mousemove listener on #req-url in collections.js) — offers to create
     the missing environment variable right there instead of making the
     user go open the environment manager, notice it's missing, and add it
     manually. */
  #url-var-hover-popup {
    position: fixed; z-index: 150; transform: translateX(-20%);
    max-width: calc(100vw - 16px);
    background: var(--surface); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 6px 20px rgba(0,0,0,0.35);
    padding: 6px 8px; font-size: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  }
  #url-var-hover-popup.hidden { display: none; }
  .url-var-hover-name { font-family: monospace; color: var(--result-fail-fg); font-weight: 700; }
  .url-var-hover-hint { color: var(--text-muted); }
  #url-var-hover-create-btn {
    background: var(--color-primary); color: #fff; border: none; border-radius: 4px; padding: 4px 8px;
    font-size: 12px; font-weight: 600; cursor: pointer;
  }
  #url-var-hover-create-btn:hover { background: var(--color-primary-hover); }
  :root[data-theme="light"] #url-var-hover-popup { background: var(--surface); border-color: var(--color-border-strong); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }
  :root[data-theme="light"] .url-var-hover-hint { color: var(--color-text-muted); }

  #var-autocomplete-popup {
    position: fixed; z-index: 150; min-width: 220px; max-width: 320px; max-height: 220px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 6px 20px rgba(0,0,0,0.35);
    padding: 4px; font-size: 12px;
  }
  #var-autocomplete-popup.hidden { display: none; }
  .ac-item { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 4px; cursor: pointer; }
  .ac-item.active, .ac-item:hover { background: var(--color-selected-bg); }
  .ac-item.ac-empty { color: var(--text-muted); cursor: default; }
  .ac-item.ac-empty:hover { background: transparent; }
  .ac-item-key { font-weight: 700; color: #eee; flex-shrink: 0; }
  .ac-item-value { color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
  .ac-item-source { flex-shrink: 0; font-size: 10px; text-transform: uppercase; color: #9aa0ac; background: var(--surface-3); border-radius: 8px; padding: 1px 6px; }
  :root[data-theme="light"] #var-autocomplete-popup { background: var(--surface); border-color: var(--color-border-strong); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }
  :root[data-theme="light"] .ac-item-key { color: var(--color-text); }
  :root[data-theme="light"] .ac-item-value { color: var(--color-text-muted); }
  :root[data-theme="light"] .ac-item-source { background: #eef0f3; color: var(--color-text-muted); }

  #url-autocomplete-popup {
    position: fixed; z-index: 150; max-height: 220px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 6px 20px rgba(0,0,0,0.35);
    padding: 4px; font-size: 12px;
  }
  #url-autocomplete-popup.hidden { display: none; }
  #url-autocomplete-popup .method-badge { flex-shrink: 0; }
  /* Kolleksiya nomi ixtiyoriy uzunlikda bo'lishi mumkin (env/global
     yorlig'idagi kabi qisqa so'z emas) — kesib, ellipsis bilan, uppercase'siz. */
  #url-autocomplete-popup .ac-item-source { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: none; }
  :root[data-theme="light"] #url-autocomplete-popup { background: var(--surface); border-color: var(--color-border-strong); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }

  /* Syntax ranglari --syntax-* tokenlari orqali mavzuga moslashadi
     (semantic.css). JSON body editor'ning haqiqiy textarea matni doim
     shaffof (.json-mode #body-content), o'qilishi to'liq shu ranglarga
     bog'liq. */
  .json-key { color: var(--syntax-key); }
  .json-string { color: var(--syntax-string); }
  .json-number { color: var(--syntax-number); }
  .json-boolean { color: var(--syntax-boolean); }
  .json-null { color: var(--syntax-null); }
  .json-punctuation { color: var(--syntax-punctuation); }
  .graphql-keyword { color: var(--syntax-boolean); font-weight: 600; }
  .graphql-variable { color: var(--syntax-number); }
  :root[data-theme="light"] .body-editor-backdrop { color: var(--color-text); }

  /* JSON body editor: same transparent-textarea-over-highlighted-backdrop
     trick as the URL bar above — only turned on (via .json-mode) when
     body-type is "json", so raw/none stay a normal plain textarea. */
  .body-editor-wrap { position: relative; }
  /* Generalized from an earlier #body-content-only version — the GraphQL
     query/variables editors (see #body-graphql-wrap) are two more
     textarea+backdrop pairs using this exact same trick, each in their own
     .body-editor-wrap, so this now targets any textarea inside one rather
     than one specific id. */
  .body-editor-backdrop, .body-editor-wrap textarea {
    box-sizing: border-box; width: 100%; min-height: 200px; margin: 0; padding: 10px;
    font-family: monospace; font-size: 12px; line-height: 18px; white-space: pre-wrap; word-break: break-word;
    border-radius: 4px; border: 1px solid #333;
  }
  .body-editor-backdrop {
    position: absolute; inset: 0; background: var(--surface-void); color: #eee; pointer-events: none; user-select: none;
    /* Backdrop scroll'i JS orqali textarea'ga sinxronlanadi — o'zining
       scrollbar'i kerak emas va u chiqsa layout kengligini o'zgartirib,
       matnni textarea bilan boshqacha wrap qiladi (qatorlar "drift"). */
    overflow: hidden; visibility: hidden;
  }
  /* Ikkalasida ham gutter zahirada tursin — aks holda textarea scrollbar
     chiqqanda uning kontent kengligi backdrop'nikidan tor bo'lib, wrap
     nuqtalari mos kelmaydi va matn ikkilangandek ko'rinadi. */
  .body-editor-backdrop, .body-editor-wrap.json-mode textarea { scrollbar-gutter: stable; }
  /* display: block — inline textarea baseline'i ostida ~6px descender bo'shlig'i
     qoladi, natijada wrap textarea'dan balandroq bo'lib backdrop (inset:0) u
     bilan aynan mos kelmaydi. */
  .body-editor-wrap textarea { position: relative; display: block; background: var(--surface-void); }
  /* The textarea (position:relative) paints AFTER .body-editor-backdrop
     (position:absolute) in the same stacking context since both are
     DOM-order-1/2 positioned elements with no z-index — without an explicit
     transparent background here, the textarea's own opaque background
     fully covers the colored backdrop text behind it, in every theme. */
  .body-editor-wrap.json-mode textarea { background: transparent; color: transparent; caret-color: #eee; }
  .body-editor-wrap.json-mode .body-editor-backdrop { visibility: visible; }
  .body-editor-wrap textarea.body-invalid { border-color: #ff6b6b; }
  .body-editor-wrap textarea.body-valid { border-color: #3a5a42; }
  .diff-line { white-space: pre-wrap; word-break: break-all; padding: 0 4px; }
  /* Diff qatorlari natija ranglarini qayta ishlatadi — mavzuga moslashadi
     (semantic.css: --result-pass/fail). */
  .diff-add { background: var(--result-pass-bg); color: var(--result-pass-fg); }
  .diff-remove { background: var(--result-fail-bg); color: var(--result-fail-fg); }

  .time-fast { color: #4caf7d; } .time-medium { color: #e0a634; } .time-slow { color: #ff6b6b; }

  .pdf-export-progress {
    /* Top-right rather than sharing the toast container's bottom-right
       corner — this panel is persistent for the whole export (seconds to
       tens of seconds) while toasts are transient, so stacking them in the
       same spot would visually collide if an error toast fires mid-export. */
    position: fixed; top: 60px; right: 16px; z-index: 201; width: 260px;
    background: #24262f; border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.35); animation: toast-in 0.15s ease-out;
  }
  .pdf-export-progress-title { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 700; margin-bottom: 8px; }
  .pdf-export-progress-pct { font-weight: 700; color: var(--color-primary-2); }
  .pdf-export-progress-bar-track { height: 6px; border-radius: 3px; background: var(--surface-void); overflow: hidden; margin-bottom: 6px; }
  .pdf-export-progress-bar-fill { height: 100%; background: linear-gradient(90deg,var(--color-primary),var(--color-primary-2)); transition: width 0.2s ease; }
  .pdf-export-progress-stage { font-size: 11px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  :root[data-theme="light"] .pdf-export-progress { background: var(--surface); border-color: var(--color-border-strong); box-shadow: 0 4px 16px rgba(0,0,0,0.12); }
  :root[data-theme="light"] .pdf-export-progress-bar-track { background: var(--color-surface-3); }

  /* Bottom-left, not bottom-right — the chat popup docks bottom-right (see
     #chat-drawer), and toasts (z-index 200) would otherwise stack directly
     on top of it every time one fired while chat was open. */
  #toast-container { position: fixed; bottom: 16px; left: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 200; }
  .toast { background: #24262f; border: 1px solid var(--border); border-left: 3px solid var(--color-primary); border-radius: 4px; padding: 10px 14px; font-size: 12px; max-width: 320px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); animation: toast-in 0.15s ease-out; }
  .toast.error { border-left-color: #ff6b6b; }
  .toast.info { border-left-color: #4f9eff; }
  .toast.success { border-left-color: #4caf50; }
  .toast.warning { border-left-color: #e0a634; }
  /* Yopish tugmasi va action tugmalari qo'shilgach toast ichi ikki ustunli:
     matn + o'ngda kichik ✕. Action tugmalari matn ostida alohida qatorda. */
  .toast { display: grid; grid-template-columns: 1fr auto; column-gap: 10px; align-items: start; padding-right: 8px; }
  .toast-text { grid-column: 1; }
  .toast-actions { grid-column: 1; display: flex; gap: 6px; margin-top: 8px; }
  .toast-close { grid-column: 2; grid-row: 1; background: transparent; border: none; color: var(--text-muted); font-size: 12px; line-height: 1; padding: 2px 4px; border-radius: 4px; cursor: pointer; }
  .toast-close:hover { color: var(--btn-color); background: var(--surface-3); }
  :root[data-theme="light"] .toast-close:hover { color: var(--color-text); background: var(--color-border); }
  @keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

  .collection-color-dot {
    display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 7px; flex-shrink: 0;
    cursor: pointer; box-shadow: 0 0 0 2px rgba(255,255,255,0.08); transition: transform 0.1s ease, box-shadow 0.1s ease;
  }
  .collection-color-dot:hover { transform: scale(1.25); box-shadow: 0 0 0 3px rgba(255,255,255,0.18); }
  :root[data-theme="light"] .collection-color-dot { box-shadow: 0 0 0 2px rgba(0,0,0,0.08); }
  :root[data-theme="light"] .collection-color-dot:hover { box-shadow: 0 0 0 3px rgba(0,0,0,0.15); }
  .collection-item { display: flex; align-items: center; }
  .req-name-edit { font-size: 12px; background: var(--surface); border: 1px solid var(--color-primary); border-radius: 3px; padding: 1px 4px; width: 100%; }
  :root[data-theme="light"] .req-name-edit, :root[data-theme="light"] .collection-name-edit { background: var(--surface); color: var(--color-text); }

  #flow-timeline { margin-bottom: 14px; padding: 10px 12px; background: var(--surface); border: 1px solid var(--color-border); border-radius: 6px; }
  /* Compact one-line echo of the flow-timeline, shown right above the URL bar
     so request progress (resolving → connecting → sent → headers → done) is
     visible no matter which tab (Params/Headers/Body/...) is active — the
     full step-by-step log only lives inside the Response tab otherwise. */
  #flow-progress-strip { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 5px 10px; margin: -2px 0 8px; border-radius: 6px; background: var(--surface); border: 1px solid var(--color-border); }
  #flow-progress-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--color-text-muted); }
  #flow-progress-strip.pending #flow-progress-dot { background: #e0a634; animation: flow-progress-pulse 1s ease-in-out infinite; }
  #flow-progress-strip.ok #flow-progress-dot { background: #8ee6ac; }
  #flow-progress-strip.err #flow-progress-dot { background: #ff6b6b; }
  #flow-progress-text { color: var(--text-muted); }
  #flow-progress-strip.ok #flow-progress-text { color: #8ee6ac; }
  #flow-progress-strip.err #flow-progress-text { color: #ff9c9c; }
  @keyframes flow-progress-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

  /* Contrast raised across the board — the previous #aaa/#666 text on this
     panel's var(--surface) background read as washed-out/hard to read. */
  .flow-step { font-size: 13px; padding: 4px 2px; color: #e4e7ee; display: flex; gap: 10px; align-items: baseline; border-bottom: 1px solid #20242e; }
  .flow-step:last-child { border-bottom: none; }
  .flow-step .flow-time { color: #97a0b3; font-family: monospace; font-size: 11px; flex-shrink: 0; width: 56px; }
  .flow-step .flow-icon { flex-shrink: 0; width: 14px; text-align: center; color: var(--color-text-muted); font-weight: 700; }
  .flow-step .flow-text { flex: 1; }
  .flow-step.ok { color: var(--flow-ok-fg); }
  .flow-step.ok .flow-icon { color: var(--flow-ok-icon); }
  .flow-step.err { color: var(--flow-err-fg); }
  .flow-step.err .flow-icon { color: var(--flow-err-icon); }
  button:disabled { opacity: 0.6; cursor: not-allowed; }
  input:disabled, select:disabled { opacity: 0.5; cursor: not-allowed; }
  @keyframes flash-attention { 0%, 100% { box-shadow: none; } 25%, 75% { box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.5); } }
  .flash-attention { animation: flash-attention 1.2s ease; border-color: #ef4444 !important; }
  .row-more-btn { background: transparent; border: none; padding: 2px 6px; border-radius: 4px; font-weight: 700; letter-spacing: 1px; color: var(--text-muted); opacity: 0; transition: opacity 0.1s; }
  /* Sidebar qator amal tugmalari — JS ularga DaisyUI `.btn btn-square
     btn-xs` beradi, lekin `.btn` ning min-height'i (~48px) qatorni
     56px'gacha cho'zib, nom span'ini ~60px'ga qisardi. Aniq kichik
     o'lcham DaisyUI'ni yengadi. */
  .request-item .row-more-btn.btn, .collection-item .row-more-btn.btn, .folder-item .row-more-btn.btn,
  .request-actions button.btn, .collection-actions button.btn {
    min-height: 22px; height: 22px; width: 22px; min-width: 22px; padding: 0;
  }
  .collection-item:hover .row-more-btn, .collection-item:focus-within .row-more-btn,
  .request-item:hover .row-more-btn, .request-item:focus-within .row-more-btn,
  .folder-item:hover .row-more-btn, .folder-item:focus-within .row-more-btn { opacity: 1; }
  .row-more-btn:hover { background: var(--surface-3); color: var(--btn-color); }
  .context-menu { position: fixed; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.35); z-index: 200; min-width: 170px; padding: 4px; }
  .context-menu-item { padding: 7px 10px; cursor: pointer; border-radius: 4px; font-size: 13px; }
  .context-menu-item:hover { background: var(--surface-3); }
  .context-menu-item:focus-visible, .context-menu-item.active { background: var(--color-selected-bg); outline: none; }
  .context-menu-item.danger { color: #ff9c9c; }
  .context-menu-item.danger:hover { background: #2e1c1c; }
  :root[data-theme="light"] .context-menu { background: var(--surface); border-color: var(--color-border-strong); }
  :root[data-theme="light"] .context-menu-item:hover, :root[data-theme="light"] .context-menu-item:focus-visible, :root[data-theme="light"] .context-menu-item.active { background: var(--color-surface-3); }

  #run-results-table tbody tr.run-row { cursor: pointer; }
  #run-results-table tbody tr.run-row:hover { background: var(--surface); }
  #run-results-table .run-expand-icon { display: inline-block; width: 12px; color: #666; }
  .run-detail-cell { background: var(--surface-void); padding: 12px !important; }
  /* Live collection-run progress (see runCollection in assertions-history.js
     and runs::ws server-side) — a thin filling bar + "Running N/total: name"
     text above the results table, so "did anything happen" is answerable
     immediately instead of only once the whole run (which can be dozens of
     requests) finishes and the table appears all at once. */
  #run-progress-strip { margin: 8px 0 14px; }
  #run-progress-bar-track { height: 6px; border-radius: 3px; background: var(--surface); border: 1px solid var(--color-border); overflow: hidden; }
  #run-progress-bar-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--color-primary), var(--color-primary-2)); transition: width 0.25s ease; }
  #run-progress-strip.err #run-progress-bar-fill { background: linear-gradient(90deg, #c23434, #ff6b6b); }
  #run-progress-text { margin-top: 6px; font-size: 12px; color: var(--text-muted); font-family: monospace; }
  /* Overall run summary, filled once the run finishes (see renderRunSummary
     in assertions-history.js) — total/passed/failed/rate plus timing and
     data-size aggregates, so the outcome of a multi-request run is readable
     at a glance instead of having to scan every row. */
  #run-summary {
    display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; padding-top: 14px;
    border-top: 1px solid var(--color-border);
  }
  .run-summary-stat {
    flex: 1 1 110px; min-width: 100px; padding: 10px 12px; border-radius: 8px;
    background: var(--surface-recessed); border: 1px solid var(--color-border);
  }
  .run-summary-value { font-size: 18px; font-weight: 700; font-family: monospace; }
  .run-summary-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; margin-top: 2px; }
  :root[data-theme="light"] .run-summary-stat { background: var(--color-surface-2); border-color: var(--color-border); }
  :root[data-theme="light"] #run-summary { border-top-color: var(--color-border); }
  :root[data-theme="light"] #run-progress-bar-track { background: var(--color-surface-3); border-color: var(--color-border); }
  .run-detail-table { margin-bottom: 8px; }
  .collection-actions { display: flex; margin-left: auto; gap: 2px; opacity: 0; transition: opacity 0.1s; }
  .collection-item:hover .collection-actions, .collection-item:focus-within .collection-actions { opacity: 1; }
  .collection-actions button { padding: 4px 8px; font-size: 13px; background: transparent; border: none; }
  .collection-actions button:hover, .collection-actions button:focus-visible { background: var(--surface-3); border-radius: 3px; }
  .collection-name-edit { font-size: 13px; background: var(--surface); border: 1px solid var(--color-primary); border-radius: 3px; padding: 1px 4px; flex: 1; min-width: 0; }
  .request-actions { display: flex; margin-left: auto; opacity: 0; transition: opacity 0.1s; }
  .request-item:hover .request-actions, .request-item:focus-within .request-actions { opacity: 1; }
  .request-actions button { padding: 3px 7px; font-size: 12px; background: transparent; border: none; }
  @media (hover: none) {
    /* .row-more-btn (the "..." context-menu trigger on collection/request/
       folder rows) is opacity:0 by default, revealed only by :hover or
       :focus-within — neither ever fires on a touch device, so without this
       it's permanently invisible and its whole context menu (rename/
       duplicate/move/delete/...) becomes unreachable on a real phone. */
    .collection-actions, .request-actions, .row-more-btn, .history-actions { opacity: 1; }
    /* Touch target floor: WCAG's 44x44px minimum for tap targets. Applied
       only on touch devices (hover:none), not desktop, so mouse-driven
       layouts keep their tighter 34px icon buttons — a mouse pointer
       doesn't need the extra margin for error a fingertip does. Low
       specificity (one class) on purpose: the label-variant modifiers
       are restored right below at equal (single-class) specificity — a tie
       resolves by source order, so putting them after is what makes them
       win, not the selector itself. */
    .icon-btn { width: 44px; height: 44px; }
    .icon-btn--label-xs { width: auto; height: 32px; }
    .icon-btn--label-sm, .icon-btn--label, .icon-btn--label-tight { width: auto; height: 38px; }
    .header-icon-btn, .round-add-btn, .row-more-btn, .caret {
      min-width: 44px; min-height: 44px;
    }
    /* DaisyUI's own .btn-xs/.btn-sm are well below the 44px touch floor —
       bump the ones used for the sidebar's small icon actions (env/add
       collection/import/clear-history) the same way as the rest of this
       block, without touching daisyui's desktop-compact sizing. */
    #sidebar .btn.btn-xs { min-width: 44px; min-height: 44px; }
    .request-actions button, .collection-actions button {
      min-width: 44px; min-height: 44px;
    }
    /* The request bar's own controls (method/URL/send) and the sidebar's
       "+ Folder" button carry the same 34px height every input/button gets
       app-wide (see the base `input, select, button { height: 34px }`
       rule) — too tight a target for a fingertip on the row a touch user
       taps most. */
    #req-method, #req-url, #send-btn, .add-folder-btn { min-height: 44px; }
    /* Flow canvas zoom cluster is deliberately compact and already
       tap-adjacent, not a list of independent targets — re-clamp it below
       the general bump above so it doesn't blow out that layout. */
    #flow-zoom-controls .icon-btn {
      width: 30px; height: 30px;
    }
    /* Flow canvas zoom cluster: more breathing room between the +/-/reset
       buttons, and pulled further off the screen edge so it's not sitting
       right under a thumb resting on the bezel. */
    #flow-zoom-controls { gap: 10px; padding: 8px; bottom: 20px; left: 16px; }
  }
  .request-actions button:hover { background: var(--surface-3); border-radius: 3px; }

  /* ---------- light theme ---------- */
  /* color-scheme tells the browser which palette to use for UI it renders
     itself — native <select> dropdown popups, date/time pickers, scrollbars,
     form control default chrome — none of which app CSS can reach. Without
     this override those stayed native default (light) even in light theme
     mode by coincidence, but were wrong (light-on-dark browser popup) in
     the app's actual light theme before this — and always wrong in the
     dark theme in browsers that default to light. */
  :root[data-theme="light"] { color-scheme: light; }
  :root[data-theme="light"] body { background: var(--color-bg); color: var(--color-text); }
  :root[data-theme="light"] .stars-bg { display: none; }
  :root[data-theme="light"] input:not([type=checkbox]):not([type=radio]), :root[data-theme="light"] select, :root[data-theme="light"] textarea { background: var(--surface); border-color: var(--color-border-strong); color: var(--color-text); }
  :root[data-theme="light"] #env-select, :root[data-theme="light"] #collections-search,
  :root[data-theme="light"] #chat-org-select, :root[data-theme="light"] #chat-new-dm-select { background: var(--surface); border-color: var(--color-border-strong); color: var(--color-text); }
  :root[data-theme="light"] #req-url { background: transparent; }
  :root[data-theme="light"] .url-input-backdrop { background: var(--surface); border-color: var(--color-border-strong); color: var(--color-text); }
  :root[data-theme="light"] .body-editor-backdrop, :root[data-theme="light"] .body-editor-wrap textarea { background: var(--surface); border-color: var(--color-border-strong); }
  /* color: transparent — asosiy .body-editor-wrap.json-mode textarea qoidasi
     bilan bir xil (0,2,1) spetsifik va undan oldin turgani uchun light'da
     `:root[data-theme="light"] textarea { color: var(--color-text) }` uni
     bosib ketardi → textarea matni ko'rinib, backdrop bilan ikkilanardi. */
  :root[data-theme="light"] .body-editor-wrap.json-mode textarea { background: transparent; color: transparent; caret-color: var(--color-text); }
  :root[data-theme="light"] {
    /* Panels/modals/inputs used flat pure #fff, which next to this theme's
       off-white #efe9dd page background reads as a harsh, glary white under
       normal room lighting — especially across large flat areas (a whole
       modal, a code block). A very slightly warm, very slightly dimmed
       off-white keeps the "clean light theme" look (still reads as white at
       a glance) while cutting the brightness/contrast spike that was the
       actual eye-strain complaint. */
    /* Sovuq neytral (status page uslubi) — surface #fff, recessed/void
       sovuq off-white. */
    --surface: #ffffff; --surface-2: #f2f4f6; --surface-3: #e9ecef;
    --surface-void: #f7f8fa; --surface-recessed: #f2f4f6;
    /* Qolgan light rang tokenlari (--btn-*, --accent*, --border,
       --text-muted/-secondary, --modal-x-*, --color-success/... ) semantic.css
       dagi :root[data-theme="light"] bloklariga ko'chdi. */
  }
  :root[data-theme="light"] #auth-screen .card { background: rgba(255,255,255,0.75); border-color: var(--color-border); box-shadow: 0 1px 8px rgba(0,0,0,0.08); }
  :root[data-theme="light"] #auth-screen::before { opacity: 0.35; }
  :root[data-theme="light"] #auth-screen::after { opacity: 0.3; }
  :root[data-theme="light"] #auth-screen h1 {
    /* Brend yashilni light temada ham saqlaydi (ilgari terrakota edi) —
       to'q siyohdan --brand'ga, cream fonda o'qiladi. */
    background: linear-gradient(135deg, #2a2620 0%, var(--brand) 100%);
    -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: none;
  }
  :root[data-theme="light"] #topbar { border-color: var(--color-border); }
  :root[data-theme="light"] #sidebar { border-color: var(--color-border); }
  :root[data-theme="light"] #sidebar h3 { color: var(--color-text-muted); }
  :root[data-theme="light"] .collection-item:hover, :root[data-theme="light"] .request-item:hover, :root[data-theme="light"] .history-item:hover { background: var(--surface-3); }
  :root[data-theme="light"] .collection-item.active, :root[data-theme="light"] .request-item.active { background: var(--color-info-bg); box-shadow: inset 3px 0 0 var(--btn-primary-bg); color: var(--color-text); }
  :root[data-theme="light"] .folder-item:hover { background: var(--surface-3); }
  :root[data-theme="light"] .sidebar-section-header:hover { background: var(--surface-3); }
  :root[data-theme="light"] .round-add-btn:hover { background: var(--color-info-bg); border-color: var(--btn-primary-bg); }
  :root[data-theme="light"] .row-more-btn:hover { background: var(--color-border); color: var(--color-text); }
  :root[data-theme="light"] .context-menu-item.danger:hover { background: var(--color-danger-bg); }
  :root[data-theme="light"] .tabs { border-color: var(--color-border); }
  :root[data-theme="light"] .tabs-fade-left { background: linear-gradient(to right, var(--color-bg), transparent); }
  :root[data-theme="light"] .tabs-fade-right { background: linear-gradient(to left, var(--color-bg), transparent); }
  :root[data-theme="light"] .request-tabs-fade-left { background: linear-gradient(to right, var(--color-bg), transparent); }
  :root[data-theme="light"] .request-tabs-fade-right { background: linear-gradient(to left, var(--color-bg), transparent); }
  :root[data-theme="light"] .secret-check { color: var(--color-text-muted); }
  :root[data-theme="light"] pre { background: var(--color-surface-3); color: var(--color-text); }
  :root[data-theme="light"] td, :root[data-theme="light"] th { border-color: var(--color-border); }
  :root[data-theme="light"] .modal-overlay { background: rgba(0,0,0,0.3); }
  :root[data-theme="light"] { --modal-shadow: 0 8px 28px rgba(0,0,0,0.13); }
  :root[data-theme="light"] .reqmodal { background: var(--surface); }
  :root[data-theme="light"] .reqmodal h3 { color: var(--color-text-muted); }
  :root[data-theme="light"] .toast { background: var(--surface); color: var(--color-text); box-shadow: 0 4px 12px rgba(0,0,0,0.12); }
  :root[data-theme="light"] .assertion-item.pass { background: #e6f7ec; color: #1e8a4c; }
  :root[data-theme="light"] .assertion-item.fail { background: var(--color-danger-bg); color: var(--color-danger); }
  /* .url-var-token / .var-token ranglari endi --syntax-var-* tokenlari orqali
     mavzuga moslashadi (semantic.css) — alohida light override kerak emas. */
  :root[data-theme="light"] .url-query-key { color: var(--color-primary); }
  :root[data-theme="light"] #flow-timeline, :root[data-theme="light"] #run-results-table tbody tr.run-row:hover, :root[data-theme="light"] .run-detail-cell { background: var(--color-surface-3); }
  :root[data-theme="light"] #flow-progress-strip { background: var(--color-surface-3); border-color: var(--color-border); }
  :root[data-theme="light"] .flow-step { color: var(--color-text-secondary); }
  :root[data-theme="light"] .flow-step .flow-time { color: var(--color-text-muted); }
  /* .flow-step.ok/.err (+ .flow-icon) endi --flow-* tokenlari orqali
     mavzuga moslashadi (semantic.css). */
  :root[data-theme="light"] .flow-step .flow-icon { color: #9aa0ac; }
  :root[data-theme="light"] .flow-step { border-bottom-color: var(--color-border); }
  :root[data-theme="light"] .code-gutter { background: #eef0f3; color: #8a929e; border-color: var(--color-border); }
  :root[data-theme="light"] .code-block { background: var(--color-surface-2); border-color: var(--color-border); }
  :root[data-theme="light"] #resp-body { background: var(--color-surface-2); }
  :root[data-theme="light"] #response-toolbar-sticky { background: var(--color-bg); }
  :root[data-theme="light"] #tab-response.response-fullscreen { background: var(--surface); }
  :root[data-theme="light"] #tab-response.response-fullscreen #response-toolbar-sticky { background: var(--surface); }
  :root[data-theme="light"] .resp-search-hit { background: #fff3bf; color: var(--surface-void); }
  :root[data-theme="light"] .resp-search-hit.current { background: #ffd43b; color: var(--surface-void); }
  :root[data-theme="light"] .collection-actions button:hover, :root[data-theme="light"] .request-actions button:hover { background: var(--color-border-strong); }

  /* ---------- theme toggle: glowing moon/sun switch ----------
     Adapted from a standalone hero-toggle demo (jh3yy) down to topbar scale.
     Reuses the app's own [data-theme] attribute instead of light-dark(),
     and drops the demo's full-viewport backdrop vignette, which only made
     sense centered on an empty page. */
  .theme-toggle {
    --step: 0.4s;
    --ease: linear(0 0%, 0.2342 12.49%, 0.4374 24.99%, 0.6093 37.49%, 0.6835 43.74%, 0.7499 49.99%, 0.8086 56.25%, 0.8593 62.5%, 0.9023 68.75%, 0.9375 75%, 0.9648 81.25%, 0.9844 87.5%, 0.9961 93.75%, 1 100%);
    --offset: calc(var(--step) * 0.5);
    position: relative; flex-shrink: 0; cursor: pointer;
    font-size: 10.6px; height: 3em; aspect-ratio: 1.8 / 1;
    padding: 0; border: 0; border-radius: 3em; background: transparent;
    box-shadow: none; overflow: visible; isolation: auto;
  }
  .theme-toggle::before, .theme-toggle::after { content: none; }
  .theme-toggle:hover { background: transparent; box-shadow: none; }
  .theme-toggle:active { transform: none; }
  .theme-toggle :is(.socket, .face) { position: absolute; border-radius: inherit; }
  .theme-toggle .socket {
    inset: 0; box-shadow: -0.05em 0.1em 0.2em -0.2em white; background: hsl(0 0% 0%);
    transition-property: background-color, box-shadow; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  :root[data-theme="light"] .theme-toggle .socket { background: hsl(0 0% 90%); }
  .theme-toggle .socket-shadow {
    position: absolute; inset: 0; opacity: 0; border-radius: inherit; box-shadow: 0em 0.075em 0.1em 0em white;
    transition-property: opacity; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  .theme-toggle .face {
    inset: 0.15em; scale: 1;
    transition-property: scale; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  .theme-toggle .face-shadow, .theme-toggle .face-shadow::after, .theme-toggle .face-shadow::before {
    position: absolute; inset: 0; border-radius: inherit;
  }
  .theme-toggle .face-shadow::after, .theme-toggle .face-shadow::before {
    content: ''; transition-property: opacity, translate, filter, scale; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  .theme-toggle .face-shadow::before { background: black; }
  .theme-toggle .face-shadow::after { background: white; scale: 0.5; }
  .theme-toggle .face-plate {
    position: absolute; inset: 0; border-radius: inherit;
    box-shadow: -0.05em 0.1em 0.2em -0.2em white inset;
    background: conic-gradient(from 45deg, #0000, hsl(0 0% 100% / 0.05)), hsl(220 27% 6%);
    transition-property: background-color; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  :root[data-theme="light"] .theme-toggle .face-plate {
    background: conic-gradient(from 45deg, #0000, hsl(0 0% 100% / 0.05)), hsl(223 4% 73%);
  }
  .theme-toggle .face-glowdrop {
    position: absolute; inset: 0; border-radius: inherit; scale: 0;
    transition-property: scale; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  .theme-toggle .face-glowdrop::after, .theme-toggle .face-glowdrop::before {
    content: ''; height: 50%; aspect-ratio: 1; background: #fff; filter: blur(6px); position: absolute; z-index: -1; border-radius: 50%;
  }
  .theme-toggle .face-glowdrop::before { left: 4%; width: 56%; translate: 0 -25%; }
  .theme-toggle .face-glowdrop::after { bottom: 0; right: 12%; width: 34%; translate: 0 20%; }
  .theme-toggle .socket-shadow, .theme-toggle .face-glows, .theme-toggle .face-shine {
    transition-property: opacity; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  .theme-toggle .face-glows {
    position: absolute; inset: -0.075em; opacity: 0; border-radius: inherit; mix-blend-mode: plus-lighter; filter: blur(8px); z-index: 20;
    mask: conic-gradient(from 280deg, #0000, #fff 20deg 45deg, #0000 95deg), conic-gradient(from 110deg, #0000, #fff 20deg, #0000 95deg);
  }
  .theme-toggle .face-glows div { position: absolute; inset: 0; border-radius: inherit; filter: blur(4px); border: 0.1em solid white; }
  .theme-toggle .face-shine { position: absolute; inset: 0; opacity: 0; border-radius: inherit; }
  .theme-toggle .face-shine-shadow {
    position: absolute; inset: 0; border-radius: inherit;
    mask: conic-gradient(from 0deg, #fff 90deg, #0000 110deg 200deg, #fff 215deg 280deg, #0000 315deg);
    box-shadow: 0.075em 0 0.025em -0.025em hsl(0 0% 0% / 0.5) inset, -0.075em -0.05em 0.025em -0.025em hsl(0 0% 0% / 0.5) inset;
  }
  .theme-toggle .face-shine::before {
    content: ''; position: absolute; inset: 0.05em; border-radius: inherit;
    box-shadow: 0 -0.05em 0.025em -0.025em hsl(0 0% 50% / 0.5) inset, -0.025em 0.05em 0.025em -0.025em hsl(0 0% 100% / 0.5) inset;
  }
  .theme-toggle .face-shine::after {
    content: ''; position: absolute; inset: 0; background: conic-gradient(from 45deg, #0000, hsl(0 0% 100% / 0.25)); border-radius: inherit;
  }
  .theme-toggle .face svg {
    --glow: hsl(182 90% 92%); width: 25%; position: absolute; top: 50%; left: 50%; translate: -52% -48%; overflow: visible;
  }
  .theme-toggle .face svg path { transform-box: fill-box; transform-origin: center center; }
  .theme-toggle .glow {
    z-index: 3; fill: var(--glow); stroke: var(--glow); opacity: 1; stroke-width: 0;
    filter: drop-shadow(0 0 0.2em var(--glow));
    transition-property: opacity; transition-duration: 1.25s; transition-delay: var(--offset);
    transition-timing-function: linear(0 0%, 0.0039 6.25%, 0.0156 12.5%, 0.0352 18.75%, 0.0625 25%, 0.0977 31.25%, 0.1407 37.5%, 0.1914 43.74%, 0.2499 49.99%, 0.3164 56.25%, 0.3906 62.5%, 0.5625 75%, 0.7656 87.5%, 1 100%);
  }
  .theme-toggle .trail-holder { z-index: 2; filter: blur(10px); }
  .theme-toggle .trail-holder .trail { stroke-width: 4; }
  .theme-toggle .trail {
    stroke-dasharray: 10 80; stroke-dashoffset: 10; opacity: 0;
    transition-property: stroke-dashoffset, opacity; transition-duration: calc(var(--step) * 3), calc(var(--step) * 0.5);
    transition-delay: var(--offset), calc(var(--offset) + (var(--step) * 2.5));
    transition-timing-function: var(--ease), linear(0 0%, 0.0039 6.25%, 0.0156 12.5%, 0.0352 18.75%, 0.0625 25%, 0.0977 31.25%, 0.1407 37.5%, 0.1914 43.74%, 0.2499 49.99%, 0.3164 56.25%, 0.3906 62.5%, 0.5625 75%, 0.7656 87.5%, 1 100%);
  }
  .theme-toggle .main .trail { stroke-dashoffset: 0; }
  .theme-toggle .inner-face { fill: hsl(230 5% 80%); }
  .theme-toggle .outline { transition-property: stroke; transition-duration: var(--step); transition-timing-function: var(--ease); }
  .theme-toggle .inner-bg {
    fill: black; transition-property: fill; transition-duration: var(--step); transition-timing-function: var(--ease);
  }
  .theme-toggle:active .socket { box-shadow: -0.045em 0.1em 0.2em -0.15em white; }
  .theme-toggle:active .face { scale: 0.99; }
  .theme-toggle[aria-pressed='true'] .face { scale: 1.12; }
  .theme-toggle[aria-pressed='true'] .outline { stroke: hsl(0 0% 30%); }
  .theme-toggle[aria-pressed='true'] .inner-bg { fill: hsl(0 0% 20%); }
  .theme-toggle[aria-pressed='true'] .socket { box-shadow: -0.025em 0.08em 0.2em -0.1em white; }
  .theme-toggle[aria-pressed='true'] .glow { opacity: 0; }
  .theme-toggle[aria-pressed='true'] .face-glowdrop { scale: 1; }
  .theme-toggle[aria-pressed='true'] .face-shadow::before { translate: -15% 55%; filter: blur(1em); opacity: 0.35; }
  .theme-toggle[aria-pressed='true'] .face-shadow::after { filter: blur(0.5em); scale: 1; }
  .theme-toggle[aria-pressed='true'] .socket-shadow, .theme-toggle[aria-pressed='true'] .face-glows, .theme-toggle[aria-pressed='true'] .face-shine { opacity: 1; }
  .theme-toggle[aria-pressed='true'] .trail { transition: stroke-dashoffset 0s; opacity: 1; stroke-dashoffset: -70; }
  .theme-toggle[aria-pressed='true'] .glow { opacity: 0; transition-property: opacity; transition-duration: var(--step); transition-delay: 0s; transition-timing-function: var(--ease); }

  #notifications-wrap { position: relative; }
  /* Pulled out of the topbar's icon row into a fixed round FAB, bottom-right,
     same corner the drawer itself opens from (see .chat-drawer below) —
     always reachable regardless of how crowded/scrolled the topbar gets
     (a real problem in the Telegram Mini App header specifically, see the
     safe-area comment near :root.in-telegram above: every icon crammed into
     that already-narrow strip made mis-taps common), and consistent with
     the Messenger/Intercom-style corner-popup pattern the drawer itself
     already uses. `position:fixed` takes it fully out of #topbar's layout
     flow, so it leaves no gap behind in the icon row. Sits ABOVE the drawer
     (z-index 71 vs 70) and the drawer itself opens floating above this
     button (see .chat-drawer's bottom offset below) rather than over it —
     it used to sit right on top of/at the same spot as the drawer and get
     fully covered the moment it opened, so the only way to close chat again
     was hunting for the small collapse-chevron inside the drawer's own
     header instead of just tapping the same big round button a second time. */
  #chat-btn {
    position: fixed; right: 20px; bottom: 20px; z-index: 71;
    width: 52px; height: 52px; border-radius: 50%; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, var(--color-primary-2), var(--color-primary)); border: none; color: #fff;
    box-shadow: 0 4px 16px rgba(53, 208, 127, 0.4);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
  }
  #chat-btn svg { width: 22px; height: 22px; }
  #chat-btn:hover { transform: scale(1.06); box-shadow: 0 6px 20px rgba(53, 208, 127, 0.5); }
  /* Stays visibly "on" for as long as the drawer is open — otherwise
     nothing shows whether chat is currently open or closed once you've
     looked away from the drawer itself (e.g. after clicking into the main
     request builder). A ring instead of a color swap — the button's own
     background is already the brand gradient, so "active" needs a cue that
     doesn't depend on changing that. */
  #chat-btn.active { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 50%, transparent), 0 4px 16px rgba(53, 208, 127, 0.4); }
  @media (max-width: 768px) {
    #chat-btn { right: 16px; bottom: max(16px, env(safe-area-inset-bottom)); }
    /* On a phone the drawer is a full-screen sheet (see #chat-drawer's own
       mobile override below), not a corner popup — it already has a big,
       obvious close button in its own header, so there's no "can't reach
       the launcher to close" problem to solve here. Without this, raising
       #chat-btn above the drawer (desktop fix, see its own comment) would
       leave the round FAB poking through on top of the full-screen sheet. */
    #chat-btn.active { display: none; }
  }

  /* ---------- Chat (bottom-right floating popup) ---------- */
  /* position:fixed — it contributes no width to #app-shell's flex layout at
     all (fixed elements are out of normal flow), so #app is always full
     width whether the popup is open or not; nothing to push/reflow. Anchored
     to the corner like a Messenger/Intercom widget rather than docked
     edge-to-edge, so the message row never stretches edge-to-edge wide the
     way a full-height side panel's did. */
  #chat-drawer {
    /* bottom: 84px = the FAB's own bottom offset (20px) + its height (52px)
       + a 12px gap — floats above the launcher button instead of over it,
       so #chat-btn (see its own z-index/comment above) stays fully visible
       and clickable the whole time the drawer is open, the same "tap the
       launcher again to close" affordance every other corner-popup chat
       widget has. */
    position: fixed; right: 20px; bottom: 84px; z-index: 70;
    width: var(--chat-drawer-width, 380px); max-width: calc(100vw - 24px);
    height: min(560px, calc(100vh - 164px)); max-height: calc(100vh - 164px);
    background: var(--surface-void); border: 1px solid var(--color-border); border-radius: 14px;
    box-shadow: 0 12px 40px rgba(0,0,0,0.45);
    display: flex; flex-direction: column;
    transform-origin: bottom right;
    transform: scale(0.92) translateY(12px); opacity: 0; pointer-events: none;
    transition: transform 0.18s ease, opacity 0.18s ease;
  }
  #chat-drawer.hidden { display: none; }
  #chat-drawer.open { transform: scale(1) translateY(0); opacity: 1; pointer-events: auto; }
  #chat-drawer-resizer { position: absolute; top: 0; left: -3px; width: 6px; height: 100%; cursor: col-resize; z-index: 1; border-radius: 14px 0 0 14px; }
  #chat-drawer-resizer:hover, #chat-drawer-resizer.dragging { background: var(--color-primary); }
  .chat-drawer-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 12px; border-bottom: 1px solid var(--color-border); border-radius: 14px 14px 0 0; flex-shrink: 0; }
  #chat-drawer-collapse-btn { flex-shrink: 0; }
  #chat-drawer-collapse-btn:hover { background: var(--surface-3); color: var(--btn-color); }
  :root[data-theme="light"] #chat-drawer-collapse-btn:hover { background: var(--color-surface-3); }
  .chat-segmented { display: flex; background: var(--surface); border-radius: 8px; padding: 2px; gap: 2px; }
  .chat-segment { background: transparent; border: none; padding: 5px 12px; font-size: 12px; font-weight: 600; border-radius: 6px; color: var(--text-muted); cursor: pointer; height: auto; }
  .chat-segment.active { background: var(--surface-3); color: var(--btn-color); }
  .chat-picker-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px; padding: 10px 12px 0; flex-shrink: 0; }
  .chat-new-dm-picker { padding: 10px 12px 0; flex-shrink: 0; }
  .chat-new-dm-picker.hidden { display: none; }
  .chat-new-dm-picker input { width: 100%; margin-bottom: 6px; }
  .chat-new-dm-picker .chat-dm-list { max-height: 160px; overflow-y: auto; }
  .chat-picker-row select { min-width: 120px; }
  .chat-picker-row select { flex: 1; min-width: 0; }
  .chat-picker-row .round-add-btn { flex-shrink: 0; }

  /* "Private" is a clickable list of prior conversations, not a dropdown —
     picking one shouldn't need an extra "open the list, then pick" step a
     <select> forces when you already know who you're looking for. Capped
     height (not flex:1) so it doesn't crowd out the thread below it when a
     conversation's already open; it's still fully scrollable past the cap. */
  .chat-dm-list-wrap { flex: 1; min-width: 0; max-height: 130px; overflow-y: auto; }
  .chat-dm-list { display: flex; flex-direction: column; gap: 2px; }
  .chat-dm-list-empty { font-size: 12px; padding: 6px 2px; }
  .chat-dm-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 13px; min-width: 0; }
  .chat-dm-row:hover { background: var(--surface); }
  .chat-dm-row.active { background: var(--color-selected-bg); box-shadow: inset 3px 0 0 var(--color-primary); font-weight: 600; }
  .chat-dm-row .chat-message-avatar { width: 22px; height: 22px; font-size: 9px; }
  .chat-dm-row-avatar-wrap { position: relative; flex-shrink: 0; display: inline-flex; }
  .chat-dm-row-online-dot {
    position: absolute; right: -1px; bottom: -1px; width: 8px; height: 8px;
    border-radius: 50%; background: #4caf7d; border: 2px solid var(--surface-void);
  }
  :root[data-theme="light"] .chat-dm-row-online-dot { border-color: var(--surface); }
  .chat-dm-row-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  :root[data-theme="light"] .chat-dm-row:hover { background: var(--color-surface-3); }
  :root[data-theme="light"] .chat-dm-row.active { background: #eef0f3; }

  .chat-thread { display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0; padding: 10px 12px 12px; }
  .chat-thread-header { font-weight: 600; padding: 0 0 10px; border-bottom: 1px solid var(--color-border); margin-bottom: 8px; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
  #chat-thread-header-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .chat-header-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #4a5062; margin-left: 7px; }
  .chat-header-dot.online { background: #4caf7d; }
  .chat-search-toggle-btn { width: 26px; height: 26px; flex-shrink: 0; }
  .chat-search-row { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-shrink: 0; }
  .chat-search-row input { flex: 1; }
  .chat-search-row .icon-btn { width: 26px; height: 26px; flex-shrink: 0; }
  /* Right padding keeps the scrollbar in its own lane instead of running
     down through message bubbles/avatars — and a message can no longer
     land any own-message action buttons on top of it, since those are now
     a context menu (right-click/long-press) instead of pinned bubble
     corner buttons. Thin custom scrollbar (webkit + Firefox), visible only
     on hover on platforms that support that distinction, instead of the
     platform-default one that draws legacy up/down arrow buttons on some
     browsers' scrollbar tracks. */
  .chat-messages-wrap { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
  .chat-scroll-bottom-btn {
    position: absolute; right: 10px; bottom: 10px; z-index: 2;
    width: 32px; height: 32px; border-radius: 50%; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-3); border: 1px solid var(--border); color: var(--btn-color);
    box-shadow: 0 2px 8px rgba(0,0,0,0.3); cursor: pointer;
  }
  .chat-scroll-bottom-btn:hover { background: var(--surface-3); }
  .chat-scroll-bottom-btn.hidden { display: none; }
  .chat-scroll-bottom-badge {
    position: absolute; top: -4px; right: -4px; min-width: 15px; height: 15px; padding: 0 3px;
    border-radius: 8px; background: #e05252; color: #fff; font-size: 9px; font-weight: 700;
    display: flex; align-items: center; justify-content: center; line-height: 1;
  }
  .chat-scroll-bottom-badge.hidden { display: none; }
  :root[data-theme="light"] .chat-scroll-bottom-btn { background: var(--surface); border-color: var(--color-border-strong); color: var(--color-text); }
  :root[data-theme="light"] .chat-scroll-bottom-btn:hover { background: var(--color-surface-3); }

  /* Consecutive messages from the same sender within a few minutes collapse
     into one visual group — only the first shows an avatar/sender label, the
     rest just align under it, cutting the repeated-avatar clutter a busy
     org room produces. The avatar is `visibility:hidden` (not removed) on
     grouped messages so the text column still lines up under the leader's. */
  .chat-message.grouped { margin-top: -6px; }
  .chat-message.grouped .chat-message-avatar { visibility: hidden; }
  .chat-message.grouped .chat-message-sender { display: none; }
  .chat-date-sep { text-align: center; font-size: 11px; color: var(--color-text-muted); margin: 6px 0 2px; position: relative; }
  .chat-date-sep span { background: var(--surface-void); padding: 0 8px; position: relative; z-index: 1; }
  .chat-date-sep::before { content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: var(--color-border); }
  :root[data-theme="light"] .chat-date-sep span { background: var(--surface); }
  :root[data-theme="light"] .chat-date-sep::before { background: var(--color-border); }
  .chat-mention { color: var(--color-primary-2); font-weight: 600; }
  /* Only applied to messages arriving live over the socket (see
     appendChatMessage's `opts.live` in chat.js) — history/older-page loads
     render a whole batch at once, and animating every one of those would
     read as a flickering wall of motion instead of "something just
     happened", which is the whole point of the cue. */
  .chat-message.live-arrival { animation: chat-message-in 0.22s ease-out; }
  @keyframes chat-message-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
  @media (prefers-reduced-motion: reduce) {
    .chat-message.live-arrival { animation: none; }
  }

  /* Before any room is picked, #chat-messages just sits empty with only the
     header's plain-text "Select a conversation" above it — a blank rectangle
     under a caption reads as broken/loading, not "nothing to show yet".
     :empty gives a proper centered placeholder with no JS bookkeeping. */
  .chat-messages:empty { align-items: center; justify-content: center; }
  .chat-messages:empty::before {
    /* Same outline as the topbar's own chat icon (#chat-btn), just scaled up
       and dimmed, so the empty state reads as "this app's chat", not a
       generic placeholder glyph. */
    content: '';
    width: 40px; height: 40px; opacity: 0.25;
    background-color: var(--color-text-muted);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/%3E%3C/svg%3E") center / contain no-repeat;
  }
  .chat-messages { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; padding: 4px 16px 4px 2px; min-height: 0; scrollbar-width: thin; scrollbar-color: transparent transparent; }
  .chat-messages:hover { scrollbar-color: var(--border) transparent; }
  .chat-messages::-webkit-scrollbar { width: 8px; }
  .chat-messages::-webkit-scrollbar-track { background: transparent; }
  .chat-messages::-webkit-scrollbar-thumb { background: transparent; border-radius: 4px; }
  .chat-messages:hover::-webkit-scrollbar-thumb { background: var(--border); }
  .chat-messages::-webkit-scrollbar-thumb:hover { background: #4a5062; }
  .chat-loading { padding: 12px 0; text-align: center; }
  /* Shimmer skeleton shown while chat history loads — replaces a static
     "…" (CLAUDE.md UX rule: loading states need a skeleton/spinner). */
  .chat-skeleton { display: flex; flex-direction: column; gap: 10px; padding: 4px 16px 4px 2px; }
  .chat-skel-row { display: flex; gap: 8px; align-items: flex-end; }
  .chat-skel-row.mine { flex-direction: row-reverse; }
  .chat-skel-avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--surface-3); flex-shrink: 0; }
  .chat-skel-bubble { height: 34px; border-radius: 10px; background: var(--surface-3); position: relative; overflow: hidden; }
  .chat-skel-bubble::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.06), transparent);
    animation: chat-skel-shimmer 1.4s infinite;
  }
  @keyframes chat-skel-shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
  @media (prefers-reduced-motion: reduce) { .chat-skel-bubble::after { animation: none; } }
  /* Collections sidebar skeleton — same shimmer as .chat-skel-bubble, shown
     as #collections-list's static markup so it's visible immediately on
     boot, before loadCollections()'s fetch resolves and renderCollections()
     wipes it via innerHTML = ''. */
  .collections-skeleton { display: flex; flex-direction: column; gap: 8px; padding: 4px 2px; }
  .collections-skel-row { height: 30px; border-radius: 8px; background: var(--surface-3); position: relative; overflow: hidden; }
  .collections-skel-row::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.06), transparent);
    animation: chat-skel-shimmer 1.4s infinite;
  }
  @media (prefers-reduced-motion: reduce) { .collections-skel-row::after { animation: none; } }
  /* Reconnecting/disconnected — reuses the same warning tokens as the
     load-test rate-limit banner, for one consistent "heads up" treatment
     app-wide instead of a chat-specific color. */
  .chat-connection-banner { background: var(--color-warning-bg); border: 1px solid var(--color-warning-border); color: var(--color-warning); border-radius: 8px; padding: 6px 12px; font-size: 12px; margin-bottom: 8px; flex-shrink: 0; }
  .chat-message { display: flex; gap: 8px; max-width: 88%; cursor: default; }
  .chat-message.mine { flex-direction: row-reverse; align-self: flex-end; text-align: right; cursor: context-menu; }
  .chat-message-avatar {
    width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700;
    letter-spacing: -0.02em; user-select: none;
  }
  .chat-message-body { min-width: 0; }
  .chat-message-sender { font-size: 11px; font-weight: 600; margin-bottom: 2px; padding: 0 2px; }
  /* 4px corner nearest the avatar (bottom-left for others, bottom-right for
     mine) instead of the usual uniform 10px — the classic "speech bubble
     points at its sender" cue, without an extra pseudo-element triangle. */
  .chat-message-text { background: var(--surface); border-radius: 10px 10px 10px 4px; padding: 8px 12px; font-size: 13.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; }
  /* accent-ink (near-black), not white — white-on-var(--color-primary-2) (the gradient's
     lighter stop) is well under WCAG AA contrast; every other accent-filled
     surface in the app (buttons, .role-badge.approved, etc.) already uses
     the dark ink for the same reason. */
  .chat-message.mine .chat-message-text {
    background: linear-gradient(135deg, var(--accent-2), var(--accent));
    color: var(--accent-ink);
    border-radius: 10px 10px 4px 10px;
  }
  .chat-message-time { font-size: 10px; color: var(--color-text-muted); margin-top: 3px; display: flex; gap: 6px; }
  .chat-message.mine .chat-message-time { justify-content: flex-end; }
  .chat-message-edited { font-style: italic; }
  /* opacity:.6 on the already-muted bubble text read as barely-there in
     dark mode — a dedicated (still clearly de-emphasized, just readable)
     color instead of a blanket opacity cut keeps it a real WCAG-passing
     gray rather than whatever .6 of the theme's base text color lands on. */
  .chat-message.deleted .chat-message-text { font-style: italic; color: var(--color-text-muted); background: transparent; border: 1px dashed var(--border); }
  .chat-message-edit-input { width: 100%; min-width: 220px; font-size: 13px; padding: 8px 12px; border-radius: 10px; resize: vertical; }
  .chat-typing-indicator { font-size: 11px; color: var(--text-muted); font-style: italic; padding: 2px 2px 0; height: 16px; flex-shrink: 0; }
  .chat-input-row { display: flex; gap: 8px; align-items: flex-end; margin-top: 8px; flex-shrink: 0; }
  /* Pill-shaped, Telegram/Messenger-style input instead of the app's usual
     boxy 4px-radius form field — a chat composer reads as a chat composer
     with a fully rounded bar, not a generic form textarea. height auto-
     grows via JS (1-4 lines, ~24px/line) up to 108px — the CSS side just
     sets the 1-line starting point and the ceiling; the radius stays a flat
     20px rather than scaling with height, so it still just reads as
     "rounded corners" once the box grows past one line instead of a stretched
     stadium shape. */
  #chat-input {
    flex: 1; resize: none; height: 40px; max-height: 108px;
    padding: 9px 16px; line-height: 1.4; box-sizing: border-box;
    border-radius: 20px; background: var(--surface); border-color: #2f333d;
    transition: border-color 0.15s ease;
  }
  #chat-input:focus { border-color: var(--color-primary); outline: none; }
  :root[data-theme="light"] #chat-input { background: #eef0f3; border-color: var(--color-border); }
  :root[data-theme="light"] #chat-input:focus { border-color: var(--color-primary); }
  /* Circular send button, sized to match the input's collapsed height — the
     standard chat-app "round send button at the end of a pill input" pairing. */
  #chat-send-btn { width: 40px; height: 40px; flex-shrink: 0; border-radius: 50%; }
  #chat-send-btn:disabled { opacity: 0.5; cursor: default; }
  :root[data-theme="light"] #chat-drawer { background: var(--surface); border-color: var(--color-border); }
  :root[data-theme="light"] .chat-drawer-header, :root[data-theme="light"] .chat-thread-header { border-color: var(--color-border); }
  :root[data-theme="light"] .chat-segmented { background: #eef0f3; }
  :root[data-theme="light"] .chat-segment.active { background: var(--surface); color: var(--color-text); box-shadow: 0 1px 2px rgba(0,0,0,0.08); }
  :root[data-theme="light"] .chat-message:not(.mine) .chat-message-text { background: #eef0f3; }
  :root[data-theme="light"] .chat-message.deleted .chat-message-text { color: #9ca3af; border-color: var(--color-border-strong); }

  @media (min-width: 769px) and (max-width: 1024px) {
    #chat-drawer { width: var(--chat-drawer-width, 340px); }
  }

  /* A phone has no room for a floating corner popup either — a 380px-wide
     card anchored 20px off the edge just runs off-screen or eats most of
     the viewport. Full-screen sheet instead, same as every other drawer in
     this app collapses to below tablet width. No resizing (nothing to
     resize against on a phone), no corner offset/radius/shadow. */
  @media (max-width: 768px) {
    #chat-drawer {
      right: 0; bottom: 0; left: 0; top: 0; width: auto; max-width: 100vw;
      height: auto; max-height: 100vh; border-radius: 0; border: none;
      transform: none; transition: opacity 0.18s ease;
    }
    #chat-drawer.hidden { display: none; }
    #chat-drawer:not(.open) { opacity: 0; pointer-events: none; }
    #chat-drawer.open { padding-bottom: env(safe-area-inset-bottom); }
    #chat-drawer-resizer { display: none; }
    .chat-drawer-header { border-radius: 0; }
    .chat-picker-row { flex-wrap: wrap; }
  }
  .notifications-badge {
    position: absolute; top: 2px; right: 2px; min-width: 15px; height: 15px; padding: 0 3px;
    border-radius: 999px; background: #e5484d; color: #fff; font-size: 10px; line-height: 15px;
    text-align: center; font-weight: 600;
  }
  .notification-item.unread { background: color-mix(in srgb, var(--btn-bg) 60%, transparent); }

  /* ---------------------------------------------------------------------
     "Qora" (black/OLED) tema — mavjud dark temaning variant'i, alohida
     rang palitrasi emas: accent/matn/border ranglar xuddi dark temadagidek
     qoladi, faqat surface qatlamlari (--surface/--surface-2/--surface-3/
     --surface-void/--surface-recessed) va body foni pastga bosiladi.
     Barcha komponent shu 5 token orqali ishlagani uchun (yuqoridagi
     --surface-void/--surface-recessed joriy qilinishi ana shu maqsadda edi)
     bitta joyda o'zgartirish BUTUN ilova bo'ylab ishlaydi — alohida-alohida
     selector qo'shish shart emas. */
  :root[data-theme="black"] {
    color-scheme: dark;
    --surface: #000000; --surface-2: #0a0a0a; --surface-3: #121212;
    --surface-void: #000000; --surface-recessed: #000000;
    /* --border va --color-* semantic.css'dagi :root[data-theme="black"] blokida. */
  }
  :root[data-theme="black"] body {
    background:
      radial-gradient(ellipse 900px 600px at 88% -5%, rgba(53, 208, 127, 0.06) 0%, transparent 60%),
      radial-gradient(ellipse 1400px 900px at 15% 105%, rgba(18, 52, 32, 0.2) 0%, transparent 55%),
      #000000;
  }
  :root[data-theme="black"] .stars-bg { opacity: 0.5; }

/* ---------- Fuzzer paneli ---------- */
.ff-hint {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: 4px;
  border-radius: 50%; border: 1px solid var(--border);
  font-size: 10px; font-weight: 700; line-height: 1;
  color: var(--color-text-muted); cursor: help; user-select: none;
}
.ff-hint:hover, .ff-hint:focus { color: var(--color-primary); border-color: var(--color-primary); outline: none; }
.ff-cat { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--btn-color); cursor: pointer; }
.ff-table th, .ff-table td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
.ff-table th { color: var(--color-text-muted); font-weight: 600; }
.ff-table td.ff-payload { font-family: monospace; max-width: 260px; overflow-wrap: anywhere; }
.ff-row-finding { background: var(--color-danger-bg, rgba(220, 60, 60, 0.08)); }
.ff-badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; }
.ff-badge.ok { color: var(--color-success); }
.ff-badge.bad { color: var(--color-danger); border: 1px solid var(--color-danger); }
.ff-kpi-grid { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
.ff-kpi-card { padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; min-width: 90px; }
.ff-kpi-card.err { border-color: var(--color-danger); }
.ff-kpi-v { font-size: 18px; font-weight: 700; }
.ff-kpi-l { font-size: 11px; color: var(--color-text-muted); }

/* ---------- Ikki panelli modal qobig'i (split-shell) ---------- */
/* Umumiy: shell scroll bo'lmaydi (overflow:hidden), header + close-row
   qotirilgan, faqat chap nav va o'ng kontent mustaqil scroll bo'ladi.
   Admin modal shuni ishlatadi; uptime-shell o'z variantini saqlaydi. */
.reqmodal.split-shell {
  width: 1080px; max-width: 96vw; height: 86vh; max-height: 86vh;
  padding: 0; display: flex; flex-direction: column; overflow: hidden;
}
.reqmodal.split-shell > .modal-header {
  padding: 14px 20px; border-bottom: 1px solid var(--border); margin: 0;
  flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.reqmodal.split-shell > .close-row {
  padding: 10px 20px; border-top: 1px solid var(--border); margin: 0; flex-shrink: 0;
}
.split-2pane { flex: 1; display: flex; min-height: 0; background: var(--surface); }
.split-nav {
  width: 216px; flex: none; border-right: 1px solid var(--border);
  overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 2px;
  background: var(--surface);
}
.split-nav .tab {
  display: block; width: 100%; text-align: left; border: 0; border-radius: 7px;
  padding: 8px 10px; font-size: 13px; color: var(--color-text-secondary, var(--color-text));
  cursor: pointer; position: relative; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.split-nav .tab:hover { background: var(--color-surface-2, rgba(127,127,127,.06)); }
/* Tanlangan bo'lim aniq ko'rinsin — oldingi ~10% alfa fon deyarli
   sezilmasdi. To'liq surface-3 fon + to'liq kuchli matn rangi + accent bar. */
.split-nav .tab.active {
  background: var(--color-surface-3);
  color: var(--color-text);
  font-weight: 600;
}
.split-nav .tab.active::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px;
  border-radius: 0 2px 2px 0; background: var(--color-primary);
}
.split-body { flex: 1; overflow-y: auto; padding: 18px 22px; min-width: 0; background: var(--surface); }
.split-body .row { flex-wrap: wrap; }
/* Nav'siz variant: `.split-body` to'g'ridan-to'g'ri shell farzandi bo'lsa ham
   (bir ustunli — header + scroll body + close-row). */
.reqmodal.split-shell > .split-body { flex: 1; min-height: 0; }
.split-body > h3:first-child { margin-top: 0; }
@media (max-width: 768px) {
  /* Mobil full-screen shell'lar: chet viewport qirrasida qirqiladi, soya
     ko'rinmaydi — border/soya olib tashlanadi (desktopда oddiy modal
     sifatida border oladi). */
  .reqmodal.split-shell, .reqmodal.uptime-shell, .reqmodal.pdf-viewer-box { border: none; box-shadow: none; }
  .reqmodal.split-shell { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; border-radius: 0; }
  .split-2pane { flex-direction: column; }
  .split-nav {
    width: auto; flex-direction: row; gap: 4px; border-right: none;
    border-bottom: 1px solid var(--border); overflow-x: auto; overflow-y: hidden;
  }
  .split-nav .tab { width: auto; flex: none; }
  .split-nav .tab.active::before { display: none; }
  .split-nav .tab.active { box-shadow: inset 0 -2px 0 var(--color-primary); border-radius: 6px 6px 0 0; }
}

/* ---------- Uptime monitor paneli ---------- */
/* Ikki panelli qobiq — shell'ning o'zi hech qachon scroll bo'lmaydi
   (overflow:hidden), header + Close doim ko'rinadi, faqat chap ro'yxat va
   o'ng detal/forma o'zlari mustaqil scroll bo'ladi. grpc-shell namunasi. */
.reqmodal.uptime-shell {
  width: 1080px; max-width: 96vw; height: 86vh; max-height: 86vh;
  padding: 0; display: flex; flex-direction: column; overflow: hidden;
}
.reqmodal.uptime-shell > .modal-header {
  padding: 14px 20px; border-bottom: 1px solid var(--border);
  flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0;
}
.reqmodal.uptime-shell > .close-row {
  padding: 10px 20px; border-top: 1px solid var(--border); margin: 0; flex-shrink: 0;
}
.uptime-2pane { flex: 1; display: flex; min-height: 0; }
.uptime-left {
  width: 300px; flex: none; border-right: 1px solid var(--border);
  display: flex; flex-direction: column; min-height: 0;
}
.uptime-left-toolbar { padding: 10px; border-bottom: 1px solid var(--border); flex-shrink: 0; display: flex; flex-direction: column; gap: 6px; }
.uptime-left-toolbar select { flex: 1; min-width: 0; }
.uptime-list { flex: 1; overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.uptime-right { flex: 1; overflow-y: auto; padding: 18px 22px; min-width: 0; }
.uptime-right > .hidden { display: none; }
#up-detail-view h3 { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--color-text-muted); margin: 22px 0 12px; }
.uptime-placeholder { color: var(--color-text-muted); font-size: 13px; padding: 56px 20px; text-align: center; line-height: 1.6; }

/* ---------- Uptime — public status page vizual tiliga moslashtirilgan ----------
   Status pill (dot + label + tint fon), tabular raqamlar, timeline incidents.
   Rang: status-2xx/4xx/5xx -> --c (success/warning/danger). */
.up-pill {
  --c: var(--color-text-muted);
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  font-size: 11.5px; font-weight: 600; color: var(--c);
  background: color-mix(in srgb, var(--c) 12%, transparent);
  padding: 3px 9px 3px 7px; border-radius: 999px;
}
.up-pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--c); flex: none; }
.up-pill.status-2xx { --c: var(--color-success); }
.up-pill.status-4xx { --c: var(--color-warning); }
.up-pill.status-5xx { --c: var(--color-danger); }

/* Chap ro'yxat kartasi — bir qatorli, ixcham. */
.up-card.up-card-compact {
  cursor: pointer; border: 1px solid transparent; border-radius: var(--radius-md);
  padding: 9px 10px; gap: 6px; position: relative;
  transition: background-color .12s ease;
}
.up-card.up-card-compact:hover { background: var(--surface-2); }
.up-card.up-card-compact.selected { background: var(--surface-2); }
.up-card.up-card-compact.selected::before {
  content: ""; position: absolute; left: 0; top: 7px; bottom: 7px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--color-primary);
}
.up-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--color-text-muted); }
.up-dot.status-2xx { background: var(--color-success); }
.up-dot.status-4xx { background: var(--color-warning); }
.up-dot.status-5xx { background: var(--color-danger); }
.up-card-name { font-weight: 600; font-size: 13px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-card-sub { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11px; color: var(--color-text-muted); }
.up-tag { font-size: 10px; padding: 0 5px; line-height: 15px; border-radius: 4px; background: var(--surface-3); color: var(--color-text-muted); }

/* O'ng detal. */
.uptime-detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
.up-detail-windows { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.up-detail-windows .up-detail-window { border: 0; border-radius: 0; background: transparent; font-size: 12px; font-weight: 500; padding: 5px 13px; color: var(--color-text-muted); }
.up-detail-windows .up-detail-window + .up-detail-window { border-left: 1px solid var(--border); }
.up-detail-windows .up-detail-window.btn-active { background: var(--color-primary); color: var(--color-primary-fg, #fff); }
.up-detail-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 14px 0; }
.up-detail-stats > div {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 12px 14px; display: flex; flex-direction: column; gap: 3px; background: var(--surface);
}
.up-detail-stats .k { font-size: 11px; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: .03em; }
.up-detail-stats .v { font-size: 19px; font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.up-detail-empty { color: var(--color-text-muted); font-size: 12px; padding: 24px 0; text-align: center; }
#up-detail-chart-wrap {
  border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 10px 12px 4px; background: var(--surface);
}

/* Incidents — vertikal timeline (public sahifadagi `.incident` kabi). */
.up-timeline { list-style: none; margin: 0; padding: 0; }
.up-inc { display: flex; gap: 13px; padding: 0 0 16px 3px; position: relative; }
.up-inc::before { content: ""; position: absolute; left: 6px; top: 15px; bottom: -2px; width: 2px; background: var(--border); }
.up-inc:last-child { padding-bottom: 0; }
.up-inc:last-child::before { display: none; }
.up-inc-dot { --c: var(--color-text-muted); width: 9px; height: 9px; border-radius: 50%; margin-top: 4px; flex: none; box-shadow: 0 0 0 4px var(--surface); z-index: 1; }
.up-inc-dot.status-2xx, .up-inc-dot.status-4xx, .up-inc-dot.status-5xx { background: var(--c); }
.up-inc-dot.status-2xx { --c: var(--color-success); }
.up-inc-dot.status-4xx { --c: var(--color-warning); }
.up-inc-dot.status-5xx { --c: var(--color-danger); }
.up-inc-body { flex: 1; min-width: 0; }
.up-inc-head { font-size: 13px; display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.up-inc-sev { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; background: none; padding: 0; }
.up-inc-sev.status-2xx { color: var(--color-success); background: none; }
.up-inc-sev.status-4xx { color: var(--color-warning); background: none; }
.up-inc-sev.status-5xx { color: var(--color-danger); background: none; }
.up-inc-ongoing { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--color-danger); background: var(--color-danger-bg); padding: 1px 6px; border-radius: 4px; }
.up-inc-meta { font-size: 11.5px; color: var(--color-text-muted); margin-top: 3px; font-variant-numeric: tabular-nums; }
.up-inc-err { font-size: 11px; color: var(--color-text-muted); margin-top: 3px; word-break: break-word; }
.up-inc-empty { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: var(--color-text-muted); }
.up-inc-empty .mark { width: 18px; height: 18px; border-radius: 50%; flex: none; font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; color: var(--color-success); background: color-mix(in srgb, var(--color-success) 15%, transparent); }

@media (max-width: 768px) {
  .reqmodal.uptime-shell { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; border-radius: 0; }
  .uptime-2pane { flex-direction: column; }
  .uptime-left { width: auto; border-right: none; border-bottom: 1px solid var(--border); max-height: 42vh; }
  .up-detail-stats { grid-template-columns: 1fr; }
}
.up-card { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; }
.up-card-head { display: flex; align-items: center; gap: 8px; }
/* Sparkline — public `.strip` uslubi: yumshoq, rounded, eng yangi ustun belgili. */
.up-spark-row { display: flex; gap: 2px; align-items: stretch; height: 20px; }
.up-spark { display: inline-block; flex: 1; min-width: 3px; border-radius: 2px; background: var(--border); opacity: 1; }
.up-spark.status-2xx { background: var(--color-success); opacity: .92; }
.up-spark.status-4xx { background: var(--color-warning); opacity: .92; }
.up-spark.status-5xx { background: var(--color-danger); opacity: .92; }
.up-spark.is-latest { opacity: 1; box-shadow: 0 0 0 1.5px var(--surface), 0 0 0 3px color-mix(in srgb, var(--color-text-muted) 30%, transparent); }
.response-binary-download-link { color: var(--color-primary-2); }
