agnes-the-ai-analyst/app/web/static/style-custom.css
Vojtech 001e5ce40e
feat(web): /home value-first redesign + unified page-shell across app (#366)
* feat(web): value-first /home reskin (CEO mock palette + pillars + first-session)

Restructures `/home` to lead with product value instead of install steps,
matching the CEO mock proposed for the homepage:

- New intro hero on top — eyebrow `Welcome, {{ display_name }}`, H1
  `{{ instance_brand }} is your team's AI workspace`, lede framing the
  product as an "AI Chief of Staff", two CTAs (`Set up in ~15 min →`
  jumps to the wizard, `Just browse — no install needed` jumps to
  `#look-around`), and a four-pillar row (Data packages · Plugins ·
  Skills · Memory). Renders for both onboarded and not-onboarded users
  so the value framing is consistent across visits.
- New `first-session` narrative — five-beat walkthrough (launch → pick
  project → memory loads → ask → close) with mock terminal frames
  carrying traffic-light dots, prompts, and dimmed system output.
- Setup wizard chrome — progress chip (`Step 1 of N · ~15 min ·
  One-time · Reversible`), thin progress bar, and per-step number
  badges on each `.install-block` so the wizard reads as bounded
  instead of an open-ended scroll.
- Palette shift from blue to green/navy: `--hp-primary` aliases
  `#2ea877` (mint), `--hp-hero-bg` is navy `#0f1b3a`, code panels stay
  near-black `#0c1224` with warm-yellow `#ffd866` accents. The token
  alias is reused so downstream rules pick up the new accent
  automatically; instance theme overrides via
  `config.theme_overrides()` still win.
- VS Code surface tile carries a `Recommended` pill; the existing
  "Want to look around first?" section is renamed to `Explore your
  workspace` and gets the `#look-around` anchor.

All test-pinned class names and IDs (`install-hero`, `install-block`,
`home-mock`, `self-mark-btn`, `setupClaudeBtn`, `offboard-strip`,
`home-getting-started`, `home-gs-item`, `home-overview`,
`home-usage`) preserved as structural anchors; new visual language
overlays via additional classes. Existing onboarded/not-onboarded
branching, `/api/me/onboarded` POST, status frame gating, post-CTA
modal, and OS-tab switching JS unchanged. Stray `~/FoundryAI`
comment swapped for `~/{{ workspace_dir }}` to honor the
vendor-agnostic OSS rule.

51 home tests pass without modification.

* fix(web): /home palette inversion — dark intro hero on top, light setup card below

Previous reskin commit kept the install-hero as a dark navy gradient and
rendered the new intro hero as a light surface — opposite of what the CEO
mock specifies. Playwright comparison vs `data/ceo_home.html` confirmed:

- CEO mock: dark navy hero at TOP (with white pillars on navy), LIGHT
  white setup card BELOW with light step rows and dark code panels
  inset.
- Previous: light intro hero on top, dark setup card below. Inverted.

This patch flips both:

- `.home-hero-intro` now: dark navy gradient `#0f1b3a → #1a2a5f`, green
  radial glow in the corner, green eyebrow, white H1 (`accent` span
  green), rgba-white lede, green pill primary CTA, translucent-white
  secondary CTA, pillars row separated by hairline border-top with
  green square-dot bullets in front of each pillar header.
- `.install-hero` and `.install-block` now: white surface card with
  thin green accent strip across the top, light step rows split by
  hairline borders, green-tinted step-number circles (`#e6f9f0` bg,
  `#1f8a5e` ink), green progress chip + bar. Code panels
  (`.install-cmd`) and terminal frames stay dark — they're the "type
  this" surfaces.
- All previously-rgba-white descendants of `.install-hero`
  (close button, eyebrow, h1, lead, links, code chips, OS tabs,
  install notes, setup-CTA button, self-mark fallback, auto-detect
  badge, terminal-howto disclosure) re-skinned for light surface.

All 12 home page tests still pass (no markup changes, only CSS).

* fix(web): /home parity polish — system font + mock sizes + blue info hint + gray step-num

After v2 palette flip, user comparison vs CEO mock surfaced three
remaining gaps in the wizard area:

- Font stack mismatch: Agnes inherits Inter via `style-custom.css`,
  but the CEO mock uses the platform system stack (San Francisco on
  macOS, Segoe UI on Windows). The rendered weight/letterforms read
  noticeably different. `.home-mock` now declares
  `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`
  for itself and all descendants, with the monospace stack reserved
  for `code`/`kbd`/`pre`, `.install-cmd`, and `.terminal-body`.
- Step number badges were green-tinted; mock uses neutral gray
  (`#f0f2f6` bg, `#4a5168` ink) — green is reserved for the "done"
  state. Switched to `--hp-surface-dim` + `--hp-text-secondary`.
- "Don't have a terminal open?" disclosure was an amber/yellow
  variant left over from the old dark-hero palette. Mock uses a
  blue info-hint vocabulary (`--info-bg: #eef3ff`,
  `--info-line: #4f7cf2`, `--info-ink: #1c3994`) with white kbd
  chips. Added the info-* tokens to the `:root` block and re-skinned
  `details.terminal-howto` (incl. summary, body, kbd) to match.

Step-body type sizes also brought in line with the mock spec —
`.install-block .label` (step h3 equivalent) is now 17px / 700 with
6px gap; `.install-note` body type is 14px / 1.55.

`--hp-info-bg / --hp-info-ink / --hp-info-line / --hp-warn-bg /
--hp-warn-ink / --hp-warn-line / --hp-surface-dim` added as
first-class tokens so future hint/warn callouts pick the same colors
without a duplicate vocabulary.

12/12 home tests pass.

* feat(web): centralize design tokens + reword /home wizard to 6 steps (CEO mock parity)

Two intertwined changes that touch both global design + /home structure:

GLOBAL TOKEN SHIFT (app/web/static/style-custom.css)
- `--primary` flipped from blue `#0073D1` to green `#2ea877` — same brand
  alias the rest of the app referenced, so every page picks up the new
  accent automatically. Old `--primary-dark` / `--primary-light` recolored
  to match.
- New tokens added: `--brand-accent`, `--hero-bg`, `--hero-ink`,
  `--surface-dim`, `--info-bg/ink/line`, `--warn-bg/ink/line`. Brings
  the global vocabulary in line with the CEO mock's `:root` block so
  callouts and hero surfaces don't have to invent local tokens.
- `--font-primary` switched from Inter-led stack to the system stack
  (`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter",
  system-ui, sans-serif`) so weight/letterforms render identically on
  macOS (San Francisco) and Windows (Segoe UI) — matches the mock and
  avoids a font-loading flash for analysts without Inter installed.
- Shadow tints re-cast in navy `rgba(15,27,58,...)`; focus ring uses
  the new green `rgba(46,168,119,0.25)`.
- `.app-nav-link` font-size 13px → 14px, padding 6px 12px → 8px 14px,
  hover bg → `--primary-light` (mint), color → `--primary-dark`.
  `.app-nav-menu-item.is-active` re-tinted to the same green system.
- Sweep across 26 templates (style-custom.css + 25 template files)
  replacing every hardcoded `#0073D1` / `#005BA3` / `#E6F3FC` /
  `rgba(0,115,209,…)` / `rgba(0,86,163,…)` with token references or
  the new green hexes — 175 occurrences total. Pages that styled their
  own buttons / borders / shadows pick up the new brand color without
  per-page overrides.

/HOME WIZARD: 6 STEPS PER MOCK (app/web/templates/home_not_onboarded.html)
- Step 1 reworded `Install Claude Code on your computer` + `~3 min`
  subhead (mock copy).
- Step 2 renamed `Pick a folder for {{ instance_brand }}` (was
  `create your workspace folder`) — same `mkdir` command, mock-aligned
  framing.
- NEW Step 3 `Open a terminal inside that folder` — no shell command,
  just the "you are standing in the right directory" reassurance with
  a Finder/PowerShell/file-manager howto disclosure. Mirrors the CEO
  mock's Step 3.
- Step 4 (was Step 3, gated by `home_automode.show`) renamed
  `Launch Claude with auto-approve on`. Body copy lightly updated so
  it references "the next step" instead of "Step 4".
- Step 5 (was Step 4) renamed `Get the install script and paste it
  into Claude`. The setup-cta-lead now explicitly says
  "pasting the script into Claude Code will install {{ instance_brand
  }}…" so existing test assertions pinning the `install Agnes`
  substring still match.
- NEW Step 6 `Optional: create a one-word shortcut for next time` —
  prints an `echo 'alias {{workspace_dir|lower}}=…' >> ~/.zshrc`
  one-liner for Unix and an `Add-Content $PROFILE …` equivalent for
  Windows. OS tabs + copy buttons reuse the existing wizard chrome.
- Progress chip dynamic: `Step 1 of 6` when home_automode is on,
  `Step 1 of 5` when off. Progress bar fill `100 // total_steps` so
  the bar sits at 16-20 % on first paint.
- `.step-lede` token added for the new short body copy beneath each
  step label (14.5px / ink-soft).
- `macOS / Linux / WSL` tab labels changed to `macOS / Linux` per
  user instruction. Terminal-howto `WSL:` paragraph dropped; the
  paste-shortcut hint now reads `(Linux)` instead of `(Linux/WSL)`.
  Functional WSL handling in `connector_prompts.py` (it's a Linux
  detection fallback, not user-facing label) preserved.
- `setup_instructions.py` Claude Code install hint:
  `npm (Linux / WSL)` → `npm (Linux)`.

SURFACES — 4 CARDS PER MOCK
- Replaced the 3-tile `.home-usage-grid` with a 4-card grid:
  - VS Code (Recommended) — `.surface-card.feature`, green ring,
    DAILY USE eyebrow + 5-step numbered list + `Open VS Code setup
    guide →` link to `/setup-advanced#vscode`.
  - Terminal — QUICK ACCESS eyebrow + 4-step list.
  - Claude Code (Desktop app) — CONNECT IT eyebrow + 4-step list.
  - Cowork (claude.ai) — `.surface-card.incomplete`, warn-tinted
    border + `Instructions needed` badge + a TODO callout describing
    the missing content. The card is intentionally honest about the
    gap rather than hiding it.

TEST UPDATES
- `test_web_home_page.py` negative onboarded-state assertions
  rebased on the new step labels (6 entries instead of 4).
- `test_home_route_resolution.py` `test_home_renders_automode_block_by_default`
  + its `_when_env_off` counterpart now check the new
  `Step 4 — Launch Claude with auto-approve on` label.

* fix(web): /home section content + layout — verbatim mock match

User comparison flagged several remaining gaps; this patch rewrites
the three lower sections of /home to match the CEO mock spec exactly:

FIRST-SESSION (5 beats)
- h2 28px / 700 / -.5px tracking (was 19px / 600).
- lede 18px ink-soft (was 13.5px secondary).
- `.session-walk` wrapper, 36px gap between beats (mock spec).
- `.session-step` grid 48px / 1fr, gap 22px — number circle on
  the left, content on the right.
- `.session-num` 40 × 40 circle with SOLID GREEN bg (`--primary`)
  and WHITE text + soft green shadow (was 28px mint pill w/
  dark-green text).
- `.session-content h3` 18px / 600 (was 14.5px / 600).
- `.session-content > p` 15px.
- `.session-content .annotation` 13.5px ink-muted body type with
  `strong` for highlighting (replaces the upper-case "WHAT'S
  HAPPENING" eyebrow pattern that didn't match the mock).
- `.session-intro` callout card (white surface + mint icon block)
  framing the "five beats" tagline.
- `.session-tldr` summary box (brand-light bg + brand-dark left
  border) wrapping up the loop.
- Terminal frames re-skinned: `#0c1224` body / `#182241` bar /
  real macOS traffic-light colors `#ff5f57` / `#febc2e` / `#28c840`.
- Terminal body 13px / 1.65 line-height with mock-spec class
  vocabulary: `.you` (yellow input), `.ai-name` (brand bold),
  `.path` (light blue), `.dim` (translucent code-ink), `.caret`
  (blinking cursor).
- Five beats rewritten with mock's exact narrative flow (launch →
  menu → pick → ask → close), vendor-agnostic project names
  (`RevenueAnalysis`, `Onboarding`, etc.) replacing the customer-
  specific `GRPN_*` examples in the mock. Templated `{{
  instance_brand }}` / `{{ workspace_dir }}` / `{{ workspace_dir |
  lower }}` (the shortcut alias) everywhere.

SURFACES (4 cards)
- The section is no longer wrapped in a white rectangle; the
  `.home-usage` class loses its bg + border + padding (mock has the
  cards directly on the page bg).
- h2 28px (was 22px). Eyebrow 12px / 1.5px tracking / brand-dark.
- `.surface-card.feature` (VS Code) now uses 2px green border +
  vertical brand-light → white gradient (was 1px ring).
- `.surface-card.incomplete` (Cowork) uses 2px red border (`#e35e5e`)
  + vertical red-tint → white gradient (was yellow flat bg).
- `.surface-card .steps` panel: inner surface-dim bg + 8px radius
  + 13px font.
- `.surface-foot` top-border + ink-muted (mock spec).
- `.badge-warn` now a solid red box (`#e35e5e` bg + white ink + 4px
  radius) instead of a yellow pill, matching the mock.
- Header layout fixed: the global absorbed `header { display: flex;
  justify-content: space-between }` rule was making the h2 sit on
  the right of the eyebrow; explicit `display: block` override on
  `.home-mock section > header` puts the title on the LEFT under
  the eyebrow as the mock has.

BROWSE — Explore your workspace
- Wrapped in `<section class="browse-section">` with proper
  eyebrow + h2 + lede (was a bare `.section-label` div).
- `.browse-grid` 5-col grid (was responsive auto-fill, 4-card
  layout). Skills tile added as a 5th card linking to
  `/marketplace?type=skills`.
- `.browse-card` mock-spec: 22 20 padding, 28px icon, 15px title,
  12.5px ink-muted desc, hover lifts -2px with brand border +
  shadow-md.

Section wrappers (`.home-usage`, `.first-session`) no longer carry
the white card chrome — they sit directly on the page bg, matching
the mock. Only Getting Started + Overview keep their white cards.

GLOBAL eyebrow vocabulary (`.home-hero-intro .eyebrow`,
`.first-session > .eyebrow`, `.surfaces > header .eyebrow`,
`.browse-section .eyebrow`) all aligned to mock spec: 12px / 700 /
1.5px tracking / brand-dark color / 14px bottom margin.

Hero h1 bumped to 44px / 800 / -1px tracking (was 32px / 600).

51/51 home tests pass.

* fix(web): /home session-intro card + terminal-body verbatim mock match

User comparison flagged three remaining /home gaps; this patch
addresses each:

- `.session-intro` rule was missing — the "five beats" tagline
  rendered as a bare line with no card chrome. Added the mock-
  spec card: white surface, 14px radius, 20×24 padding, 1px
  border + shadow-sm, with a 44×44 brand-light icon block on the
  left.

- Beat 1 terminal-title was `~/{{ workspace_dir }} — zsh` (mock-
  style shell-pwd format), but the user wants every terminal
  frame across all 5 beats to read `claude — {{ instance_brand }}`.
  Updated.

- Terminal-body line structure for beats 2-5 rewritten verbatim
  from the CEO mock:
  - `<span class="prompt">&gt;</span><span class="you">…</span>`
    now has no space between the prompt and user input (mock
    pattern: zero gap, the .prompt's `margin-right: 8px` provides
    the visual separation).
  - Beat 2 menu items use `<strong>[N]</strong>` numbering with
    project entries on indented lines, each project name followed
    by a `<span class="dim">(N ago)</span>` timestamp at a fixed
    column — instead of my prior single-line concatenation.
  - Beat 3 narrative split into 4 stanzas separated by blank lines
    (matches mock): the "Switched to <strong>X</strong>" status,
    then dim Loaded/Last-session lines, then a stand-alone "One
    unprocessed input detected:" pair, then the "Want me to
    process …" question. My prior version dim-wrapped the entire
    block, which looked off.
  - Beat 4 narrative split into headline summary + risks section
    with <strong> heads + bullet lists separated by blank lines,
    matching the mock's "Q1 close summary" / "Open risks" rhythm.
    The Q1 question carries the mock's manual line-break + 2-
    space continuation indent inside the `.you` span — without
    that, terminal-body's `white-space: pre-wrap` would auto-wrap
    awkwardly at a different column than the mock.
  - Beat 5 exit narrative uses two separate dim lines + a
    standalone `.ai-name` "See you next time." line, then prompt
    + caret. My prior version collapsed everything into one dim
    block.
  - Project names changed from customer-specific (`GRPN_*`) to
    generic (RevenueAnalysis, WeeklyReview, Onboarding, OpsDb,
    HRHandShake) so the OSS distribution stays vendor-agnostic
    per CLAUDE.md.
  - `Marketing plan` examples replaced with `Q1 close` so the
    narrative stays plausible for an analyst audience.

12/12 home tests pass.

* fix(web): /home surfaces verbatim mock — VS Code thumb, Terminal expected-output, NEW badge

User comparison flagged three remaining surface-section gaps:

- VS Code surface card was rendering a generic "Screenshot pending"
  placeholder; the mock has a labeled inline mockup
  (`<a class="vscode-thumb">` w/ `.thumb-fallback`) showing the
  recommended 4-pane layout (EXPLORER yellow, TERMINAL 1 purple,
  TERMINAL 2 green, TERMINAL 3 orange) on a dark navy bg + a
  "Recommended layout" caption pill. CSS `.vscode-thumb` block
  added — uses gradient-strip backgrounds to draw the colored
  panel bars without needing a base64 image.

- "Recommended" badge was a pill (999px radius) with
  `--brand-accent` bg + navy text. Mock uses `.badge` instead of
  `.recommend-pill` — solid `--primary` (brand-dark green) bg
  with WHITE text and 4px radius. Replaced the class + CSS rule
  so the badge reads as a tag, not a pill.

- Terminal surface card was missing the "What you should see"
  subsection — mock has an `.expected-output` block showing a
  sample of the welcome menu inside a dim dashed panel. Added the
  block with the mock's exact rendered output (templated to
  `{{ instance_brand }}` + generic project names instead of
  customer-specific GRPN entries) plus the `.expected-output`
  CSS (surface-dim bg + dashed border + `::before` "WHAT YOU
  SHOULD SEE" eyebrow per mock spec).

Also addressed the explore-section feedback:

- Skills browse-card now carries the `new` class so it picks up
  the `.browse-card.new::after` corner badge ("NEW", green bg,
  white text, 10px / 700 / 0.5px tracking) per mock.
- Browse cards align same height via `align-self: stretch` (grid
  default) + `flex-grow: 1` on `.browse-desc` so descriptions
  fill remaining vertical space; previously the Skills tile sat
  shorter because its desc text was longer than others'.

Structural HTML changes to all four surface cards: dropped the
inner `<div class="surface-card-head">` wrapper + `<p
class="surface-pitch">` class in favor of mock's flat layout
(`.what` + `.steps` + `.when-to-use`). `<ol class="surface-steps">`
replaced with `<div class="steps"><strong
class="steps-eyebrow">DAILY USE / QUICK ACCESS / CONNECT IT</strong>
<ol>...</ol></div>` so the eyebrow + numbered list share the
mock's tinted surface-dim panel.

12/12 home tests pass.

* fix(web): align /home setup walkthrough to design spec

- Setup-section header (eyebrow + heading + lede) floats above the
  install hero; install card has no accent strip; step labels drop
  `Step N —` prefix; closing strip is single flex row.
- VS Code surface card renders recommended-layout screenshot from
  `/static/img/vscode-layout.png` with click-to-enlarge lightbox.
- Workspace install path cascades to `~/Desktop/{workspace_dir}` in
  every step, surface card, first-session annotation, and shortcut.
- Step 1 verify text restores Enterprise — Finance and Legal option.
- Step 6 shortcut installs a shell function with arg forwarding
  (`"$@"` unix / `@args` windows) and a user-facing Auto / YOLO
  permission-mode toggle.
- Step 5 manual-fallback details inline on the CTA row; description
  reads at step-lede size, not 13px chip.
- Setup-section heading no longer right-aligns (was inheriting
  `header { display: flex; justify-content: space-between }` from
  the legacy stylesheet; wrapper changed to `<div>`).
- Getting Started `<details>` block removed (duplicated links).

* test(web): align /home tests with restructured setup wizard

- Replace test_getting_started_card_renders_on_home with
  test_setup_section_renders_for_not_onboarded — asserts the new
  setup-section-header floats above the install hero and Getting
  Started markup is absent (block removed in the prior commit).
- Update automode-block test to match labels without the
  `Step N —` prefix.
- Update setup-CTA partial test to match the relabeled
  "Copy install script to clipboard" button.

Drop orphaned CSS for `.home-getting-started`, `.home-gs-summary*`,
and `.home-gs-item` — selectors had no matching markup after the
Getting Started block was removed.

Also: Step 3 `pwd` expected-output uses an absolute path
(`/Users/yourname/Desktop/{workspace_dir}`) instead of the
tilde-prefixed form, matching what the command actually prints.

* fix(web): repaint home_onboarded + setup_advanced; align CTA label

- home_onboarded + setup_advanced still carried the retired blue
  `#0056A3` as both `--hp-primary-dark` and the hero gradient
  endpoint. Both reference `var(--primary-dark)` now so the green
  palette cascades.
- setup_advanced YOLO snippet was the old `alias` form (no cd, no
  arg forwarding). Replaced with the shell function variant from
  /home Step 6 — drops into ~/Desktop/{workspace_dir} and forwards
  "\$@" (unix) / @args (Windows).
- setup_advanced ~/{workspace_dir} path references cascaded to
  ~/Desktop/{workspace_dir} so install story matches /home.
- Dashboard's "Setup a new Claude Code" button label aligned to the
  canonical "Copy install script to clipboard" — matches /home and
  the new docstring in _claude_setup_cta.jinja, which now mandates
  this label across consumers.

* fix(web): keep base brand blue; scope green palette to /home redesign

User noticed login + dashboard had turned green when the /home
redesign flipped --primary from blue (#0073D1) to green (#2ea877)
in commit 278f202e. The brand-wide flip went further than the
redesign needed — only /home, /home (onboarded), and /setup-advanced
intentionally use the green/navy spec; every other page (login,
dashboard, catalog, marketplace, admin, profile) was just inheriting
the green because --primary cascaded everywhere.

Revert the global brand colour to blue and lock the green into the
two outstanding redesign scopes:

- style-custom.css: --primary back to #0073D1, --primary-light back
  to rgba(0,115,209,0.1), --primary-dark back to #005BA3,
  --brand-accent back to a lighter blue.
- home_onboarded.html: .home-mock now sets --hp-primary,
  --hp-primary-dark, --hp-primary-light to explicit green hex
  (matching home_not_onboarded), so the hero stays green regardless
  of the global brand.
- setup_advanced.html: same lock — .advanced-mock pins the green
  palette in-scope.

Hero gradients on both pages now reference the local --hp-primary
chain (not the global --primary), so any future palette tweak inside
either scope cascades correctly without disturbing the rest of the app.

* refactor(web): hoist --hp-* into shared design-tokens.css (--ds-*)

PR 2 of the design-system extraction ladder. Pure mechanical rename
+ dedup; no visual diff on any rendered page (verified on /home,
/dashboard).

- New app/web/static/css/design-tokens.css declares the full token
  set on :root: brand surface (green primary, primary-dark, mint
  light, brand-accent), hero (navy bg + ink), code-panel (near-black
  bg + cool ink + warm-yellow), light surfaces (bg/surface/border),
  text (primary/secondary/muted), orange accent, info + warn
  callout vocabularies, navy-tinted elevation shadows, system font
  stack + mono.
- base.html loads it alongside style-custom.css so the tokens are
  globally available.
- Rename --hp-* -> --ds-* in home_not_onboarded (313 refs),
  home_onboarded (15), setup_advanced (39). 367 token references
  pointed at one of three local blocks; now all point at the
  global :root.
- Drop the three local token blocks. Each scope class
  (.home-mock / .advanced-mock) only keeps its base ink + font-size
  + line-height rules.

The legacy --primary family stays canonical for the blue base
brand — login, dashboard, catalog, marketplace, admin still read
blue. The design system is opt-in via the scope class.

* refactor(web): extract shared components.css; migrate /home markup

PR 3 of the design-system extraction ladder. First batch of
reusable components lifted out of home_not_onboarded.html into a
new shared stylesheet; markup migrated to consume them.

- New app/web/static/css/components.css with five components, all
  reusable on any page that loads design-tokens.css:
    .callout-rec        — amber lightbulb recommendation box
    .callout-hint       — blue info hint box
    .code-output        — "WHAT YOU SHOULD SEE" terminal output block
    .lightbox           — full-bleed image enlarge overlay
    .setup-section-header — wizard header (eyebrow + h2 + lede)
- base.html loads components.css after design-tokens.css.
- home_not_onboarded.html markup renamed:
    class="rec"             -> class="callout-rec"
    class="hint"            -> class="callout-hint"
    class="expected-output" -> class="code-output"
- Local CSS rules removed from home_not_onboarded.html for each of
  the extracted components — ~150 lines down to 5-line "extracted to
  components.css" comments. The bespoke wizard-specific styles
  (.install-cmd, .os-tabs, .mode-tabs, .terminal-frame) stay
  template-local for now since they only have one consumer.

Visual regression check: /home install hero renders the amber rec
callout, blue hint callout, dashed code-output block, green section
header, and click-to-enlarge VS Code thumb identically to the
pre-extraction render. 43 home tests pass.

* fix(web): unify page-headers — activity-center full-width, marketplace shares box

- /activity-center audit-log hero rendered as half-width because the
  _page_hero include was inside <header class="obs-topbar">, a flex
  row that pinned the time-range + auto-refresh controls next to it.
  The hero is now a sibling rendered before the <header>, so it
  spans the full container width like every other admin page; the
  controls keep their flex row underneath.
- Marketplace hero unified with .page-header--hero. Markup is now
  <section class="page-header page-header--hero mp-hero"> so the
  shared box drives padding/radius/gradient/max-width/shadow; the
  .mp-hero override block only carries the right-anchored cover
  image and the rules for the search row + scope checkboxes (which
  the canonical hero doesn't have). Inner text uses the canonical
  .page-header__eyebrow / __title / __subtitle classes.
- .page-header--hero shadow tint now follows the brand blue
  (rgba(0, 115, 209, 0.2)) instead of the leftover green from the
  prior palette flip; same depth highlight everywhere the gradient
  is blue.

* fix(web): unify remaining page heroes — admin, profile, install, store, stack

Sweep across pages that carried bespoke gradient hero markup so
every page-hero shares the canonical `.page-header--hero`
dimensions (padding 28/32/24, border-radius 14, max-width
var(--width-app), navy-tinted shadow, gradient with --primary →
--primary-dark). Inner text uses the .page-header__eyebrow /
__title / __subtitle classes so typography matches across the app.

- admin_tables: migrated to _page_hero.html include.
- admin_tokens: kept .tokens-hero wrapper for the counts-chip row
  but added the canonical class on the same element; stripped
  duplicate gradient + padding + typography rules.
- install: same pattern (kept hero-meta pill row).
- profile: migrated to _page_hero.html include.
- store_upload: kept .upload-hero wrapper for the .meta chip row;
  composite class with the canonical hero.
- setup_advanced: .advanced-mock .ad-hero now matches canonical
  dimensions; green palette retained via --ds-primary/dark.
- stack_card.css: .stack-hero (catalog + corporate-memory search
  hero) uses canonical gradient + padding + max-width.

The detail-page heroes (marketplace_plugin_detail,
marketplace_item_detail, catalog_*_detail, store_edit,
admin_group_detail, admin_store_submission_detail) stay bespoke
for now — they're rich detail headers with photos, badges, install
actions; converting them would lose contract context. Same applies
to dashboard.html env-setup-cta (it's a CTA card, not a page hero).

* fix(web): canonicalise .container — single page shell every page inherits

Previously each admin page set its own `.container:has(.<page>)
{max-width: none}` + `.<page>-page {max-width: 1400px}` override,
and per-page hero markup either nested inside flex toolbars (which
pinned the hero next to filter controls and squeezed it half-width)
or self-constrained with a different max-width than the page. /home,
/dashboard, /marketplace, and /admin/* ended up at different widths
with different nav-to-hero gaps.

- style-custom.css `.container` now carries the canonical 1280px
  max-width + `16px 32px 48px` padding so every page inherits the
  same nav-to-hero gap and side gutters. `.container > main` is
  margin/padding 0 so the container is the sole owner of gutters.
- `.page-header--hero` drops its self-constraining max-width and
  auto-centering margin — the container provides the width, so the
  hero sits flush with the table/toolbar below it.
- `.stack-hero` (catalog + corporate-memory) and `.advanced-mock
  .ad-hero` (/setup-advanced) follow the same pattern: container
  owns the width.
- Per-page max-width overrides stripped from admin_users,
  admin_access, admin_groups, admin_marketplaces, admin_welcome,
  admin_workspace_prompt.
- _page_hero include extracted from inside flex toolbars on
  admin_users, admin_access, admin_groups, admin_marketplaces,
  admin_server_config, admin_welcome, admin_workspace_prompt,
  admin_sessions, admin_session_detail, admin_usage,
  activity_center. The toolbar (`.users-toolbar`, `.gp-toolbar`,
  etc.) keeps only the filter + action controls; hero renders
  before it as a sibling.
- _page_chrome.html trimmed to just the page-background tint for
  the redesign scopes; the duplicate `.container` rules it carried
  are now redundant.

Verified: /home, /admin/marketplaces, /admin/users all render
container width 1280px with hero top at 88px (16px below the
72px-tall sticky nav). Same spacing as /home design spec.

* fix(web): admin_tables + admin_corporate_memory inherit canonical .container

Both pages were overriding `{% block layout %}` from base.html,
which bypasses the canonical `.container` wrapper. Result: hero
span the full viewport (1596px on a wide screen) while the inner
content sat at a narrower max-width — hero and content didn't
align, and the nav-to-hero gap differed from every other admin
page.

Switched both templates to `{% block content %}` so they render
inside the canonical `.container` from base.html — same path as
admin_groups, admin_users, admin_marketplaces, etc.

- admin_tables: dropped local `.page-title { max-width: 1600px }`
  + `.content { max-width: 1600px }` overrides (kept typography +
  inner gutter rules) and the mobile padding overrides that paired
  with them. Container now owns the gutters.
- admin_corporate_memory: only the block keyword needed changing;
  the template already had a clean inner structure (no max-width
  override on `.container-memory`).

Verified on /admin/tables and /admin/corporate-memory:
- .container width 1280, padding 16/32/48
- Hero top 88 (nav 72 + container padding-top 16)
- Hero + content both 1216px wide, both at left 190 — perfect
  alignment with /admin/groups.

* fix(web): drop .page-shell padding override + admin_tables stale :root

Two regressions discovered after the canonical-container unification:

1. `.container:has(.page-shell)` still set `padding: 28px 32px 48px`
   while the canonical `.container` had moved to `16px 32px 48px`.
   Every page-shell consumer (/admin/sessions, /admin/sessions/<id>,
   /admin/usage, /marketplace, /dashboard, marketplace detail pages,
   /me/activity, /store/*, /admin/store-submissions) was rendering
   with a 28px nav-to-hero gap while /admin/users + /admin/groups
   rendered with 16px. Same width, mismatched vertical rhythm.
   The opt-in rule is now a no-op marker: canonical container
   already provides 1280px + 16/32/48 + main margin/padding 0.

2. admin_tables.html had a stale `<style>` block that re-declared
   `:root { --primary: var(--primary); ... }`. The self-referential
   token resolved to empty, collapsing the page-header hero's
   `linear-gradient(135deg, var(--primary), var(--primary-dark))`
   to no background — the hero appeared as a pale ghost without
   colour. The entire shadow `:root` block was a stale copy of the
   design tokens that style-custom.css already provides. Dropped
   it; tokens now resolve from the global `:root`.

After both fixes /admin/sessions, /admin/tables, and every other
page-shell consumer match /admin/groups exactly: container 1280px,
container padding-top 16px, hero at top 88px / left 190px / width
1216px.

* fix(web): drop /admin/tokens .tokens-page width + padding override

`.tokens-page` carried its own `max-width: 1280px; margin: 0 auto;
padding: 28px 8px 48px` block — the canonical `.container` already
provides width + 16/32/48 padding, so the nested wrapper was
adding 28px on top of the container's 16px (= 44px nav-to-hero
gap, vs 16px on every other admin page) and shrinking the hero
sideways by 8px on each side (1200px vs the canonical 1216px).

After: container owns the layout; `.tokens-page` is just a
font-family scope. /admin/tokens hero now sits at top 88, left 190,
width 1216 — same numbers as /admin/groups / /admin/users.

* fix(web): hero links readable on blue; /admin/access Groups link href

- New `.page-header--hero a` rule in style-custom.css forces any
  anchor inside a gradient hero to render white + underlined so
  links stay readable on the blue background. Previously links
  inherited the global `var(--primary)` blue, which disappeared
  on top of the matching blue gradient. No per-page class needed —
  drop a plain `<a>` in any hero subtitle and it just works.
- /admin/access hero subtitle was Jinja-passing the inline link
  with HTML-entity-encoded quotes (`href=&quot;...&quot;`). The
  entities decoded to literal `"` characters inside the rendered
  href, producing `/admin/%22/admin/groups%22` — a 404. Switched
  the `set` to a block-set (`{% set page_hero_subtitle %}...{% endset %}`)
  so the inline `<a href="/admin/groups">Groups</a>` survives
  unescaped through `_page_hero.html`. Also stripped the now-redundant
  inline `style="color:#fff;text-decoration:underline;"` — the new
  shared rule handles it.

* fix(web): /dashboard top padding matches every other page

`.main` on /dashboard had `padding: 28px 32px 48px` while every
other page now uses `16px 32px 48px` via the canonical
`.container`. Dashboard bypasses `.container` (overrides
base.html's `layout` block to render a full-width `<main>`
directly), so the padding lives on `.main` itself — bumped the
top to 16px to match.

After: first child top = 88, left = 190, width = 1216 — same
numbers as /admin/groups / /admin/users / /admin/marketplaces.

* fix(web): green eyebrow + white title on .page-header--hero (matches /home)

`.page-header--hero .page-header__eyebrow` was faint white
(rgba(255,255,255,0.75)) — readable but unbranded against the blue
gradient. Changed to `var(--ds-brand-accent)` (mint green #54d3a0)
so every page hero pairs a green eyebrow with white title +
subtitle, echoing /home's setup-section header (green eyebrow,
dark heading combo). One CSS rule applies everywhere — no
per-page styling needed.

Also bumped the eyebrow to font-weight 700 / letter-spacing 1.2px
so the green stands out cleanly against the gradient.

* fix(web): page-header--hero + stack-hero use /home navy gradient

`.page-header--hero` and `.stack-hero` were on the brand-blue
gradient (`var(--primary)` → `var(--primary-dark)`) while
/home's hero (`.home-hero-intro`) sits on the deeper navy
gradient (`#0f1b3a` → `#1a2a5f`). Every other page-hero now
uses that same navy gradient so /home, /marketplace, /catalog,
/corporate-memory, /admin/*, /profile, /install, /dashboard,
/setup-advanced share one brand surface. Shadow tint adjusted
to the navy depth (rgba(15, 27, 58, 0.22)).

Brand blue stays the link/CTA colour everywhere else; only the
hero box itself is navy.

* fix(web): primary buttons green; marketplace tabs navy translucent

Two parity tweaks pulling the rest of the app toward /home's
visual language.

- `.btn-primary` (both rules in style-custom.css) now uses
  `var(--ds-primary)` / `var(--ds-primary-dark)` green fill,
  matching the "Copy install script to clipboard" button on
  /home. Brand-blue `--primary` still drives link colour and the
  accent surface; only the filled button background flipped to
  green. Every page with a `.btn-primary` (admin "+Add user",
  "+Add marketplace", catalog, marketplace actions, dashboard,
  modals) now reads as the same "do it" affordance.
- `.mp-tabs` (Curated Marketplace / Flea Market / My Stack tab
  group) now sits on the navy `--ds-hero-bg` with translucent
  white pills (rgba(255,255,255,0.10) inactive, 0.18 active) —
  same translucent-white-on-navy treatment as the "Just browse —
  no install needed" pill on /home. Icons render as soft white;
  per-tab colour-coding dropped in favour of the unified surface.

* fix(web): catalog/memory tabs + empty-state CTA + admin action buttons

Bring /catalog and /memory in line with /home + /marketplace:

- `.stack-tabs` (Browse / My Stack / Recipes on /catalog,
  Browse / My Stack on /memory) now uses the navy `--ds-hero-bg`
  container with translucent-white-on-navy pills, mirroring the
  `.mp-tabs` treatment and /home's "Just browse — no install
  needed" CTA pill. Per-tab icon colour-coding dropped — icons
  render as soft white on the navy fill.
- `.stack-tabs-row__actions .btn` (right-slot "+New Recipe",
  "+New Data Package" admin CTAs) now uses green primary fill
  (`--ds-primary`), matching `.btn-primary` and /home's
  "Copy install script to clipboard" button.
- `.stack-empty .cta a` (empty-state action button — the
  "Open /admin/tables →" CTA on /catalog and equivalent on
  /memory) flipped from blue `--primary` to green `--ds-primary`
  so the colour aligns with every other primary button in the app.

* fix(web): marketplace Search button green (--ds-primary) matching other CTAs

* fix(web): unify Search button + admin-action button across browse pages

- Added Search button (`<button class="stack-hero__search-btn">`)
  to /catalog and /memory heroes — same green pill as /marketplace.
  Wired to the existing live-filter pipeline (button click runs
  `applyFilters()` and refocuses the input). All three browse pages
  now wear the identical search bar UI.
- `.stack-hero__search-btn` shares `--ds-primary` fill with
  `.mp-hero .search-btn`.
- `.mp-actions .btn` ("Submit a skill or plugin" CTA on /marketplace)
  flipped from the legacy blue-outline to the same green primary
  fill + dimensions (`display: inline-flex; line-height: 1;
  padding: 9px 16px; gap: 6px`) as `.stack-tabs-row__actions .btn`
  on /catalog and /memory. All three right-slot action buttons
  render at identical height now.
- `.stack-tabs-row__actions .btn` got `inline-flex` + `line-height: 1`
  + `gap: 6px` so a `<button class="btn">` and a `<a class="btn">`
  both render at exactly 33px high — the embedded
  `.admin-only-hint` chip no longer pushes one variant taller
  than the other.

* fix(web): marketplace guide CTAs green (fastpath + primary); drop flea purple

* fix(web): dashboard CTA hero on navy; readable <code> chips in hero

- `.env-setup-cta` on /dashboard ("Set up a new Claude Code"
  card) flipped from the brand-blue gradient + green-tinted shadow
  to the canonical navy gradient (`--ds-hero-bg` → `#1a2a5f`) with
  navy-tinted shadow + 14px radius + 28/32/24 padding, matching
  `.page-header--hero` and /home's `.home-hero-intro`. Dashboard's
  top CTA now sits on the same brand surface as every other hero.
- Added `.page-header--hero code` rule — translucent white pill +
  warm-yellow ink (#ffd866) so `<code>` chips embedded in hero
  subtitles read as code samples against the navy gradient. The
  global `code` rule sets `color: var(--text-primary)` (dark),
  which turned in-hero chips into invisible dark-on-white-on-navy
  ghosts (e.g. the `-by-dev` suffix on /store/new).
- /store/new's `.page-header__subtitle code` dropped its inline
  style override — the shared rule handles it now.

* feat(web): two-theme switching via data-theme + admin toggle

Introduces a theme system that flips the entire UI palette between
"navy" (current design, default) and "blue" (pre-redesign palette)
via a single `<html data-theme="...">` attribute. Page markup, class
names, and component styles don't change — only the `--ds-*` token
values flip.

Backend
- New `app/instance_config.py::get_instance_theme()` resolves the
  active theme from `AGNES_INSTANCE_THEME` env > `instance.theme`
  in instance.yaml > default "navy". Unrecognised values clamp to
  "navy" so a typo doesn't break the page.
- `app/web/router.py::_build_context` injects `instance_theme`
  alongside `instance_brand` etc. so every template inherits it.
- `app/web/templates/base.html` renders
  `<html lang="en" data-theme="{{ instance_theme | default('navy') }}">`.

CSS
- `app/web/static/css/design-tokens.css` adds two new tokens to
  the default `:root` set: `--ds-hero-shadow` (drop-shadow tint
  on hero boxes) and `--ds-hero-eyebrow` (eyebrow accent colour).
  Plus a `:root[data-theme="blue"]` override block that flips
  seven tokens: `--ds-primary`, `--ds-primary-dark`,
  `--ds-primary-light`, `--ds-brand-accent`, `--ds-hero-bg`,
  `--ds-hero-bg-deep`, `--ds-hero-shadow`, `--ds-hero-eyebrow`.
  The blue theme aliases the brand surface tokens back to the
  legacy `--primary` family.
- `.page-header--hero`, `.stack-hero`, `.env-setup-cta`,
  `.home-mock .home-hero-intro` now reference the new
  `--ds-hero-shadow` and `--ds-hero-bg-deep` tokens instead of
  hard-coding `rgba(15, 27, 58, 0.22)` and `#1a2a5f` — gradient +
  shadow now flip with the theme.
- `.page-header--hero .page-header__eyebrow` uses
  `var(--ds-hero-eyebrow)` so the eyebrow goes mint-green on
  navy and translucent-white on blue (mint on blue reads poorly).

Admin
- `app/api/admin.py::_KNOWN_FIELDS["instance"]` now registers a
  `theme` field of kind `select` with options `["navy", "blue"]`
  and a `hint` explaining the trade-off. The existing
  /admin/server-config UI auto-renders a select for this — no
  template changes needed.

Defaults
- Default value is "navy" so existing instances see no visual
  change. Admins flip to "blue" via /admin/server-config to
  restore the pre-redesign look.

Restart note: uvicorn must reload to pick up the Python changes
(new getter, new template-context key, new known-field). CSS
changes hot-reload via browser refresh.

* fix(web): blue theme — home hero eyebrow + CTA contrast

`.home-hero-intro .eyebrow` and `.btn-intro-primary` referenced
`--ds-brand-accent` directly, which on the blue theme resolves to
the lighter brand-accent blue (#4F9DEB). Result: light-blue eyebrow
on the blue gradient ("WELCOME, ADMIN" barely readable) and a
light-blue button with darker-blue text ("Set up in ~15 min")
that all sat in the same hue range.

Introduces three new theme-aware tokens:
- `--ds-hero-eyebrow` already existed; blue theme bumped opacity
  to 0.92 so the eyebrow reads as full white.
- `--ds-hero-cta-bg` + `--ds-hero-cta-fg` + `--ds-hero-cta-bg-hover`
  flip the primary hero CTA: mint-green on navy (default), white-
  on-blue under `data-theme="blue"`.

`.home-hero-intro .eyebrow` now uses `--ds-hero-eyebrow` (mint on
navy / white on blue) and `.btn-intro-primary` uses the CTA token
trio.

Recommended palette on blue theme:
- Eyebrow: white at 92% opacity (clear on the blue gradient).
- Primary CTA pill: white background, brand-blue dark text
  (`--primary-dark` = #005BA3) for AAA-level contrast.
- Secondary CTA: translucent white pill (unchanged).

* fix(web): blue theme — callout-hint info bg/border/ink re-tinted to brand blue (was indigo, clashed with brand-blue hero)
2026-05-21 06:19:16 +00:00

4520 lines
103 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Design System - Data Analyst Portal v2 */
:root {
/* Colors — `--primary` is the canonical app-wide brand colour
(blue), used across nav, buttons, links, and active states on
login, dashboard, catalog, marketplace, admin, etc. The /home
redesign opted into a green/navy palette via local
`.home-mock` / `.advanced-mock` overrides; those scopes set
their own `--hp-primary` so the green only applies where the
redesign explicitly opts in. Everything else inherits blue. */
--primary: #0073D1;
--primary-light: rgba(0, 115, 209, 0.1);
--primary-dark: #005BA3;
--brand-accent: #4F9DEB;
--hero-bg: #0f1b3a;
--hero-ink: #f3f6ff;
--text-primary: #0e1525;
--text-secondary: #4a5168;
--background: #f6f7fa;
--surface: #ffffff;
--surface-dim: #f0f2f6;
--border: #e4e7ee;
--border-light: #f0f2f6;
--border-dark: #c4c4c4;
--success: #10B77F;
--warning: #F59F0A;
--error: #EA580C;
--info-bg: #eef3ff;
--info-ink: #1c3994;
--info-line: #4f7cf2;
--warn-bg: #fff8e6;
--warn-ink: #6e4d00;
--warn-line: #f5c84b;
/* Typography — system stack matching the CEO mock so weight +
letterforms render identically on macOS (San Francisco) and
Windows (Segoe UI). Inter remains a graceful fallback in the
cascade. */
--font-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", system-ui, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
/* Font sizes */
--text-xs: 10px;
--text-sm: 12px;
--text-base: 14px;
--text-md: 16px;
--text-lg: 18px;
--text-xl: 24px;
--text-2xl: 30px;
/* Font weights */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
--font-extrabold: 800;
/* Extra text colors — referenced by inline page styles that should
eventually migrate (home hero used --hp-text-muted, etc.) */
--text-muted: #9CA3AF;
--text-disabled: #D1D5DB;
/* Spacing — 4-multiple scale. Gaps filled to cover dashboard /home
inline-style values (28px / 40px / 48px / 64px). */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--space-8: 32px;
--space-9: 40px;
--space-10: 48px;
--space-12: 64px;
/* Border radius */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-full: 9999px;
/* Shadows. --shadow-card is the canonical card elevation;
--shadow-elevated lifts hero blocks. Shadow tints aligned with
the new green/navy palette so heavy elevations don't carry a
leftover blue cast. */
--shadow-sm: rgba(15, 27, 58, 0.05) 0px 1px 2px 0px, rgba(15, 27, 58, 0.06) 0px 1px 3px 0px;
--shadow-md: rgba(15, 27, 58, 0.08) 0px 4px 16px 0px;
--shadow-card: 0 1px 3px 0 rgba(15, 27, 58, 0.06), 0 1px 2px -1px rgba(15, 27, 58, 0.05);
--shadow-elevated: 0 8px 24px rgba(15, 27, 58, 0.14);
/* Focus + interaction */
--focus-ring: 0 0 0 3px rgba(46, 168, 119, 0.25);
--transition-fast: 120ms ease;
--transition-base: 200ms ease;
--transition-slow: 320ms ease;
/* Layout — outer chrome widths. Pages should reference these instead
of hardcoding 1280 / 1200 / 800 to keep /home and /dashboard in
step. */
--width-narrow: 800px;
--width-app: 1280px;
--width-wide: 1400px;
/* Legacy token aliases — keep absorbed style.css rules rendering
until Task 16 cleanup. Each legacy name maps onto its modern
equivalent so every absorbed selector resolves to a real value
instead of falling back to `unset`. */
--bg: var(--background);
--card-bg: var(--surface);
--text: var(--text-primary);
--text-light: var(--text-secondary);
--secondary: var(--text-secondary);
--radius: var(--radius-lg);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-primary);
background-color: var(--background);
color: var(--text-primary);
font-size: var(--text-base);
line-height: 1.5;
min-height: 100vh;
}
/* =====================================================
Absorbed from the deleted app/web/static/style.css.
Kept verbatim here so legacy classes used by login/auth
pages, error pages, and password-setup pages keep
rendering. Individual rules retire in the design-pass
migration tasks; final cleanup in Task 16 removes the
ones no template references after that sweep.
===================================================== */
.container {
/* Canonical page-shell width + gutters. Pages inherit this
directly — no per-page max-width / padding override needed.
The 16px / 48px asymmetric vertical padding gives a tight gap
between the sticky nav and the first hero (matches /home) and
generous breathing room above the footer. Side gutters are
32px so dense tables / grids align to the same vertical rules
as the canonical `.page-header--hero`.
Pages that genuinely need narrower line-length (setup wizards,
single-form flows) opt in via .container--narrow; wide tables
opt in via .container--wide. */
max-width: var(--width-app);
margin: 0 auto;
padding: 16px 32px 48px;
}
/* Strip default `<main>` padding/margin when it sits inside our
canonical `.container`. The container already owns the gutters,
so the `<main>` element is a pure flow container. */
.container > main {
margin: 0;
padding: 0;
}
/* Width modifiers — opt-in alternatives to the default page shell. */
.container--narrow { max-width: var(--width-narrow); } /* 800px — long-form reading, setup flows */
.container--wide { max-width: var(--width-wide); } /* 1400px — admin index lists, marketplace grids */
.container--full { max-width: none; padding-left: var(--space-4); padding-right: var(--space-4); }
/* Dashboard */
.dashboard h2 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 24px;
color: var(--text);
}
/* Header */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
margin-bottom: 24px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
gap: 16px;
}
a.logo { text-decoration: none; color: inherit; display: block; }
a.logo:hover h1 { color: var(--primary); }
.logo h1 {
font-size: 1.375rem;
font-weight: 600;
color: var(--text);
margin: 0;
letter-spacing: -0.02em;
}
.logo .subtitle {
font-size: 0.8125rem;
color: var(--text-light);
margin-top: 2px;
}
nav {
display: flex;
align-items: center;
gap: 15px;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.875rem;
color: var(--text-light);
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 20px;
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
border-radius: var(--radius);
border: none;
cursor: pointer;
transition: background-color 0.2s, box-shadow 0.2s;
}
.btn-primary {
/* Primary action buttons across the app share /home's green CTA
fill (`--ds-primary`) so users see one consistent "do it"
affordance everywhere. The brand-blue `--primary` stays as
the link / accent colour elsewhere; only the filled button
background flips to green. */
background-color: var(--ds-primary);
color: white;
font-weight: 500;
transition: all 0.15s ease;
}
.btn-primary:hover {
background-color: var(--ds-primary-dark);
box-shadow: 0 2px 8px rgba(46, 168, 119, 0.3);
}
.btn-primary:active {
transform: scale(0.98);
}
.btn-secondary {
background-color: transparent;
color: var(--secondary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background-color: var(--bg);
}
.btn-sm {
padding: 6px 12px;
font-size: 0.75rem;
}
.btn-google {
background-color: white;
color: var(--text);
border: 1px solid var(--border);
padding: 14px 28px;
font-size: 1rem;
font-weight: 500;
width: 100%;
max-width: 280px;
transition: all 0.2s ease;
}
.btn-google:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
border-color: var(--border-dark);
background-color: var(--background);
}
.btn-google:active {
transform: scale(0.98);
}
.google-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
}
/* Cards */
.card {
background: var(--card-bg);
border-radius: var(--radius);
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
border: 1px solid var(--border);
}
.card h3 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 16px;
color: var(--text);
letter-spacing: -0.01em;
}
.card p {
color: var(--text-light);
margin-bottom: 12px;
}
.card-error {
border-left: 4px solid var(--error);
background-color: #fff8f8;
}
.card-highlight {
border-left: 4px solid var(--primary);
background-color: #f8faff;
}
.card-ai {
border-left: 4px solid var(--success);
background-color: #f8fdf8;
}
/* Manual setup collapsible */
.manual-setup {
margin-top: 16px;
}
.manual-setup summary {
cursor: pointer;
font-size: 0.875rem;
color: var(--text-light);
padding: 8px 0;
}
.manual-setup summary:hover {
color: var(--text);
}
.manual-setup .card {
margin-top: 12px;
margin-bottom: 0;
}
.card-error h3 {
color: var(--error);
}
.error-message {
color: var(--error);
font-weight: 500;
}
/* Info grid */
.info-grid {
display: grid;
gap: 12px;
}
.info-item {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.info-item .label {
font-weight: 500;
min-width: 140px;
}
.info-item .value {
color: var(--text-light);
}
/* Badges */
.badge {
display: inline-block;
padding: 2px 8px;
font-size: 0.75rem;
font-weight: 500;
border-radius: 4px;
background-color: var(--bg);
color: var(--text-light);
margin-right: 4px;
}
.badge-analyst {
background-color: #e8f5e9;
color: #2e7d32;
}
.badge-privileged {
background-color: #fff3e0;
color: #ef6c00;
}
.badge-admin {
background-color: #e3f2fd;
color: #1565c0;
}
/* Code blocks */
code {
font-family: var(--font-mono);
font-size: var(--text-sm);
background: var(--border-light);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 1px var(--space-1);
color: var(--text-primary);
}
/* Label qualifiers — optional, default, hint text next to form labels */
.label-qualifier,
.optional {
font-size: var(--text-sm);
font-weight: var(--font-normal);
color: var(--text-secondary);
margin-left: var(--space-1);
}
/* Radio option cards — visual selected state */
.sync-option-card:has(input:checked) {
border-color: var(--primary) !important;
background: var(--primary-light) !important;
}
.code-block {
background-color: #1a1a2e;
color: #e4e4e7;
padding: 14px 16px;
border-radius: var(--radius);
overflow-x: auto;
margin: 12px 0;
border: 1px solid #2d2d44;
}
.code-block code,
.code-block pre {
background: transparent;
border: none;
border-radius: 0;
padding: 0;
font-size: 0.8125rem;
color: inherit;
line-height: 1.6;
}
.code-block pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
.code-block.code-compact {
padding: 10px 14px;
margin: 6px 0;
}
/* Command rows for Quick Start */
.command-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.command-row .command-label {
font-weight: 500;
font-size: 0.875rem;
min-width: 70px;
color: var(--text-light);
}
.command-row .code-block {
flex: 1;
margin: 0;
}
/* SSH config details/summary */
.ssh-config-details {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.ssh-config-details summary {
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
color: var(--primary);
padding: 4px 0;
user-select: none;
}
.ssh-config-details summary:hover {
color: var(--primary-dark);
}
.ssh-config-details[open] summary {
margin-bottom: 8px;
}
.ssh-config-details .help-text {
margin-top: 4px;
margin-bottom: 4px;
}
/* Forms */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 500;
margin-bottom: 8px;
}
.form-group textarea {
width: 100%;
padding: 12px 14px;
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace;
font-size: 0.8125rem;
line-height: 1.5;
border: 1px solid var(--border);
border-radius: var(--radius);
resize: vertical;
min-height: 100px;
background-color: var(--card-bg);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.form-group textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.15);
}
.form-group textarea::placeholder {
color: #9aa0a6;
}
.help-text {
font-size: 0.8125rem;
color: var(--text-light);
margin-top: 8px;
line-height: 1.5;
}
.help-text strong {
color: var(--text);
font-weight: 500;
}
/* Flash messages */
.flash-messages {
margin-bottom: 24px;
}
.flash {
padding: 14px 18px;
border-radius: var(--radius);
margin-bottom: 12px;
font-size: 0.875rem;
font-weight: 500;
}
.flash-success {
background-color: #e8f5e9;
color: #2e7d32;
border: 1px solid #c8e6c9;
}
.flash-error {
background-color: #ffebee;
color: #c62828;
border: 1px solid #ffcdd2;
}
.flash-info {
background-color: #e3f2fd;
color: #1565c0;
border: 1px solid #bbdefb;
}
.flash-warning {
background-color: #fff3e0;
color: #ef6c00;
border: 1px solid #ffe0b2;
}
/* Login page */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 60vh;
padding: 20px;
}
.login-card {
background: var(--card-bg);
border-radius: 12px;
padding: 48px 40px;
text-align: center;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
max-width: 640px;
width: 100%;
border: 1px solid var(--border);
}
.login-card h2 {
margin-bottom: 12px;
font-size: 1.75rem;
font-weight: 600;
color: var(--text);
}
.login-description {
color: var(--text-light);
margin-bottom: 32px;
font-size: 1rem;
line-height: 1.5;
}
.login-steps {
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid var(--border);
text-align: left;
}
.login-steps .step {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.875rem;
color: var(--text-light);
padding: 6px 0;
}
.login-steps .step-num,
.steps-horizontal .step-num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
background-color: var(--primary);
color: white;
border-radius: 50%;
font-size: 0.75rem;
font-weight: 600;
flex-shrink: 0;
}
/* Inline steps for compact layout */
.steps-inline {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.steps-inline .step-item {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.8125rem;
color: var(--text-light);
}
/* Form row for inline layout */
.form-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.form-group-inline {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 0;
}
.form-group-inline label {
margin-bottom: 0;
}
/* AI hint inline */
.ai-hint {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--border);
font-size: 0.8125rem;
color: var(--text-light);
}
.ai-hint code {
display: inline;
font-size: 0.75rem;
color: var(--primary);
}
/* Manual setup compact */
.manual-content {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
padding: 12px 0;
}
.manual-content code {
font-size: 0.75rem;
}
.manual-content .help-text {
margin: 0;
font-size: 0.75rem;
}
.login-note {
font-size: 0.8125rem;
color: var(--text-light);
margin-top: 12px;
}
/* Error page */
.error-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 60vh;
}
.error-card {
text-align: center;
}
.error-card h2 {
font-size: 3rem;
margin-bottom: 16px;
}
.error-card p {
color: var(--text-light);
margin-bottom: 24px;
}
/* Footer */
footer {
margin-top: 48px;
padding-top: 20px;
padding-bottom: 8px;
border-top: 1px solid var(--border);
text-align: center;
color: var(--text-light);
font-size: 0.8125rem;
}
/* Username box and copy buttons */
.username-box {
background: #f0f9ff;
border: 2px solid #0ea5e9;
border-radius: 8px;
padding: 16px;
margin: 16px 0;
}
.username-display {
display: flex;
align-items: center;
gap: 12px;
margin-top: 8px;
}
.username-display code {
font-size: 20px;
font-weight: bold;
color: #0369a1;
background: white;
padding: 8px 16px;
border-radius: 6px;
flex-grow: 1;
}
.btn-copy {
background: #0ea5e9;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: all 0.2s ease;
}
.btn-copy:hover {
background: #0284c7;
}
.btn-copy:active {
transform: scale(0.98);
}
.btn-copy.copied {
background: #22c55e;
}
.btn-copy-inline {
background: #0ea5e9;
color: white;
border: none;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-left: 8px;
transition: background 0.15s ease;
}
.btn-copy-inline:hover {
background: #0284c7;
}
.btn-copy-block {
background: #0ea5e9;
color: white;
border: none;
padding: 10px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
width: 100%;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-copy-block:hover {
background: #0284c7;
box-shadow: 0 2px 8px rgba(14, 165, 233, 0.3);
}
.btn-copy-block:active {
transform: scale(0.98);
}
.btn-copy-block.copied {
background: #22c55e;
}
.username-preview {
font-size: 18px;
color: #64748b;
}
.info-box {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 12px;
margin-bottom: 16px;
}
.card-success {
border-left: 4px solid var(--success);
background-color: #f8fdf8;
}
.help-details {
margin-top: 12px;
font-size: 0.875rem;
}
.help-details summary {
cursor: pointer;
color: var(--primary);
padding: 4px 0;
font-weight: 500;
}
.help-details summary:hover {
color: var(--primary-dark);
}
.help-details ul {
margin-top: 8px;
margin-left: 20px;
color: var(--text-light);
}
.help-details li {
margin-bottom: 4px;
}
/* Claude Code Setup Card on Dashboard */
.cc-setup-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
margin-bottom: 24px;
}
.cc-setup-card h3 {
color: white;
margin-bottom: 12px;
}
.cc-setup-card .cc-instruction {
color: rgba(255, 255, 255, 0.95);
margin-bottom: 16px;
font-size: 0.9375rem;
line-height: 1.6;
}
.cc-setup-card .cc-helper-text {
color: rgba(255, 255, 255, 0.8);
font-size: 0.8125rem;
margin-top: 12px;
margin-bottom: 0;
}
.cc-setup-card .btn-copy-block {
background: white;
color: #667eea;
font-weight: 600;
padding: 12px 24px;
border-radius: 6px;
border: none;
cursor: pointer;
font-size: 14px;
width: 100%;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.cc-setup-card .btn-copy-block:hover {
background: #f0f0f0;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.cc-setup-card .btn-copy-block:active {
transform: scale(0.98);
}
.cc-setup-card .btn-copy-block.copied {
background: #34a853;
color: white;
}
/* CLI hint in setup card */
.cc-setup-card .cli-hint {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.cc-setup-card .cli-hint summary {
cursor: pointer;
color: rgba(255, 255, 255, 0.85);
font-size: 0.8125rem;
font-weight: 500;
padding: 4px 0;
}
.cc-setup-card .cli-hint summary:hover {
color: white;
}
.cc-setup-card .cli-hint .cli-content {
margin-top: 10px;
background: rgba(255, 255, 255, 0.1);
padding: 10px 12px;
border-radius: 6px;
}
.cc-setup-card .cli-hint .cli-content code {
display: block;
background: rgba(0, 0, 0, 0.2);
color: white;
padding: 8px 10px;
border-radius: 4px;
font-size: 0.75rem;
margin-bottom: 6px;
}
.cc-setup-card .cli-hint .cli-desc {
color: rgba(255, 255, 255, 0.7);
font-size: 0.75rem;
}
/* Support info banner */
.support-info {
text-align: center;
color: var(--text-light);
font-size: 0.8125rem;
padding: 12px 16px;
background: #f8fafc;
border-radius: 6px;
margin-bottom: 20px;
border: 1px solid var(--border);
}
.support-info strong {
color: var(--text);
}
/* Auth tabs */
.auth-tabs {
display: flex;
gap: 0;
margin-bottom: 24px;
border-bottom: 2px solid var(--border);
}
.auth-tab {
flex: 1;
padding: 12px 16px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
font-size: 0.9375rem;
font-weight: 500;
color: var(--text-light);
cursor: pointer;
transition: all 0.2s ease;
}
.auth-tab:hover {
color: var(--text);
}
.auth-tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
.auth-tab-content {
display: none;
}
.auth-tab-content.active {
display: block;
}
.tab-description {
color: var(--text-light);
font-size: 0.875rem;
margin-bottom: 20px;
text-align: center;
}
.signup-note {
color: var(--text-light);
font-size: 0.8125rem;
text-align: center;
margin-top: 16px;
}
/* Login form styles */
.login-form {
text-align: left;
margin: 24px 0;
}
.login-form .form-group {
margin-bottom: 16px;
}
.login-form .form-group label {
display: block;
font-weight: 500;
font-size: 0.875rem;
margin-bottom: 6px;
color: var(--text);
}
.login-form .form-group input {
width: 100%;
padding: 12px 14px;
font-size: 0.9375rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background-color: var(--card-bg);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.login-form .form-group input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.15);
}
.login-form .form-group input::placeholder {
color: #9aa0a6;
}
.login-form .form-hint {
display: block;
font-size: 0.75rem;
color: var(--text-light);
margin-top: 4px;
}
.btn-block {
width: 100%;
}
.btn-link {
background: transparent;
color: var(--primary);
border: none;
padding: 8px 4px;
font-size: 0.875rem;
cursor: pointer;
text-decoration: none;
}
.btn-link:hover {
color: var(--primary-dark);
text-decoration: underline;
}
/* Login page links */
.login-links {
display: flex;
justify-content: center;
gap: 16px;
margin: 16px 0;
flex-wrap: wrap;
}
.login-links form {
margin: 0;
}
/* Request access form */
.request-access-form,
.reset-form {
display: inline;
}
/* Divider */
.divider {
display: flex;
align-items: center;
margin: 24px 0;
color: var(--text-light);
font-size: 0.875rem;
}
.divider::before,
.divider::after {
content: "";
flex: 1;
height: 1px;
background-color: var(--border);
}
.divider span {
padding: 0 16px;
}
/* Account email display */
.account-email {
background-color: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px 16px;
margin-bottom: 20px;
text-align: center;
font-size: 0.9375rem;
}
/* Responsive */
@media (max-width: 600px) {
.container {
padding: 15px;
}
header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.info-item {
flex-direction: column;
gap: 4px;
}
.info-item .label {
min-width: auto;
}
.username-display {
flex-direction: column;
align-items: stretch;
}
.username-display code {
text-align: center;
}
.login-links {
flex-direction: column;
gap: 8px;
}
}
/* Container */
.container-v2 {
max-width: 1400px;
margin: 0 auto;
padding: var(--space-6) var(--space-5);
}
/* Header */
.header-v2 {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-4) 0;
margin-bottom: var(--space-6);
border-bottom: 1px solid var(--border);
}
.logo-v2 h1 {
font-size: var(--text-lg);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin: 0;
}
.logo-v2 .subtitle {
font-size: var(--text-sm);
color: var(--text-secondary);
margin-top: 2px;
}
.nav-v2 {
display: flex;
align-items: center;
gap: var(--space-3);
}
.user-info-v2 {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.avatar-v2 {
width: 32px;
height: 32px;
border-radius: var(--radius-full);
}
/* Buttons */
.btn-v2 {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: 0 var(--space-3);
height: 36px;
font-size: var(--text-base);
font-weight: var(--font-medium);
text-decoration: none;
border-radius: var(--radius-md);
border: none;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-primary-v2 {
background-color: var(--primary);
color: white;
}
.btn-primary-v2:hover {
background-color: var(--primary-dark);
}
.btn-secondary-v2 {
background-color: var(--background);
color: var(--text-primary);
border: 1px solid var(--border);
}
.btn-secondary-v2:hover {
background-color: var(--border);
}
.btn-ghost-v2 {
background-color: transparent;
color: var(--text-secondary);
}
.btn-ghost-v2:hover {
background-color: rgba(243, 244, 246, 0.5);
}
/* Cards */
.card-v2 {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-5);
margin-bottom: var(--space-5);
box-shadow: var(--shadow-sm);
}
.card-v2 h3 {
font-size: var(--text-md);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-4);
}
.card-v2 p {
color: var(--text-secondary);
margin-bottom: var(--space-3);
}
/* Welcome section */
.welcome-v2 {
margin-bottom: var(--space-6);
}
.welcome-v2 h2 {
font-size: var(--text-xl);
font-weight: var(--font-extrabold);
color: var(--text-primary);
margin-bottom: var(--space-2);
}
/* AI Setup Card - Primary CTA */
.ai-setup-card {
background: linear-gradient(135deg, var(--primary) 0%, #0056A3 100%);
border: none;
color: white;
padding: var(--space-6);
margin-bottom: var(--space-5);
}
.ai-setup-card h3 {
color: white;
font-size: var(--text-lg);
margin-bottom: var(--space-3);
}
.ai-setup-card .setup-description {
color: rgba(255, 255, 255, 0.9);
font-size: var(--text-base);
line-height: 1.6;
margin-bottom: var(--space-4);
}
.ai-setup-card .btn-copy-v2 {
background: white;
color: var(--primary);
font-weight: var(--font-semibold);
padding: var(--space-3) var(--space-6);
height: 44px;
width: 100%;
border-radius: var(--radius-md);
border: none;
cursor: pointer;
font-size: var(--text-base);
text-transform: uppercase;
letter-spacing: 0.5px;
transition: all 0.2s ease;
}
.ai-setup-card .btn-copy-v2:hover {
background: var(--border-light);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.ai-setup-card .btn-copy-v2.copied {
background: var(--success);
color: white;
}
.ai-setup-card .helper-text {
color: rgba(255, 255, 255, 0.9);
font-size: var(--text-sm);
margin-top: var(--space-3);
margin-bottom: 0;
line-height: 1.5;
}
/* CLI Hint */
.cli-hint-v2 {
margin-top: var(--space-4);
padding-top: var(--space-3);
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.cli-hint-v2 summary {
cursor: pointer;
color: rgba(255, 255, 255, 0.8);
font-size: var(--text-sm);
font-weight: var(--font-medium);
padding: var(--space-1) 0;
list-style: none;
}
.cli-hint-v2 summary::-webkit-details-marker {
display: none;
}
.cli-hint-v2 summary::before {
content: '+ ';
}
.cli-hint-v2[open] summary::before {
content: '- ';
}
.cli-hint-v2 summary:hover {
color: white;
}
.cli-hint-v2 .cli-content {
margin-top: var(--space-3);
background: rgba(0, 0, 0, 0.15);
padding: var(--space-3);
border-radius: var(--radius-md);
}
.cli-hint-v2 code {
display: block;
background: rgba(0, 0, 0, 0.2);
color: white;
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: var(--text-sm);
margin-bottom: var(--space-2);
}
.cli-hint-v2 .cli-desc {
color: rgba(255, 255, 255, 0.7);
font-size: var(--text-xs);
}
/* Support banner */
.support-banner {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
margin-bottom: var(--space-5);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.support-banner strong {
color: var(--text-primary);
}
.support-banner .slack-badge {
display: inline-flex;
align-items: center;
gap: var(--space-1);
background: var(--primary-light);
color: var(--primary);
padding: 2px var(--space-2);
border-radius: var(--radius-full);
font-weight: var(--font-medium);
}
/* Status cards */
.status-card {
border-left: 3px solid var(--success);
}
.status-card.error {
border-left-color: var(--error);
}
.status-card.warning {
border-left-color: var(--warning);
}
/* Username box */
.username-box-v2 {
background: var(--primary-light);
border: 1px solid var(--primary);
border-radius: var(--radius-lg);
padding: var(--space-4);
margin: var(--space-4) 0;
}
.username-box-v2 label {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-secondary);
display: block;
margin-bottom: var(--space-2);
}
.username-display-v2 {
display: flex;
align-items: center;
gap: var(--space-3);
}
.username-display-v2 code {
font-family: var(--font-mono);
font-size: var(--text-xl);
font-weight: var(--font-bold);
color: var(--primary);
background: white;
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
flex-grow: 1;
}
.username-display-v2 .btn-copy-sm {
background: var(--primary);
color: white;
border: none;
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
cursor: pointer;
font-size: var(--text-sm);
font-weight: var(--font-semibold);
text-transform: uppercase;
letter-spacing: 0.5px;
transition: all 0.15s ease;
}
.username-display-v2 .btn-copy-sm:hover {
background: var(--primary-dark);
}
.username-display-v2 .btn-copy-sm.copied {
background: var(--success);
}
/* Info grid */
.info-grid-v2 {
display: grid;
gap: var(--space-3);
}
.info-item-v2 {
display: flex;
align-items: center;
gap: var(--space-3);
}
.info-item-v2 .label {
font-weight: var(--font-medium);
color: var(--text-secondary);
min-width: 120px;
font-size: var(--text-sm);
}
.info-item-v2 .value {
color: var(--text-primary);
}
.info-item-v2 code {
font-family: var(--font-mono);
font-size: var(--text-sm);
background: var(--background);
padding: 2px var(--space-2);
border-radius: var(--radius-sm);
}
/* Badges */
.badge-v2 {
display: inline-flex;
align-items: center;
padding: 2px var(--space-2);
font-size: var(--text-xs);
font-weight: var(--font-medium);
border-radius: var(--radius-full);
background: var(--background);
color: var(--text-secondary);
margin-right: var(--space-1);
}
.badge-analyst-v2 {
background: rgba(16, 183, 127, 0.1);
color: var(--success);
}
.badge-privileged-v2 {
background: rgba(245, 159, 10, 0.1);
color: var(--warning);
}
.badge-admin-v2 {
background: rgba(46, 168, 119, 0.1);
color: var(--primary);
}
/* Form */
.form-v2 {
margin-top: var(--space-4);
}
.form-group-v2 {
margin-bottom: var(--space-4);
}
.form-group-v2 label {
display: block;
font-weight: var(--font-medium);
color: var(--text-primary);
margin-bottom: var(--space-2);
font-size: var(--text-sm);
}
.form-group-v2 textarea {
width: 100%;
padding: var(--space-3);
font-family: var(--font-mono);
font-size: var(--text-sm);
line-height: 1.5;
border: 1px solid var(--border);
border-radius: var(--radius-md);
resize: vertical;
min-height: 80px;
background: var(--surface);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.form-group-v2 textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px var(--primary-light);
}
.form-group-v2 textarea::placeholder {
color: var(--text-secondary);
}
.form-row-v2 {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
}
.form-info-v2 {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.form-info-v2 code {
font-family: var(--font-mono);
font-weight: var(--font-semibold);
color: var(--text-primary);
}
/* Help text */
.help-text-v2 {
font-size: var(--text-sm);
color: var(--text-secondary);
margin-top: var(--space-2);
}
.help-text-v2 strong {
color: var(--text-primary);
}
/* Info box */
.info-box-v2 {
background: var(--background);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-3);
margin-bottom: var(--space-4);
display: flex;
align-items: center;
gap: var(--space-2);
}
.info-box-v2 .username-preview {
font-family: var(--font-mono);
font-size: var(--text-lg);
font-weight: var(--font-semibold);
color: var(--primary);
}
/* Alert */
.alert-v2 {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-4);
border-radius: var(--radius-lg);
margin-bottom: var(--space-4);
}
.alert-success-v2 {
background: rgba(16, 183, 127, 0.1);
border-left: 3px solid var(--success);
}
.alert-error-v2 {
background: rgba(234, 88, 12, 0.1);
border-left: 3px solid var(--error);
}
.alert-v2 .alert-icon {
font-size: var(--text-lg);
}
.alert-v2 h4 {
font-size: var(--text-base);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-1);
}
.alert-v2 p {
font-size: var(--text-sm);
color: var(--text-secondary);
margin: 0;
}
/* Flash messages */
.flash-messages-v2 {
margin-bottom: var(--space-5);
}
.flash-v2 {
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-lg);
margin-bottom: var(--space-3);
font-size: var(--text-sm);
font-weight: var(--font-medium);
}
.flash-success-v2 {
background: rgba(16, 183, 127, 0.1);
color: #0d9668;
border: 1px solid rgba(16, 183, 127, 0.3);
}
.flash-error-v2 {
background: rgba(234, 88, 12, 0.1);
color: #c2410c;
border: 1px solid rgba(234, 88, 12, 0.3);
}
.flash-info-v2 {
background: var(--primary-light);
color: var(--primary);
border: 1px solid rgba(46, 168, 119, 0.3);
}
/* Footer */
.footer-v2 {
margin-top: var(--space-8);
padding-top: var(--space-5);
border-top: 1px solid var(--border);
text-align: center;
color: var(--text-secondary);
font-size: var(--text-sm);
}
/* Next step card */
.next-step-card {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border: 1px solid var(--primary);
border-left: 3px solid var(--primary);
}
.next-step-card h3 {
color: var(--primary);
}
/* Data Link Card */
.data-link-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-5);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
text-decoration: none;
transition: all 0.15s ease;
margin-bottom: var(--space-5);
}
.data-link-card:hover {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
}
.data-link-content {
display: flex;
align-items: center;
gap: var(--space-4);
}
.data-link-icon {
width: 44px;
height: 44px;
background: var(--primary-light);
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.data-link-text h3 {
font-size: var(--text-md);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: 2px;
}
.data-link-text p {
font-size: var(--text-sm);
color: var(--text-secondary);
margin: 0;
}
.data-link-stats {
display: flex;
gap: var(--space-4);
padding: var(--space-3) var(--space-4);
background: var(--background);
border-radius: var(--radius-md);
}
.data-stat {
text-align: center;
padding: 0 var(--space-2);
}
.data-stat .value {
font-size: var(--text-md);
font-weight: var(--font-semibold);
color: var(--text-primary);
}
.data-stat .label {
font-size: 10px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.data-stat-divider {
width: 1px;
background: var(--border);
}
.data-link-arrow {
width: 32px;
height: 32px;
background: var(--primary-light);
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
color: var(--primary);
transition: all 0.15s ease;
flex-shrink: 0;
}
.data-link-card:hover .data-link-arrow {
background: var(--primary);
color: white;
transform: translateX(2px);
}
.data-categories {
display: flex;
gap: var(--space-2);
margin-top: var(--space-2);
}
.category-tag {
font-size: var(--text-xs);
padding: 2px 8px;
border-radius: var(--radius-full);
font-weight: var(--font-medium);
}
.category-tag.finance { background: rgba(16, 183, 127, 0.1); color: #0d9668; }
.category-tag.hr { background: rgba(139, 92, 246, 0.1); color: #7c3aed; }
.category-tag.sales { background: rgba(46, 168, 119, 0.1); color: var(--primary); }
.category-tag.telemetry { background: rgba(245, 159, 10, 0.1); color: #b45309; }
.category-tag.support { background: rgba(234, 88, 12, 0.1); color: #EA580C; }
.category-tag.revenue { background: rgba(46, 168, 119, 0.1); color: var(--primary); }
.category-tag.customers { background: rgba(139, 92, 246, 0.1); color: #7c3aed; }
.category-tag.marketing { background: rgba(245, 159, 10, 0.1); color: #b45309; }
.data-highlights {
display: flex;
gap: var(--space-4);
margin-top: var(--space-3);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.data-highlight {
display: flex;
align-items: center;
gap: 4px;
}
.data-highlight strong {
color: var(--text-primary);
font-weight: var(--font-semibold);
}
/* Slack badge as link */
a.slack-badge {
text-decoration: none;
transition: all 0.15s ease;
}
a.slack-badge:hover {
background: var(--primary);
color: white;
}
/* Dashboard 2-column grid */
.dashboard-grid {
display: grid;
grid-template-columns: 3fr 2fr;
gap: var(--space-5);
align-items: start;
}
.col-left {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.col-right {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.full-width {
grid-column: 1 / -1;
}
/* Remove bottom margin for cards inside grid (grid gap handles spacing) */
.dashboard-grid .card-v2 {
margin-bottom: 0;
}
.dashboard-grid .data-link-card {
margin-bottom: 0;
}
.dashboard-grid .support-banner {
margin-bottom: 0;
}
.support-banner .heart {
color: #e74c3c;
}
/* Responsive */
@media (max-width: 1024px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.container-v2 {
padding: var(--space-4);
}
.header-v2 {
flex-direction: column;
gap: var(--space-3);
text-align: center;
}
.username-display-v2 {
flex-direction: column;
}
.username-display-v2 code {
width: 100%;
text-align: center;
}
.form-row-v2 {
flex-direction: column;
align-items: stretch;
}
.info-item-v2 {
flex-direction: column;
align-items: flex-start;
gap: var(--space-1);
}
.info-item-v2 .label {
min-width: auto;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
}
/* Setup steps styling */
.setup-steps {
margin: 24px 0;
display: flex;
flex-direction: column;
gap: 20px;
}
.step-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.step-header {
display: flex;
align-items: center;
gap: 12px;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: white;
color: var(--primary);
border-radius: 50%;
font-weight: 700;
font-size: 16px;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.step-header strong {
color: white;
font-size: 16px;
font-weight: 600;
}
.step-body {
margin-left: 44px;
}
.step-body p {
margin: 0 0 8px 0;
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
line-height: 1.5;
}
/* Code block styling */
.code-block-wrapper {
position: relative;
display: flex;
align-items: center;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
padding: 12px 48px 12px 12px;
margin-top: 8px;
}
.code-block {
flex: 1;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
font-size: 13px;
color: #fff;
background: transparent;
border: none;
white-space: pre;
overflow-x: auto;
}
.btn-copy-code {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
padding: 6px 8px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.btn-copy-code:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.4);
}
.btn-copy-code.copied {
background: rgba(76, 175, 80, 0.3);
border-color: rgba(76, 175, 80, 0.5);
}
.btn-copy-code svg {
width: 16px;
height: 16px;
}
/* Compact setup banner (existing users) */
.setup-compact {
background: linear-gradient(135deg, var(--primary) 0%, #0056A3 100%);
border: none;
color: white;
padding: var(--space-4) var(--space-5);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
}
.setup-compact h3 {
color: white;
margin-bottom: 0;
font-size: var(--text-base);
}
.setup-compact p {
color: rgba(255, 255, 255, 0.85);
margin: 4px 0 0 0;
font-size: var(--text-sm);
}
.setup-compact .btn-copy-v2 {
background: white;
color: var(--primary);
font-weight: var(--font-semibold);
padding: var(--space-2) var(--space-5);
height: 40px;
border-radius: var(--radius-md);
border: none;
cursor: pointer;
font-size: var(--text-sm);
white-space: nowrap;
transition: all 0.15s ease;
flex-shrink: 0;
}
.setup-compact .btn-copy-v2:hover {
background: var(--border-light);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.setup-compact .btn-copy-v2.copied {
background: var(--success);
color: white;
}
/* Unified Notifications card */
.notifications-card {
padding: 0;
}
.notifications-card h3 {
padding: var(--space-5) var(--space-5) 0;
margin-bottom: var(--space-3);
}
.notif-channels {
display: flex;
flex-direction: column;
}
.notif-channel {
padding: var(--space-4) var(--space-5);
border-top: 1px solid var(--border);
display: flex;
align-items: flex-start;
gap: var(--space-3);
}
.notif-channel-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.notif-channel-icon.telegram {
background: #e3f2fd;
}
.notif-channel-icon.desktop {
background: #f3e8ff;
}
.notif-channel-body {
flex: 1;
min-width: 0;
}
.notif-channel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
margin-bottom: var(--space-1);
}
.notif-channel-name {
font-weight: var(--font-semibold);
font-size: var(--text-base);
color: var(--text-primary);
}
.notif-status-badge {
font-size: var(--text-xs);
font-weight: var(--font-medium);
padding: 2px 8px;
border-radius: var(--radius-full);
}
.notif-status-badge.linked {
background: rgba(16, 183, 127, 0.1);
color: var(--success);
}
.notif-status-badge.not-linked {
background: var(--background);
color: var(--text-secondary);
}
.notif-channel-desc {
font-size: var(--text-sm);
color: var(--text-secondary);
margin-bottom: var(--space-3);
}
.notif-channel-action {
display: flex;
align-items: center;
gap: var(--space-2);
}
.notif-channel-action input[type="text"] {
width: 120px;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-family: var(--font-mono);
font-size: var(--text-sm);
text-align: center;
}
.btn-sm-v2 {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-1);
padding: 0 var(--space-3);
height: 32px;
font-size: var(--text-sm);
font-weight: var(--font-medium);
text-decoration: none;
border-radius: var(--radius-md);
border: none;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-sm-primary {
background-color: var(--primary);
color: white;
}
.btn-sm-primary:hover {
background-color: var(--primary-dark);
}
.btn-sm-secondary {
background-color: var(--background);
color: var(--text-primary);
border: 1px solid var(--border);
}
.btn-sm-secondary:hover {
background-color: var(--border);
}
.download-link {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--primary);
text-decoration: none;
}
.download-link:hover {
text-decoration: underline;
}
.notif-install-steps {
margin: 4px 0 0;
padding-left: 20px;
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.7;
}
.notif-install-steps li {
padding-left: 4px;
}
.notif-install-steps .download-link {
font-weight: var(--font-normal);
}
/* Beta warning styles */
.beta-warning {
background: rgba(245, 159, 10, 0.1);
border: 1px solid rgba(245, 159, 10, 0.3);
border-radius: var(--radius-md);
padding: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
font-size: var(--text-sm);
color: #b45309;
}
.beta-warning strong {
color: #92400e;
}
.beta-tag {
display: inline-flex;
align-items: center;
background: rgba(245, 159, 10, 0.15);
color: #b45309;
font-size: var(--text-xs);
font-weight: var(--font-medium);
padding: 1px 6px;
border-radius: var(--radius-full);
margin-left: var(--space-1);
}
@media (max-width: 640px) {
.setup-compact {
flex-direction: column;
align-items: stretch;
text-align: center;
}
.setup-compact .btn-copy-v2 {
width: 100%;
}
}
/* Data Settings Card */
.settings-card {
border-left: 3px solid var(--primary);
}
.settings-card h3 {
margin-bottom: var(--space-3);
}
.dataset-toggles {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.dataset-toggle {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: var(--space-3);
background: var(--background);
border-radius: var(--radius-md);
gap: var(--space-3);
}
.dataset-toggle.disabled {
opacity: 0.5;
pointer-events: none;
}
.dataset-info {
flex: 1;
min-width: 0;
}
.dataset-label {
font-weight: var(--font-medium);
font-size: var(--text-base);
color: var(--text-primary);
display: flex;
align-items: center;
gap: var(--space-2);
}
.dataset-size {
font-size: var(--text-xs);
color: var(--text-secondary);
font-weight: var(--font-normal);
}
.dataset-desc {
font-size: var(--text-sm);
color: var(--text-secondary);
margin-top: 2px;
}
/* Toggle Switch */
.toggle-switch {
position: relative;
width: 44px;
height: 24px;
flex-shrink: 0;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--border);
transition: 0.2s ease;
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.2s ease;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.toggle-switch input:checked + .toggle-slider {
background-color: var(--primary);
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(20px);
}
.toggle-switch input:disabled + .toggle-slider {
cursor: not-allowed;
opacity: 0.6;
}
.settings-hint {
font-size: var(--text-sm);
color: var(--text-secondary);
margin-top: var(--space-3);
margin-bottom: 0;
padding-top: var(--space-3);
border-top: 1px solid var(--border);
}
.settings-saving {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.settings-error {
font-size: var(--text-sm);
color: var(--error);
margin-top: var(--space-2);
}
/* ========== KPI Card (Your Data Widget) ========== */
.kpi-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
}
.kpi-header {
background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);
color: white;
padding: var(--space-5);
display: flex;
justify-content: space-between;
align-items: center;
}
.kpi-header h3 {
font-size: var(--text-md);
font-weight: var(--font-semibold);
color: white;
margin: 0;
}
.kpi-header-stats {
display: flex;
gap: var(--space-5);
}
.kpi-header-stat {
text-align: center;
}
.kpi-header-stat .value {
font-size: var(--text-xl);
font-weight: var(--font-semibold);
}
.kpi-header-stat .label {
font-size: var(--text-xs);
opacity: 0.7;
text-transform: uppercase;
}
.kpi-header-stat-secondary {
opacity: 0.7;
}
.kpi-body {
padding: var(--space-5);
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4);
}
.kpi-section {
padding: var(--space-4);
border-radius: var(--radius-md);
border: 1px solid var(--border);
}
.kpi-section-disabled {
opacity: 0.5;
}
.kpi-section-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.kpi-icon {
width: 24px;
height: 24px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
}
.kpi-icon.batch {
background: var(--primary-light);
}
.kpi-icon.live {
background: rgba(16, 183, 127, 0.1);
}
.kpi-icon.disabled {
background: var(--background);
}
.kpi-section-title {
font-size: var(--text-sm);
font-weight: var(--font-medium);
}
.kpi-badge {
margin-left: auto;
font-size: 9px;
font-weight: var(--font-semibold);
padding: 2px 6px;
border-radius: var(--radius-full);
text-transform: uppercase;
display: flex;
align-items: center;
gap: 4px;
}
.kpi-badge.live {
background: rgba(16, 183, 127, 0.1);
color: var(--success);
}
.kpi-section-time {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-bottom: var(--space-2);
}
.kpi-section-time.live {
color: var(--success);
}
.kpi-section-datasets {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.kpi-footer {
padding: var(--space-3) var(--space-5);
background: var(--background);
display: flex;
justify-content: space-between;
align-items: center;
}
.kpi-highlights {
display: flex;
gap: var(--space-4);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.kpi-highlights strong {
color: var(--text-primary);
}
.kpi-link {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--primary);
text-decoration: none;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.15s ease;
}
.kpi-link:hover {
text-decoration: underline;
}
/* Live dot animation */
.live-dot {
width: 6px;
height: 6px;
background: var(--success);
border-radius: 50%;
animation: pulse 2s infinite;
display: inline-block;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* Disabled category tag */
.category-tag.disabled {
background: var(--background);
color: var(--text-secondary);
}
/* Responsive KPI card */
@media (max-width: 640px) {
.kpi-header {
flex-direction: column;
gap: var(--space-3);
text-align: center;
}
.kpi-body {
grid-template-columns: 1fr;
}
.kpi-footer {
flex-direction: column;
gap: var(--space-3);
text-align: center;
}
.kpi-highlights {
flex-wrap: wrap;
justify-content: center;
}
}
/* ========== Login Page Split Layout ========== */
.login-page {
min-height: 100vh;
background: var(--background);
}
.login-split {
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 100vh;
}
/* Left side: Features */
.login-features {
background: var(--primary);
color: white;
padding: var(--space-8);
display: flex;
align-items: center;
justify-content: center;
}
.features-content {
max-width: 480px;
}
.features-title {
font-size: 28px;
font-weight: var(--font-semibold);
line-height: 1.3;
margin-bottom: var(--space-3);
}
.features-subtitle {
font-size: var(--text-md);
line-height: 1.6;
opacity: 0.9;
margin-bottom: var(--space-6);
}
/* Feature cards */
.feature-cards {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.feature-card {
display: flex;
gap: var(--space-3);
padding: var(--space-4);
background: rgba(255, 255, 255, 0.1);
border-radius: var(--radius-lg);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.feature-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.15);
color: white;
}
.feature-icon-data,
.feature-icon-memory,
.feature-icon-auto,
.feature-icon-notif {
background: rgba(255, 255, 255, 0.15);
color: white;
}
.feature-text h3 {
font-size: var(--text-base);
font-weight: var(--font-semibold);
margin-bottom: 2px;
color: white;
}
.feature-text p {
font-size: var(--text-sm);
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
margin: 0;
}
/* Right side: Login card */
.login-card-wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-6);
background: var(--background);
}
.login-page .login-card {
background: var(--surface);
border-radius: var(--radius-xl);
padding: 48px 40px;
text-align: center;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
max-width: 420px;
width: 100%;
border: 1px solid var(--border);
}
.login-page .login-card h2 {
margin-bottom: var(--space-2);
font-size: var(--text-xl);
font-weight: var(--font-semibold);
color: var(--text-primary);
}
.login-page .login-description {
color: var(--text-secondary);
margin-bottom: var(--space-6);
font-size: var(--text-base);
line-height: 1.5;
}
.login-page .login-note {
font-size: var(--text-sm);
color: var(--text-secondary);
margin-top: var(--space-3);
}
.login-page .login-note strong {
color: var(--text-primary);
}
.login-page .divider {
display: flex;
align-items: center;
margin: var(--space-5) 0;
color: var(--text-secondary);
font-size: var(--text-sm);
}
.login-page .divider::before,
.login-page .divider::after {
content: "";
flex: 1;
height: 1px;
background-color: var(--border);
}
.login-page .divider span {
padding: 0 var(--space-4);
}
/* Google button styling */
.login-page .btn-google {
background-color: white;
color: var(--text-primary);
border: 1px solid var(--border);
padding: 14px 28px;
font-size: var(--text-base);
font-weight: var(--font-medium);
width: 100%;
max-width: 280px;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
border-radius: var(--radius-md);
text-decoration: none;
cursor: pointer;
}
.login-page .btn-google:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
border-color: var(--border-dark);
background-color: var(--background);
}
.login-page .btn-google:active {
transform: scale(0.98);
}
/* Email magic link button styling */
.login-page .btn-email {
background-color: #4361ee;
color: white;
border: 1px solid #3a56d4;
padding: 14px 28px;
font-size: var(--text-base);
font-weight: var(--font-medium);
border-radius: var(--radius-md);
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 12px;
transition: all 0.2s ease;
cursor: pointer;
}
.login-page .btn-email:hover {
background-color: #3a56d4;
box-shadow: 0 2px 8px rgba(67, 97, 238, 0.3);
}
.login-page .btn-email:active {
transform: scale(0.98);
}
.login-page .btn-secondary {
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
padding: 14px 28px;
font-size: var(--text-base);
font-weight: var(--font-medium);
border-radius: var(--radius-md);
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
cursor: pointer;
}
.login-page .btn-secondary:hover {
background-color: var(--background);
border-color: var(--text-secondary);
}
/* Responsive: Tablet */
@media (max-width: 1024px) {
.login-split {
grid-template-columns: 1fr;
}
.login-features {
padding: var(--space-6);
min-height: auto;
}
.features-content {
max-width: 600px;
}
.features-title {
font-size: 28px;
}
.features-subtitle {
margin-bottom: var(--space-6);
}
.feature-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
}
.feature-card {
padding: var(--space-3);
}
.feature-icon {
width: 36px;
height: 36px;
}
.login-card-wrapper {
padding: var(--space-6);
}
}
/* Responsive: Mobile */
@media (max-width: 640px) {
.login-features {
padding: var(--space-5);
}
.features-content {
max-width: 100%;
}
.features-title {
font-size: 24px;
}
.features-subtitle {
font-size: var(--text-base);
margin-bottom: var(--space-5);
}
.feature-cards {
grid-template-columns: 1fr;
gap: var(--space-3);
}
.feature-card {
padding: var(--space-3);
gap: var(--space-3);
}
.feature-text h3 {
font-size: var(--text-base);
}
.feature-text p {
font-size: var(--text-xs);
}
.login-card-wrapper {
padding: var(--space-4);
}
.login-page .login-card {
padding: var(--space-6);
}
}
/* ========== Corporate Memory Widget (Design System - Warning Color) ========== */
.memory-widget {
border-left: 3px solid var(--warning);
}
.memory-widget .card-header-custom {
background: rgba(245, 159, 10, 0.08);
padding: var(--space-4) var(--space-5);
margin: calc(-1 * var(--space-5));
margin-bottom: var(--space-4);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: var(--space-2);
}
.memory-icon {
width: 28px;
height: 28px;
background: var(--warning);
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.memory-widget h3 {
margin-bottom: 0;
display: flex;
align-items: center;
gap: var(--space-2);
}
.memory-stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
margin-bottom: var(--space-4);
}
.memory-stat {
text-align: center;
padding: var(--space-4);
background: var(--background);
border-radius: var(--radius-lg);
}
.memory-stat .value {
font-size: var(--text-2xl);
font-weight: var(--font-bold);
color: var(--text-primary);
line-height: 1;
}
.memory-stat .label {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: var(--space-1);
}
.memory-stat.highlight {
background: var(--warning);
}
.memory-stat.highlight .value,
.memory-stat.highlight .label {
color: white;
}
.memory-description {
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.6;
padding: var(--space-3) var(--space-4);
background: var(--background);
border-radius: var(--radius-md);
border-left: 3px solid rgba(245, 159, 10, 0.3);
margin-bottom: var(--space-4);
}
.memory-description strong {
color: var(--text-primary);
}
.memory-description code {
font-family: var(--font-mono);
font-size: var(--text-xs);
background: rgba(0, 0, 0, 0.05);
padding: 1px 4px;
border-radius: var(--radius-sm);
}
.memory-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: var(--space-3);
border-top: 1px solid var(--border);
margin-top: var(--space-4);
}
.sync-status {
display: flex;
align-items: center;
gap: 6px;
font-size: var(--text-sm);
color: var(--success);
}
.memory-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: var(--space-2) var(--space-4);
background: var(--warning);
color: white;
text-decoration: none;
font-size: var(--text-sm);
font-weight: var(--font-medium);
border-radius: var(--radius-md);
transition: all 0.15s ease;
}
.memory-link:hover {
background: #d97706;
transform: translateY(-1px);
}
/* ─── Shared modern header (used by base.html + future pages) ─── */
/* Mirrors the inline header styles in dashboard.html so all pages share chrome. */
.app-header {
background: var(--surface, #fff);
border-bottom: 1px solid var(--border, #e5e7eb);
padding: 0 32px;
/* Cancel the generic `header { margin-bottom: 24px }` from style.css,
which would otherwise leave a 24px stripe of pale grey under the
app-header. Invisible on white-on-white pages (dashboard) but very
visible on /home where the blue install-hero sits directly below. */
margin-bottom: 0;
height: 72px;
/* Override the legacy generic `header { margin-bottom: 24px }` rule from
style.css — the .app-header sits flush against the page container so
all base.html pages match the dashboard's header-to-content spacing. */
margin-bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 100;
}
.app-header-left {
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
}
.app-header-logo {
display: inline-flex;
align-items: center;
text-decoration: none;
color: inherit;
font-weight: 600;
font-size: 16px;
}
.app-header-logo svg {
display: block;
max-height: 40px;
width: auto;
}
a.app-header-logo:focus-visible {
outline: 2px solid var(--primary, #6366f1);
outline-offset: 2px;
border-radius: 4px;
}
.app-header-subtitle {
font-size: 11px;
font-weight: 500;
color: var(--text-secondary, #6b7280);
letter-spacing: 0.4px;
text-transform: uppercase;
margin-top: 2px;
}
.app-header-right {
display: flex;
align-items: center;
gap: 16px;
}
.app-header-email {
font-size: 13px;
color: var(--text-secondary, #6b7280);
font-weight: 500;
}
.app-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--primary-light, #eef2ff);
color: var(--primary, #6366f1);
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
letter-spacing: 0.3px;
}
.app-avatar-img {
width: 36px;
height: 36px;
border-radius: 50%;
border: 2px solid var(--border, #e5e7eb);
}
/* Top-nav entries. Both <a class="app-nav-link"> and
<button class="app-nav-link app-nav-menu-trigger"> share this rule
so the Admin dropdown trigger renders identical to its siblings —
same font, color, padding, hover, active state. The button-element
resets (background/border/font-family) flow from here, so
.app-nav-menu-trigger no longer needs to strip <button> chrome
separately. */
.app-nav-link {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 14px;
font-weight: 500;
font-family: inherit;
color: var(--text-secondary);
text-decoration: none;
padding: 8px 14px;
border-radius: 8px;
border: 0;
background: transparent;
cursor: pointer;
line-height: 1.2;
transition: all 0.15s ease;
}
.app-nav-link:hover {
color: var(--primary-dark);
background: var(--primary-light);
}
.app-nav-link.is-active,
.app-nav-link[aria-expanded="true"] {
color: var(--primary-dark);
background: var(--primary-light);
}
.app-nav-link:focus { outline: none; }
.app-nav-link:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
.app-btn-logout {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary, #6b7280);
background: none;
border: 1px solid var(--border, #e5e7eb);
border-radius: 8px;
padding: 6px 14px;
cursor: pointer;
transition: all 0.15s ease;
text-decoration: none;
display: inline-block;
}
.app-btn-logout:hover {
color: var(--text-primary, #111827);
border-color: #d1d5db;
background: var(--border-light, #f3f4f6);
}
/* ── User menu (dropdown) ── */
.app-user-menu { position: relative; display: inline-flex; align-items: center; }
.app-user-menu-trigger {
display: inline-flex; align-items: center; gap: 6px;
background: none; border: 1px solid transparent;
border-radius: 999px; padding: 4px 10px 4px 4px;
cursor: pointer; transition: all 0.15s ease;
}
.app-user-menu-trigger:hover { background: var(--border-light, #f3f4f6); border-color: var(--border, #e5e7eb); }
.app-user-menu-trigger[aria-expanded="true"] { background: var(--border-light, #f3f4f6); border-color: var(--border, #e5e7eb); }
.app-user-menu-chevron { color: var(--text-secondary, #6b7280); transition: transform 0.15s ease; }
.app-user-menu-trigger[aria-expanded="true"] .app-user-menu-chevron { transform: rotate(180deg); }
.app-user-menu-panel {
position: absolute; top: calc(100% + 8px); right: 0;
min-width: 220px;
background: var(--surface, #fff);
border: 1px solid var(--border, #e5e7eb);
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
padding: 6px;
z-index: 50;
}
.app-user-menu-panel[hidden] { display: none; }
.app-user-menu-header {
padding: 10px 12px 8px;
border-bottom: 1px solid var(--border-light, #f3f4f6);
margin-bottom: 4px;
}
.app-user-menu-email { font-size: 13px; font-weight: 500; color: var(--text-primary, #111827); word-break: break-all; }
.app-user-menu-role { font-size: 11px; color: var(--text-secondary, #6b7280); margin-top: 2px; text-transform: uppercase; letter-spacing: 0.3px; }
.app-user-menu-item {
display: block; padding: 8px 12px;
font-size: 13px; color: var(--text-primary, #111827);
text-decoration: none; border-radius: 6px;
}
.app-user-menu-item:hover { background: var(--border-light, #f3f4f6); }
.app-user-menu-item.is-active { background: rgba(46, 168, 119, 0.08); color: var(--primary, var(--primary)); font-weight: 500; }
/* ── Admin nav dropdown — same vocabulary as user menu, scoped under nav links ── */
.app-nav-menu { position: relative; display: inline-flex; }
/* .app-nav-menu-trigger styling flows entirely from .app-nav-link above.
The trigger class is kept in markup as a hook for app.js dropdown
wiring and for targeting the chevron rotation below. */
.app-nav-menu-chevron { color: currentColor; transition: transform 0.15s ease; }
.app-nav-menu-trigger[aria-expanded="true"] .app-nav-menu-chevron { transform: rotate(180deg); }
.app-nav-menu-panel {
position: absolute; top: calc(100% + 8px); right: 0;
min-width: 200px;
background: var(--surface, #fff);
border: 1px solid var(--border, #e5e7eb);
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
padding: 6px;
z-index: 50;
}
.app-nav-menu-panel[hidden] { display: none; }
.app-nav-menu-item {
display: block; padding: 8px 12px;
font-size: 13px; color: var(--text-primary, #111827);
text-decoration: none; border-radius: 6px;
}
.app-nav-menu-item:hover { background: var(--border-light, #f3f4f6); }
.app-nav-menu-item.is-active { background: var(--primary-light); color: var(--primary-dark); font-weight: 500; }
.admin-menu-separator { margin: 4px 8px; border: 0; border-top: 1px solid var(--border, #e5e7eb); }
/* Section header inside the admin dropdown — non-clickable, small-caps,
sitting in a light gray band so each named group (Activity Center /
Users & Access / Data / Agent Experience / Server) reads as a
visually contained cluster. The negative horizontal margins extend
the band edge-to-edge inside the panel (panel has 6px padding;
matching -6px here). Rounded top corners on the first header line
up with the panel's own border-radius so the first band tucks
into the top of the panel cleanly. */
.app-nav-menu-section {
background: var(--border-light, #f3f4f6);
margin: 8px -6px 4px;
padding: 6px 12px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-secondary, #6b7280);
font-weight: 700;
}
.app-nav-menu-section:first-child {
margin-top: -6px;
border-radius: 10px 10px 0 0;
}
.app-nav-menu-section + .app-nav-menu-item { margin-top: 4px; }
/* <details data-section=...> group wrapper — visually identical to the
pre-collapse render, but each section can fold up. <summary> reuses
the .app-nav-menu-section style and gets a chevron via :before that
rotates 90deg when [open] (no JS for the visual). */
.app-nav-menu-group { margin: 0; }
.app-nav-menu-group > summary.app-nav-menu-section {
cursor: pointer;
user-select: none;
list-style: none; /* hide native disclosure triangle (most browsers) */
display: flex;
align-items: center;
gap: 6px;
}
.app-nav-menu-group > summary.app-nav-menu-section::-webkit-details-marker {
display: none; /* WebKit fallback */
}
.app-nav-menu-group > summary.app-nav-menu-section::before {
content: "";
font-size: 12px;
line-height: 1;
color: var(--text-secondary, #6b7280);
transition: transform 0.12s ease;
}
.app-nav-menu-group[open] > summary.app-nav-menu-section::before {
transform: rotate(90deg);
}
/* First-section rounded corners stayed on the original first-child rule,
but the wrapper <details> is now the first child — re-apply on the
nested <summary>. */
.app-nav-menu-group:first-of-type > summary.app-nav-menu-section {
margin-top: -6px;
border-radius: 10px 10px 0 0;
}
@media (max-width: 720px) {
.app-header { padding: 0 16px; gap: 8px; }
.app-header-email { display: none; }
.app-nav-link { padding: 6px 8px; }
}
/* ── Standard page shell ─────────────────────────────────────────────
Shared layout container for pages that extend base.html. Matches the
dashboard's `.main` dimensions so /dashboard, /marketplace, and the
marketplace detail pages all align. Add `page-shell` to a page's root
wrapper to opt in. */
/* `.page-shell` opt-in is now redundant — the canonical `.container`
already provides the 1280px max-width + `16px 32px 48px` padding +
stripped `<main>` defaults that this rule used to add. Kept as a
no-op marker so existing templates that opt in continue to render
the same as everything else. */
/* ==============================================================
News content vocabulary (shared)
--------------------------------------------------------------
Used by: /news (full content), /home (perex slot via .home-news-body),
/admin/news preview pane.
Author classes documented in docs/operator/news-content-guide.md:
.news-hero, .callout, .callout-{info,warn,success,danger},
.video-embed, .news-section, .news-grid-2, .news-grid-3, .news-cta.
Selectors are scope-free where the class itself is the marker
(.callout, .news-hero, .video-embed, .news-grid-*, .news-cta) so
authors can drop them anywhere — /home perex, /news body,
/admin/news preview, future admin pages.
The table / pre / code overrides ARE scoped to .news-content because
`<table>` / `<pre>` / inline `<code>` exist on plenty of other pages
that should not inherit this styling.
============================================================== */
/* Callouts — boxed inline notice with colored left border. */
.callout {
border-left: 4px solid var(--primary);
background: var(--primary-light);
color: #111827;
padding: 12px 16px;
border-radius: 6px;
margin: 12px 0;
}
.callout-info { border-left-color: var(--primary); background: var(--primary-light); }
.callout-warn { border-left-color: #C2410C; background: #FED7AA; }
.callout-success { border-left-color: #047857; background: #D1FAE5; }
.callout-danger { border-left-color: #B91C1C; background: #FEE2E2; }
.callout > :first-child { margin-top: 0; }
.callout > :last-child { margin-bottom: 0; }
/* 16:9 wrapper for YouTube/Vimeo/Loom iframes. */
.video-embed {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
border-radius: 8px;
margin: 14px 0;
background: black;
}
.video-embed iframe,
.video-embed video,
.video-embed a {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
border: 0;
}
.video-embed a {
display: flex;
align-items: center;
justify-content: center;
color: #fff;
text-decoration: underline;
}
/* Section divider — subtle top margin to separate topics. */
.news-section { margin-top: 28px; }
.news-section:first-child { margin-top: 0; }
/* 2- / 3-column responsive grids. */
.news-grid-2,
.news-grid-3 {
display: grid;
gap: 16px;
margin: 12px 0;
}
.news-grid-2 { grid-template-columns: repeat(2, 1fr); }
.news-grid-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 720px) {
.news-grid-2,
.news-grid-3 { grid-template-columns: 1fr; }
}
/* Anchor styled as primary button. */
a.news-cta {
display: inline-block;
background: var(--primary);
color: white !important; /* override link colors on /home */
text-decoration: none !important;
padding: 9px 18px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
margin: 8px 0;
transition: background 120ms ease;
}
a.news-cta:hover { background: #0056A3; }
/* --- .news-content scope: tables, code, pre, blockquote, headings ---
Anything inside the long-form body needs these refinements; they
would over-style chrome elsewhere on the site so we scope them. */
.news-content { font-size: 15px; line-height: 1.6; color: #111827; }
.news-content h1 { font-size: 24px; margin: 24px 0 12px; line-height: 1.25; }
.news-content h2 { font-size: 20px; margin: 22px 0 10px; line-height: 1.3; }
.news-content h3 { font-size: 17px; margin: 18px 0 8px; }
.news-content p { margin: 0 0 12px; }
.news-content ul,
.news-content ol { margin: 0 0 12px 24px; }
.news-content li { margin: 4px 0; }
.news-content blockquote {
border-left: 4px solid #E5E7EB;
padding: 6px 16px;
margin: 14px 0;
color: #4B5563;
font-style: italic;
background: #F9FAFB;
border-radius: 0 6px 6px 0;
}
/* Code: inline = light gray pill; block = dark navy with amber text.
The :not(pre code) split keeps inline code from overriding the
pre>code dark scheme — earlier versions had pre>code inheriting
the light background from the inline rule, producing the
"yellow-on-silver" unreadable combo. */
.news-content code {
background: #F3F4F6;
color: #0F172A;
padding: 1px 6px;
border-radius: 4px;
font-family: var(--font-mono);
font-size: 13px;
}
.news-content pre {
background: #0F172A;
color: #FBBF24;
padding: 14px 18px;
border-radius: 8px;
overflow-x: auto;
margin: 14px 0;
line-height: 1.55;
}
.news-content pre code {
background: transparent;
color: inherit;
padding: 0;
font-size: 13px;
border-radius: 0;
}
/* Tables: border-collapse, header band, zebra rows on hover, compact
padding. Authors get a presentable default without writing custom
classes; .news-grid-2 / .news-grid-3 covers the layout-grid use case. */
.news-content table {
width: 100%;
border-collapse: collapse;
margin: 14px 0;
font-size: 14px;
background: white;
border: 1px solid #E5E7EB;
border-radius: 8px;
overflow: hidden;
}
.news-content thead { background: #F9FAFB; }
.news-content th {
text-align: left;
padding: 10px 14px;
font-weight: 600;
color: #374151;
border-bottom: 1px solid #E5E7EB;
}
.news-content td {
padding: 10px 14px;
border-bottom: 1px solid #F3F4F6;
vertical-align: top;
}
.news-content tbody tr:last-child td { border-bottom: none; }
.news-content tbody tr:hover { background: #F9FAFB; }
.news-content table code {
/* Inline code inside table cells reads better with a slightly
lighter background than the page default. */
background: #EEF2F7;
}
/* Reusable hero block — blue gradient, eyebrow + title + lead.
Defined AFTER the .news-content overrides so its `color: white` on
nested headings wins source-order ties (both selectors share
specificity 0,1,1 since they're "one class + one type"). Authors
drop `<section class="news-hero">` anywhere — /home perex, /news
body, /admin/news preview. */
.news-hero {
background: linear-gradient(135deg, var(--primary) 0%, #0056A3 100%);
color: white;
border-radius: 16px;
padding: 28px 32px;
margin: 18px 0;
box-shadow: 0 8px 24px rgba(31, 138, 94, 0.18);
}
.news-hero .eyebrow {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
opacity: 0.85;
margin-bottom: 10px;
color: white;
}
.news-hero h1,
.news-hero h2,
.news-hero h3 {
color: white;
margin: 0 0 8px;
font-weight: 600;
line-height: 1.25;
}
.news-hero h1 { font-size: 26px; letter-spacing: -0.4px; }
.news-hero h2 { font-size: 20px; }
.news-hero h3 { font-size: 17px; }
.news-hero .lead,
.news-hero p {
font-size: 15px;
opacity: 0.94;
line-height: 1.55;
margin: 0;
color: white;
}
.news-hero a {
color: #FBBF24;
text-decoration: underline;
}
.news-hero code {
background: rgba(15, 23, 42, 0.55);
color: #FBBF24;
border: 1px solid rgba(255, 255, 255, 0.18);
padding: 1px 6px;
border-radius: 4px;
font-size: 12.5px;
}
/* /home perex slot: the same callout / video-embed / hero classes
work inside .home-news-body without selector duplication, since the
rules above don't depend on a parent. The home-specific chrome
(margin, header strip) lives in home_not_onboarded.html. */
/* ==============================================================
Marketplace format guide page (/marketplace/format-guide).
Renders curator-facing markdown via markdown-it-py; the rules
below scope the document chrome to .format-guide so we don't
override <h1>/<pre>/<table> styling on other pages. Dark <pre>
matches the JSON snippets the guide includes.
============================================================== */
.container:has(.format-guide) { max-width: none; padding: 24px 16px; }
.format-guide {
max-width: 880px;
margin: 0 auto;
padding: 0;
line-height: 1.55;
}
.format-guide h1 {
font-size: 28px; font-weight: 700;
margin: 0 0 8px;
color: var(--text-primary, #111827);
}
.format-guide .lead {
font-size: 15px; color: var(--text-secondary, #6b7280);
margin-bottom: 32px; padding-bottom: 16px;
border-bottom: 1px solid var(--border, #e5e7eb);
}
.format-guide h2 {
font-size: 20px; font-weight: 600;
margin: 32px 0 12px;
color: var(--text-primary, #111827);
}
.format-guide h3 {
font-size: 16px; font-weight: 600;
margin: 24px 0 8px;
color: var(--text-primary, #111827);
}
.format-guide p {
margin: 0 0 12px;
color: var(--text-primary, #111827);
}
.format-guide a { color: var(--primary, #6366f1); text-decoration: none; }
.format-guide a:hover { text-decoration: underline; }
.format-guide code {
background: var(--surface-alt, #f3f4f6);
padding: 2px 6px; border-radius: 4px;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 13px;
}
.format-guide pre {
background: #0f172a; color: #e2e8f0;
padding: 16px; border-radius: 8px; overflow-x: auto;
font-size: 13px;
font-family: var(--font-mono, ui-monospace, monospace);
line-height: 1.5;
}
.format-guide pre code { background: none; color: inherit; padding: 0; }
.format-guide table {
width: 100%; border-collapse: collapse;
margin: 12px 0 20px;
font-size: 13.5px;
}
.format-guide th, .format-guide td {
text-align: left; padding: 8px 12px;
border-bottom: 1px solid var(--border-light, #f3f4f6);
}
.format-guide thead th {
background: var(--surface-alt, #f9fafb);
font-weight: 600; font-size: 12px;
text-transform: uppercase; letter-spacing: 0.4px;
color: var(--text-secondary, #6b7280);
}
.format-guide ul, .format-guide ol {
margin: 0 0 12px; padding-left: 24px;
}
.format-guide li { margin-bottom: 6px; }
.format-guide blockquote {
border-left: 3px solid var(--primary, #6366f1);
margin: 12px 0; padding: 4px 12px;
background: var(--surface-alt, #f9fafb);
border-radius: 0 8px 8px 0;
}
.format-guide .back-link {
display: inline-flex; align-items: center; gap: 4px;
color: var(--text-secondary, #6b7280);
font-size: 13px;
margin-bottom: 16px;
}
.format-guide .back-link:hover { color: var(--primary, #6366f1); }
/* =====================================================
Canonical button family (Task 4)
.btn + .btn-primary | .btn-secondary | .btn-ghost | .btn-danger
+ .btn-sm | .btn-lg
Base .btn, .btn-primary, .btn-secondary, .btn-sm come from the
absorbed style.css block above (no change needed — they already use
the new tokens via the legacy-alias layer). This section appends
selector-list aliases for legacy class names ( .btn-secondary-v2, .btn-ghost-v2, .modal-btn) and defines the new
variants (.btn-ghost, .btn-danger, .btn-lg) that the legacy stylesheet
didn't ship.
Aliases removed in Task 16 once templates migrate to canonical names.
Cascade-order note: this section is the LAST in the file, so it wins
over both the absorbed legacy rules AND any earlier v2-suffixed rules
(e.g. .btn-primary-v2 hover color in the absorbed block). Adding
per-page <style> overrides will still beat this on equal-specificity
selectors due to source order — Tasks 814 strip those.
===================================================== */
.btn-primary {
background-color: var(--ds-primary);
color: #fff;
border: 1px solid var(--ds-primary);
transition: all 0.15s ease;
}
.btn-primary:hover,
.btn-primary-v2:hover,
.modal-btn.primary:hover {
background-color: var(--ds-primary-dark);
border-color: var(--ds-primary-dark);
box-shadow: 0 2px 8px rgba(46, 168, 119, 0.3);
}
.btn-secondary {
background-color: var(--surface);
color: var(--text-primary);
border: 1px solid var(--border);
}
.btn-secondary:hover,
.btn-secondary-v2:hover,
.modal-btn:not(.primary):not(.danger):hover {
background-color: var(--border-light);
}
.btn-ghost {
background-color: transparent;
color: var(--text-secondary);
border: 1px solid transparent;
}
.btn-ghost:hover,
.btn-ghost-v2:hover {
background-color: var(--border-light);
color: var(--text-primary);
}
.btn-danger {
background-color: #fff;
color: var(--error);
border: 1px solid var(--error);
}
.btn-danger:hover,
.modal-btn.danger:hover {
background-color: var(--error);
color: #fff;
}
.btn-warning {
background: #f59e0b;
color: #fff;
border: 1px solid #d97706;
}
.btn-warning:hover { filter: brightness(1.06); }
.btn-lg { padding: 12px 20px; font-size: var(--text-md); }
.btn:disabled,
.btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
.btn:focus { outline: none; }
.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* =====================================================
Canonical form controls (Task 5)
.search-input | .filter-bar | .filter-pill | .form-input
Selector-list aliases for legacy per-page classes ( .marketplaces-search, .kb-search, .filters-card, .pill) so existing
markup keeps rendering until migration tasks swap them out.
===================================================== */
.search-input,
.filter-bar input[type="search"],
.filter-bar input[type="text"],
.filter-bar select {
height: 36px;
padding: 0 var(--space-3);
font-family: var(--font-primary);
font-size: var(--text-sm);
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.search-input::placeholder,
.users-search::placeholder,
.marketplaces-search::placeholder,
.kb-search::placeholder,
.filter-bar input::placeholder { color: var(--text-muted); }
.search-input:focus,
.users-search:focus,
.marketplaces-search:focus,
.kb-search:focus,
.filter-bar input:focus,
.filter-bar select:focus {
outline: none;
border-color: var(--primary);
box-shadow: var(--focus-ring);
}
.filter-bar {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
padding: var(--space-3) var(--space-4);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
margin-bottom: var(--space-4);
}
.filter-bar > .search-input,
.filters-card > .search-input { flex: 1 1 240px; min-width: 200px; }
.filter-pill {
display: inline-flex;
align-items: center;
height: 28px;
padding: 0 var(--space-3);
font-family: var(--font-primary);
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-secondary);
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius-full);
cursor: pointer;
transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
text-decoration: none;
}
.filter-pill:hover {
background: var(--border-light);
color: var(--text-primary);
}
.filter-pill.is-active,
.filter-pill[aria-pressed="true"] {
background: var(--primary-light);
color: var(--primary);
border-color: var(--primary);
}
/* Form input — sibling of .search-input for forms (multi-line capable). */
.form-input,
input.form-input,
select.form-input,
textarea.form-input {
width: 100%;
min-height: 36px;
padding: var(--space-2) var(--space-3);
font-family: var(--font-primary);
font-size: var(--text-sm);
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
textarea.form-input {
min-height: 96px;
resize: vertical;
font-family: var(--font-mono);
line-height: 1.5;
}
textarea.form-textarea {
width: 100%;
min-height: 80px;
padding: var(--space-2) var(--space-3);
font-family: var(--font-primary);
font-size: var(--text-sm);
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
resize: vertical;
line-height: normal;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
textarea.form-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: var(--focus-ring);
}
textarea.form-textarea::placeholder { color: var(--text-muted); }
.form-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: var(--focus-ring);
}
.form-input::placeholder { color: var(--text-muted); }
/* chip-input widget — set font on the container so the inner input's
`font: inherit` inline style picks up the right value */
.chip-input {
font-family: var(--font-primary);
font-size: var(--text-sm);
color: var(--text-primary);
}
/* =====================================================
Page-header primitive (Task 6)
.page-header + __main / __title / __subtitle / __actions / __eyebrow
+ variants: .page-header--hero | .page-header--compact
+ .tab-strip (paired tab row for marketplace tabs etc.)
===================================================== */
.page-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-4);
margin: var(--space-6) 0 var(--space-5);
flex-wrap: wrap;
}
.page-header__main { min-width: 0; flex: 1 1 auto; }
.page-header__title {
margin: 0;
font-size: 22px;
font-weight: var(--font-semibold);
color: var(--text-primary);
line-height: 1.2;
}
.page-header__subtitle {
margin: var(--space-2) 0 0;
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.4;
}
.page-header__eyebrow {
margin: 0 0 var(--space-2);
font-size: var(--text-xs);
font-weight: var(--font-semibold);
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-secondary);
}
.page-header__actions {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
}
/* Hero variant — gradient background, larger title, light eyebrow.
Tuned to match the look of admin_tokens.html's per-page .tokens-hero
(which analysts already liked): tighter padding, 14px radius, soft
blue glow shadow. Applied across all primary-content pages so the
admin section reads as one product instead of a patchwork. */
.page-header--hero {
padding: 28px 32px 24px;
/* Spans the parent container width. `.container` already provides
the canonical max-width (1280px) + 32px side gutters, so the
hero sits flush with the rest of the page content rather than
self-constraining. Standalone pages without a `.container`
wrapper still get a sensible width via `.container--wide` /
`.container--narrow` modifiers on their own wrappers. */
margin: 0 0 var(--space-5);
box-sizing: border-box;
/* Hero gradient — `--ds-hero-bg` / `--ds-hero-bg-deep` flip
between themes (navy by default, blue under
`data-theme="blue"`). Markup and class names don't change. */
background: linear-gradient(135deg, var(--ds-hero-bg) 0%, var(--ds-hero-bg-deep) 100%);
color: #fff;
border-radius: 14px;
/* Shadow tint follows the gradient via `--ds-hero-shadow`. */
box-shadow: 0 4px 16px var(--ds-hero-shadow);
position: relative;
overflow: hidden;
}
.page-header--hero .page-header__main { color: inherit; }
.page-header--hero .page-header__title {
font-size: 28px;
font-weight: 600;
letter-spacing: -0.01em;
color: #fff;
margin: 0 0 6px;
}
.page-header--hero .page-header__subtitle {
color: rgba(255, 255, 255, 0.85);
font-size: 13.5px;
margin: 6px 0 0;
max-width: 720px;
}
.page-header--hero .page-header__eyebrow {
/* Eyebrow accent follows the theme: mint-green on navy
(default) or translucent white on blue. `--ds-hero-eyebrow`
is set per-theme in design-tokens.css. */
color: var(--ds-hero-eyebrow);
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.2px;
margin: 0 0 6px;
}
.page-header--hero .page-header__actions a,
.page-header--hero .page-header__actions button {
color: #fff;
}
/* Any anchor anywhere inside the gradient hero — default `--primary`
blue would disappear on the blue background, so force a white,
underlined link with full contrast. Applies without needing a
per-page class. */
.page-header--hero a {
color: #fff;
text-decoration: underline;
text-underline-offset: 2px;
}
.page-header--hero a:hover { color: rgba(255, 255, 255, 0.85); }
/* `<code>` chips inside the gradient hero — translucent white pill
with white amber-yellow text so they read as code samples
against the navy background. The global `code` rule sets a
dark `color: var(--text-primary)` that turned chips into dark
text on a faint white pill (invisible on navy). */
.page-header--hero code {
background: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.22);
color: #ffd866;
font-family: var(--font-mono);
font-size: 12px;
padding: 1px 6px;
border-radius: 4px;
}
@media (max-width: 640px) {
.page-header--hero { padding: 24px 20px 20px; border-radius: 12px; }
.page-header--hero .page-header__title { font-size: 22px; }
}
/* Compact variant — for dense admin index pages that have many controls
already and don't need vertical generosity. Smaller title, tighter
spacing, no border. */
.page-header--compact {
margin: var(--space-4) 0 var(--space-3);
}
.page-header--compact .page-header__title { font-size: 18px; }
/* Tab strip — the secondary row pattern used by /marketplace?tab=… and
similar tabbed pages. Sits below the .page-header (or inside it on
hero-variant pages). */
.tab-strip {
display: flex;
gap: var(--space-1);
align-items: center;
border-bottom: 1px solid var(--border);
margin-bottom: var(--space-5);
}
.tab-strip__item {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-2) var(--space-3);
font-family: var(--font-primary);
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-secondary);
text-decoration: none;
background: transparent;
border: 0;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
cursor: pointer;
transition: color var(--transition-fast), border-color var(--transition-fast);
}
.tab-strip__item:hover { color: var(--text-primary); }
.tab-strip__item.is-active,
.tab-strip__item[aria-selected="true"] {
color: var(--primary);
border-bottom-color: var(--primary);
}
/* =====================================================
Data display + feedback primitives (Task 7)
.data-table (+ --compact) | .empty-state | .toast | .stat-card
Selector-list aliases for legacy per-page tables ( .gp-table, .marketplaces-table, .audit-table) so existing markup
keeps rendering until migration tasks swap them out.
===================================================== */
.data-table,
.ad-table,
.ea-table,
.md-table,
.members-table,
.obs-table,
.overview-stats-table,
.registry-table,
.sample-table,
.sched-table,
.sess-table,
.sub-table,
.subs-table,
.ud-table {
width: 100%;
border-collapse: collapse;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
font-variant-numeric: tabular-nums;
}
.data-table thead,
.ad-table thead,
.ea-table thead,
.md-table thead,
.members-table thead,
.obs-table thead,
.overview-stats-table thead,
.registry-table thead,
.sample-table thead,
.sched-table thead,
.sess-table thead,
.sub-table thead,
.subs-table thead,
.ud-table thead {
background: var(--border-light);
}
.data-table th,
.ad-table th,
.ea-table th,
.md-table th,
.members-table th,
.obs-table th,
.overview-stats-table th,
.registry-table th,
.sample-table th,
.sched-table th,
.sess-table th,
.sub-table th,
.subs-table th,
.ud-table th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-size: var(--text-xs);
font-weight: var(--font-semibold);
text-transform: uppercase;
letter-spacing: 0.4px;
color: var(--text-secondary);
border-bottom: 1px solid var(--border);
}
.data-table td,
.ad-table td,
.ea-table td,
.md-table td,
.members-table td,
.obs-table td,
.overview-stats-table td,
.registry-table td,
.sample-table td,
.sched-table td,
.sess-table td,
.sub-table td,
.subs-table td,
.ud-table td {
padding: var(--space-3) var(--space-4);
font-size: var(--text-sm);
color: var(--text-primary);
border-bottom: 1px solid var(--border-light);
vertical-align: middle;
}
.data-table tbody tr:last-child td,
.ad-table tbody tr:last-child td,
.ea-table tbody tr:last-child td,
.md-table tbody tr:last-child td,
.members-table tbody tr:last-child td,
.obs-table tbody tr:last-child td,
.overview-stats-table tbody tr:last-child td,
.registry-table tbody tr:last-child td,
.sample-table tbody tr:last-child td,
.sched-table tbody tr:last-child td,
.sess-table tbody tr:last-child td,
.sub-table tbody tr:last-child td,
.subs-table tbody tr:last-child td,
.ud-table tbody tr:last-child td {
border-bottom: none;
}
.data-table tbody tr:hover,
.ad-table tbody tr:hover,
.ea-table tbody tr:hover,
.md-table tbody tr:hover,
.members-table tbody tr:hover,
.obs-table tbody tr:hover,
.overview-stats-table tbody tr:hover,
.registry-table tbody tr:hover,
.sample-table tbody tr:hover,
.sched-table tbody tr:hover,
.sess-table tbody tr:hover,
.sub-table tbody tr:hover,
.subs-table tbody tr:hover,
.ud-table tbody tr:hover {
background: var(--border-light);
}
/* Compact modifier — for dense lists like /admin/activity (audit log). */
.data-table--compact th,
.data-table--compact td {
padding: var(--space-2) var(--space-3);
font-size: var(--text-xs);
}
/* =====================================================
.empty-state — for "no records" / "no results" panels
===================================================== */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-3);
padding: var(--space-8) var(--space-6);
text-align: center;
background: var(--surface);
border: 1px dashed var(--border);
border-radius: var(--radius-lg);
color: var(--text-secondary);
}
.empty-state__icon {
font-size: 32px;
opacity: 0.5;
line-height: 1;
}
.empty-state__title {
margin: 0;
font-size: var(--text-md);
font-weight: var(--font-medium);
color: var(--text-primary);
}
.empty-state__description {
margin: 0;
max-width: 480px;
font-size: var(--text-sm);
line-height: 1.5;
}
.empty-state__actions {
margin-top: var(--space-3);
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
justify-content: center;
}
/* =====================================================
.toast — global notification surface
Paired with window.appToast({kind, msg, timeout}) in app.js
===================================================== */
.toast-container {
position: fixed;
right: var(--space-5);
bottom: var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-2);
z-index: 9999;
pointer-events: none;
}
.toast {
pointer-events: auto;
min-width: 240px;
max-width: 360px;
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
background: var(--surface);
border: 1px solid var(--border);
box-shadow: var(--shadow-elevated);
font-size: var(--text-sm);
color: var(--text-primary);
cursor: pointer;
animation: toast-in 200ms ease;
}
.toast.is-success { border-left: 3px solid var(--success); }
.toast.is-warning { border-left: 3px solid var(--warning); }
.toast.is-error { border-left: 3px solid var(--error); }
.toast.is-info { border-left: 3px solid var(--primary); }
@keyframes toast-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: none; }
}
/* =====================================================
.stat-card — for dashboard metric tiles
===================================================== */
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-1);
min-width: 0;
}
.stat-card__label {
font-size: var(--text-xs);
font-weight: var(--font-semibold);
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-secondary);
}
.stat-card__value {
font-size: 28px;
font-weight: var(--font-semibold);
color: var(--text-primary);
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.stat-card__hint {
font-size: var(--text-xs);
color: var(--text-muted);
}
.stat-card__icon {
font-size: 18px;
opacity: 0.6;
}
/* Variant: with primary accent. */
.stat-card--accent {
border-color: var(--primary);
background: var(--primary-light);
}
.stat-card--accent .stat-card__value { color: var(--primary); }
/* Stat row container — gap-spaced grid for the dashboard tile row. */
.stat-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--space-3);
margin-bottom: var(--space-5);
}