/* Hide Alpine-controlled elements until Alpine initializes (x-cloak) */
[x-cloak]{display:none!important;}
/* ============================================================
   Utility display classes -- TASK CJ (Boss P1 2026-09-01 23:44Z,
   'ads popup opens hugging the left edge, clipped'). CLASS bug across 58
   sites: `display:flex|grid|inline-flex` lived INSIDE the same inline
   `style=""` attribute as `x-show`. Alpine's x-show toggles visibility by
   writing `el.style.display` directly (`'none'` on hide, `''` on show) --
   when `display` lives in that same inline attribute, the show-path write
   silently ERASES it, so the element falls back to its CSS default
   display (`block` for a `div`) and any flex/grid layout (centering,
   row/column gap, alignment) breaks the moment the element is toggled at
   least once. Same bug family as the Alpine template-scope gotcha
   (lint_alpine_template_scope.py): a structural pattern, not a typo.
   Fix: move `display` OUT of the attribute Alpine writes to and into a
   class -- classes are untouched by x-show's style write, so the layout
   survives every show/hide cycle. Structural guard:
   ci/lint_xshow_inline_display.py. Worked example:
   portal/conversation.html's ads-picker overlay (Boss's exact repro,
   ~line 347).
   Lives HERE in app.css, not portal.css -- app.css is the ONE stylesheet
   base.html loads unconditionally on every page (auth/, buyer/, panel/,
   portal/ all extend base.html; only portal/panel pages additionally load
   portal.css/panel.css). 8 of the 58 sites are in auth/forgot.html and
   panel/{deal,quote}.html, which do NOT load portal.css -- defining these
   classes there would have left those sites with an undefined class
   (still no display), the exact same bug wearing a different hat. */
.u-flex { display: flex; }
.u-inline-flex { display: inline-flex; }
.u-grid { display: grid; }
/* Fixed full-viewport modal backdrop + centered card -- the shape every
   `x-show="somethingOpen"` overlay in the 58-site sweep used this for
   (conversation.html/inbox.html ads pickers, integrations.html
   connect/disconnect modals, quotes.html assign modal, deals_board.html
   and panel/deal.html's lost-deal modal). `background`/`z-index` stay
   inline per site (they vary: 50 vs 60, different rgba alphas) -- only
   the position/display/centering triple is common enough to share. */
.modal-veil { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
/* TASK CJ fast-follow (Boss report 2026-09-02 00:07Z, live-measured
   w=10px disp:block on the conversation-page emoji popup): the popup's
   inline `left:{{ left }};right:{{ right }}` (app/templates/portal/
   _emoji_picker.html's `emoji_picker()` macro) resolves against its
   `position:relative` ANCESTOR -- that ancestor is only the 34x34px emoji
   BUTTON's own flex-item wrapper (`.composer` is `display:flex`, and the
   wrapper carries `flex:none`, so it shrinks to its button's content
   width, not the composer's full width). left+right both set with
   width:auto is CSS's classic over-constrained case: the browser solves
   for width = (containing-block width) - left - right = 34 - 12 - 12 =
   10px at both real call sites (conversation.html 12px/12px, inbox.html
   10px/10px) -- confirmed empirically with the real markup in a real
   Chromium instance. This is INDEPENDENT of (survives) the x-show/
   inline-display fix above: erasing/restoring `display` never changes
   how `left`/`right`/`width:auto` resolve on a position:absolute box.
   `.emoji-picker-panel`'s `min-width` overrides the computed 10px (the
   browser then treats `right` as the flexible edge, so the panel grows
   rightward from its `left` anchor instead of clipping) -- one class, on
   the ONE shared partial macro, fixes both call sites; no per-page
   duplication. */
.emoji-picker-panel { min-width: 240px; }
/* NiuFlow 牛顺 design tokens + shared components. Source of truth: Claude Design handoff.
   #254: the tokens BOTH this file and panel.css used (identical values)
   moved to tokens.css, loaded by base.html ahead of this file -- see that
   file's header comment. This block now holds ONLY the tokens exclusive
   to app.css: the auth-page chrome gradient (--color-chrome-1/2/3,
   --color-body-bg), one muted shade only error.html's 404 code uses
   (--color-muted-3), and --color-amber/--color-hot, the base hues a few
   templates reference directly (var(--color-amber) in portal/billing.html
   + portal/conversation.html, var(--color-hot) in
   admin/tenant_detail.html) alongside the -fg/-bg pairs already in
   tokens.css. None of these were ever duplicated in panel.css, so they
   stay out of the shared file per minimum-diff. */
:root {
  --color-body-bg: #eef0f3;
  --color-chrome-1: #16202b;
  --color-chrome-2: #1c2836;
  --color-chrome-3: #243244;
  --color-muted-3: #b3bcc7;
  --color-amber: #d99a1f;
  --color-hot: #d94f2b;
}
* { box-sizing: border-box; }
body { margin: 0; font-family: var(--font-sans); color: var(--color-ink); background: var(--color-body-bg); font-size: 14px; line-height: 1.5; }
a { color: var(--color-link); text-decoration: none; }
a:hover { color: var(--color-accent-hover); text-decoration: underline; }
.mono { font-family: var(--font-mono); }
.muted { color: var(--color-muted); }

/* buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font-family: inherit; font-size: 13px; font-weight: 600; border-radius: var(--radius-button); padding: 9px 16px; cursor: pointer; border: 1px solid transparent; }
.btn-primary { background: var(--color-accent); border-color: var(--color-accent); color: #fff; }
.btn-primary:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.btn-primary:disabled { opacity: .55; cursor: default; }
.btn-outline { background: transparent; border-color: var(--color-accent); color: var(--color-accent); }
.btn-outline:hover { background: var(--color-accent-tint); }
.btn-ghost { background: transparent; border-color: var(--color-border); color: var(--color-muted); }
.btn-ghost:hover { background: var(--color-panel-alt); color: var(--color-ink); }
.btn-block { width: 100%; }

/* forms */
.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--color-muted); }
.field input, .field select, .field textarea { font-family: inherit; font-size: 14px; color: var(--color-ink); padding: 9px 11px; border: 1px solid var(--color-border); border-radius: var(--radius-button); background: #fff; width: 100%; }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--color-accent); box-shadow: 0 0 0 3px var(--color-accent-tint); }
.form-error { color: var(--color-hot-fg); background: var(--color-hot-bg); border-radius: var(--radius-chip); font-size: 12px; padding: 8px 10px; }

/* chips / badges */
.chip { display: inline-block; font-size: 10px; font-weight: 600; letter-spacing: .02em; padding: 2px 7px; border-radius: var(--radius-chip); white-space: nowrap; }
.chip-success { color: var(--color-success); background: var(--color-success-bg); }
.chip-amber { color: var(--color-amber-fg); background: var(--color-amber-bg); }
.chip-hot { color: var(--color-hot-fg); background: var(--color-hot-bg); }
.chip-neutral { color: var(--color-neutral-fg); background: var(--color-neutral-bg); }

/* cards */
.card { background: var(--color-panel); border: 1px solid var(--color-border); border-radius: var(--radius-panel); padding: 16px; }
.card-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--color-muted); }

/* lang toggle (shared) */
.lang-toggle { display: flex; gap: 2px; }
.lang-toggle button { font-family: inherit; font-size: 11px; font-weight: 500; color: var(--color-muted); background: transparent; border: 1px solid var(--color-border); border-radius: var(--radius-chip); padding: 3px 8px; cursor: pointer; }
.lang-toggle button.active { background: var(--color-accent); border-color: var(--color-accent); color: #fff; }

/* auth (login) */
.auth-wrap { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; background: linear-gradient(180deg, var(--color-chrome-1) 0%, var(--color-chrome-2) 46%, var(--color-body-bg) 46.1%); }
.auth-card { width: 100%; max-width: 380px; background: var(--color-panel); border: 1px solid var(--color-border); border-radius: var(--radius-panel); padding: 28px; display: flex; flex-direction: column; gap: 16px; box-shadow: 0 12px 32px rgba(22,32,43,.18); }
.auth-brand { display: flex; align-items: center; gap: 10px; }
.auth-logo { width: 34px; height: 34px; border-radius: 8px; background: var(--color-accent); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; }
.auth-title { font-size: 17px; font-weight: 700; }
.auth-sub { font-size: 12px; color: var(--color-muted); margin-top: 1px; }
.auth-footer { font-size: 11px; color: var(--color-muted-3); text-align: center; font-family: var(--font-mono); }
