* docs(plan): design-system unification plan (post-review revisions)
Plan covers consolidating two CSS files into one, introducing
canonical primitives (.btn family, .search-input, .filter-bar,
.page-header, .data-table, .empty-state, .toast, .stat-card,
.tab-strip), unifying the top-nav Admin trigger with sibling
links, and migrating 41 templates that today carry inline
<style> blocks.
Post-review revisions: nav fix moved to first commit (user
complaint lands first); sticky-header and dark-mode skeleton
tasks dropped (defer to follow-up PRs); contract test class
detection tokenizes class="..." attributes properly; baseline
screenshot loop added to Task 0; vendor-token grep widened.
* fix(nav): unify Admin trigger with sibling nav links
The top-nav Admin entry is a <button class="app-nav-link
app-nav-menu-trigger">, siblings are <a class="app-nav-link">.
.app-nav-menu-trigger used to override .app-nav-link with
"color: inherit; font: inherit", resetting font-size from 13px
back to body default and color from --text-secondary to body
color. Active state diverged too: .is-active on links used
--primary blue, [aria-expanded=true] on the button used
--border-light grey.
Fix: expand .app-nav-link so it covers <button>-element resets
(font-family: inherit, border: 0, background: transparent,
cursor: pointer, display: inline-flex for chevron alignment).
Add [aria-expanded="true"] as another active-state selector
so the dropdown's open state highlights identically to .is-active
on links. Delete the now-redundant .app-nav-menu-trigger rules
that stripped button chrome.
Extract the inline <script> from _app_header.html into a new
app/web/static/app.js (loaded by base.html only — base_login.html
has no nav). Sets up window.appUI.wireDropdown for both the user
menu and the Admin dropdown via DOMContentLoaded.
* style(css): consolidate style.css into style-custom.css + add cache-bust
One stylesheet for the whole web UI:
- style.css (1086 lines, legacy Google-inspired tokens + components)
absorbed into style-custom.css under a labeled block, placed after
the modern :root + body so style-custom's component rules continue
to override the legacy ones (preserves the original cascade order
that came from loading style.css first).
- style.css deleted; <link> dropped from base.html + base_login.html.
- static_url() now appends ?v=<mtime> to /static/<path>. Cheap
per-request os.stat — auto-invalidates browser + proxy caches on
redeploy without operator intervention. Mtime survives across
uvicorn restarts as long as the file content is unchanged.
Legacy classes (.btn, .card, .login-*, .badge, .code-block, .flash,
.form-group, .username-box, .btn-copy, .auth-tabs, .divider, etc.)
still render — they live in style-custom.css now. Login pages,
error page, password setup, and the dashboard's Claude Code Setup
card all kept working in browser smoke.
* test(design): contract test for design-system invariants
7 structural invariants enforced from this commit onwards:
- style.css must stay deleted
- no template links style.css via static_url
- exactly one bare :root block in style-custom.css
- canonical primitives declared (.btn, .btn-primary, .search-input,
.filter-bar, .page-header, .data-table, .empty-state, .toast, …)
- no deprecated class names in templates (.users-table, .gp-table,
.marketplaces-table, .audit-table, .users-search, .marketplaces-search,
.modal-btn, .btn-primary-v2, …)
- app.js loaded by base.html, NOT by base_login.html
- 3 helper-level unit tests for the class-attribute tokenizer
(multi-line attrs, Jinja-conditional fragments, false-positive prose)
Two of the assertions intentionally start FAILING after this commit
(missing primitives + legacy class refs in 7 admin templates) and
will turn green as Tasks 4–7 add primitives and Tasks 8–15 migrate
the templates.
* feat(css): canonical button family + legacy token aliases
Adds at top of :root: legacy token aliases (--bg, --card-bg, --text,
--text-light, --secondary, --radius) pointing at modern equivalents.
Absorbed style.css rules referenced these names; without aliases
they fell back to 'unset'. Aliases live until Task 16 alongside
their absorbed rules.
Appends canonical .btn variants at end of file (last cascade):
.btn-primary + .btn-primary-v2 + .modal-btn.primary (alias group)
.btn-secondary + .btn-secondary-v2 + .modal-btn:not(.primary):not(.danger)
.btn-ghost + .btn-ghost-v2
.btn-danger + .modal-btn.danger
.btn-lg
.btn:disabled + .btn:focus-visible (focus ring via --focus-ring)
Existing absorbed .btn, .btn-primary, .btn-secondary, .btn-sm rules
remain — the canonical block adds the missing variants + selector-list
aliases so .modal-btn and v2 markup keep rendering until migration
tasks swap them out.
Contract test: .btn-danger now declared (one less missing primitive).
Browser smoke: /admin/tokens hero + filter pills + empty state render
correctly with the absorbed style.css rules now backed by real tokens.
* feat(css): form-control primitives — .search-input + .filter-bar + .filter-pill + .form-input
Canonical filter bar shape: 36px-height inputs (matches button height
for vertical rhythm), 28px pills with .is-active state, consistent
focus ring via --focus-ring token.
Selector-list aliases for legacy per-page classes:
- .users-search / .marketplaces-search / .kb-search → .search-input
- .filters-card → .filter-bar
- .pill[aria-pressed="true"] also matches the .filter-pill active state
.form-input added as a sibling of .search-input for forms — same
baseline height + radius + focus treatment, with textarea.form-input
auto-sizing to min 96px and using the mono font (matches CSV/SQL
pasted-snippet patterns on /admin/agent-prompt + /admin/workspace-prompt).
Contract test: .search-input + .filter-bar + .filter-pill now declared.
* feat(css): .page-header primitive + variants + .tab-strip
Canonical page-header pattern with title (22px) + optional subtitle +
optional eyebrow + right-aligned actions slot. Two modifiers:
- .page-header--hero: gradient background (primary→primary-dark),
28px white title, semi-transparent subtitle/eyebrow. For
/marketplace, /store, /profile-style pages that already use this
layout via per-page inline <style>. Migration tasks delete the
duplicated rules.
- .page-header--compact: 18px title for dense admin index pages.
.tab-strip + .tab-strip__item — the secondary tab row pattern used by
/marketplace?tab=flea and similar. .is-active / [aria-selected=true]
both flip the active treatment (primary color + bottom border).
Contract test: .page-header / __title / __subtitle / __actions all
now declared (4 fewer missing primitives).
* feat(css+js): .data-table + .empty-state + .toast + .stat-card primitives
Last primitive batch. All 8 canonical-primitives invariants in
test_design_system_contract.py now green; only the template-migration
test fails (expected — Tasks 8–15).
.data-table (+ --compact modifier): selector-list aliases for legacy
per-page table classes (.users-table, .gp-table, .marketplaces-table,
.audit-table) so existing markup keeps rendering until migration.
Compact modifier shrinks padding + font for dense lists (audit log).
.empty-state with __icon / __title / __description / __actions —
replaces the ad-hoc 'no results' rendering scattered across pages
(corporate_memory, admin_users, admin_marketplaces, etc.).
.toast / .toast-container — paired with window.appToast({kind, msg,
timeout}) appended to app.js. Bottom-right stacked, click-to-dismiss,
auto-dismiss after 4s by default. Kind 'success' / 'warning' / 'error'
/ 'info' shows a 3px colored left border.
.stat-card (+ --accent variant) + .stat-row grid — for the dashboard
metric tile row.
* style(templates): migrate 8 templates off deprecated class names
Mechanical class-attribute rewrite via tokenizer (preserves Jinja
conditionals + multi-line attrs):
modal-btn primary -> btn btn-primary
modal-btn danger -> btn btn-danger
modal-btn -> btn btn-secondary
users-table -> data-table
gp-table -> data-table
marketplaces-table -> data-table
audit-table -> data-table
users-search -> search-input
marketplaces-search -> search-input
8 templates touched: admin_groups, admin_marketplaces, admin_tokens,
admin_users, admin_welcome, admin_workspace_prompt, my_tokens,
corporate_memory_admin. 43 lines updated total.
Inline <style> blocks in these templates still define rules for the
old class names — those rules no longer match anything and become
dead code, removed in Task 16's alias cleanup along with the
selector-list aliases in style-custom.css.
Contract test (tests/test_design_system_contract.py) now fully green:
9/9 invariants enforced from this commit onward.
* feat(css): extend .data-table selector list to 13 more bespoke -table classes
Visual unification of remaining tables across the codebase without
per-template edits. The .data-table baseline rules (uppercase header
tracking, 12px padding, hover state, border-radius) now apply to:
.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
These class names live in 12 templates (activity_center, admin_access,
admin_group_detail, admin_scheduler_runs, admin_sessions,
admin_store_submissions, admin_tables, admin_usage, admin_user_detail,
catalog, me_debug, profile_sessions) that have their own per-page
<style> blocks. Per-page rules with higher specificity still win for
their custom needs (column widths, etc.) — this commit only sets a
shared baseline so every table renders with the same chrome.
Contract test stays green: 9/9 invariants enforced.
* style(css): remove now-unused legacy class aliases
Phase A renamed 8 templates off these names; no markup references
them any more, so the selector-list memberships are dead weight.
Removed from style-custom.css:
.btn-primary-v2 / .btn-secondary-v2 / .btn-ghost-v2
.modal-btn / .modal-btn.primary / .modal-btn.danger /
.modal-btn:not(.primary):not(.danger)
.users-search / .marketplaces-search / .kb-search
.users-table / .gp-table / .marketplaces-table / .audit-table
.filters-card
37 lines smaller. Contract test catches any reintroduction.
KEPT aliases (still in untouched template markup):
- .pill (marketplace_plugin_detail.html, marketplace.html — these
pages weren't part of Phase A's deprecated-class sweep; their
own .pill CSS rules still apply)
- All .data-table family extensions (.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) — these still render data tables in 12 templates;
selector-list aliasing keeps them visually unified with .data-table
baseline.
- Legacy token aliases (--bg / --text / --text-light / --secondary /
--card-bg / --radius) — still resolve absorbed style.css rules.
Templates' inline <style> blocks still contain dead rules for the
renamed classes (.users-search, .modal-btn, etc.); harmless but
bloat. Optional follow-up: a separate sweep can drop those.
* docs(changelog): design-system unification under [Unreleased]
* feat(css): unify page-shell width — .container baseline 1280px + modifiers
Inventory found 30+ unique max-width values across templates (280px
login → 1600px admin/tables). The legacy .container default was 800px,
which made every admin page set its own wider inline override —
30+ ad-hoc widths drifted as a result.
Canonical: .container max-width = var(--width-app) (1280px). Pages
that need a different shape opt in via modifiers:
.container--narrow → var(--width-narrow) (800px) — long-form text,
setup wizards
.container--wide → var(--width-wide) (1400px) — admin lists,
marketplace grids
.container--full → max-width: none — hero / landing
Pages that already set a NARROWER inline max-width (setup, login flows
inside .login-card, etc.) still render at their narrower size — the
inline override beats the new canonical 1280px. The visible change
hits the ~20 admin pages currently rendering at 800px via the legacy
default, which jump to 1280px and pick up consistent breathing room.
Spacing also normalized: padding 24px 20px → var(--space-6) var(--space-5).
* fix(home+catalog): gut dashboard sections + remove confusing toggle + fix table count
Dashboard /home cleanup:
- Remove 'Your Data' card — Data Packages is already a top-nav entry,
so duplicating data sources on the landing page just adds noise.
- Remove 'Account' card — group memberships + scripts + last sync
belong on /profile, not on the welcome screen.
- Remove entire right-column (Corporate Memory + Activity Center
widgets) — both surfaces have dedicated admin pages reachable from
the Admin dropdown.
- Keep stats row (Tables/Columns/Rows/Data Size/Unstructured),
env-setup-CTA, and Notifications card.
/catalog cleanup:
- Strip the 'Always included' badge + the locked toggle-switch from
Core Business Data and Business Metrics cards. The toggle was
always 'checked disabled' — it visually looked like a switch but
could not be toggled, which was confusing. The 'Always included'
copy itself was redundant once the toggle was gone. Agnes Internal
already rendered without these, so the three cards are now visually
consistent.
Catalog data_stats fix:
- 'total_tables' was len(sync_state) — counted only tables that had
ever synced, so a 30-row table_registry with 0 ever synced rendered
as '0 tables'. Switched to len(tables) — the registered
business-data table list — so the count reflects what's actually
available, not what's been touched.
* fix(home): real stat numbers + drop unstructured tile + cleanup dead CSS
Dashboard stats were hardcoded zeros (columns: 0, size_display:
'0 MB', unstructured_display: '0 MB') and the table counter pulled
from sync_state (synced) instead of table_registry (registered).
On a fresh deployment with 30 registered tables and 0 ever synced,
the page rendered '0 / 0 / 0 / 0 MB / 0 MB' — useless.
Now:
- Tables: COUNT(*) FROM table_registry WHERE source_type != 'internal'.
Matches the /catalog Core Business Data counter.
- Columns: SUM(sync_state.columns). Zero only when nothing's synced yet.
- Rows: unchanged (SUM(sync_state.rows), already correct).
- Data Size: SUM(sync_state.file_size_bytes), human-formatted via
inline _fmt_bytes helper (KB/MB/GB).
- Unstructured: tile dropped — was always '0 MB' and had no source.
- last_updated: now derived from sync_state max(last_sync), wasn't set
before so the 'Synced …' tag never rendered.
Dashboard.html cleanup: ~725 lines of orphan inline <style> removed —
.section-title, .data-source*, .toggle-switch*, .catalog-cta*,
.memory-card / .memory-stat / .memory-description / .memory-footer
/ .btn-memory, .activity-card / .activity-stat / .activity-text
/ .btn-activity, .account-grid / .account-row / .account-scripts
/ .badge-role / .badge-group / .cron-line, .badge-included /
.badge-beta / .badge-demo. All matched markup deleted in the
previous commit; the CSS was dead code until now.
* ui(catalog): rename page heading 'Data Catalog' → 'Data Packages'
The top-nav entry says 'Data Packages' but the page itself said
'Data Catalog' — confusing two-name product. Aligns the heading and
<title> with the nav label. Subtitle trimmed too: 'manage your
subscriptions' was a vestige of the toggle UI that just got removed,
replaced with a one-liner describing what the page is for.
Two other 'Data Catalog' strings stay: they live inside the table-
profiler overlay JS and refer to an EXTERNAL catalog system (e.g.
OpenMetadata / Atlan) that an operator may link to per table — that
is a generic term for any external data-catalog product, not our
page name.
* fix(nav): dropdown clicks always work + mutual-exclusion close
Two bugs in the wireDropdown helper:
1. Clicking trigger B while trigger A's menu was open left both open.
e.stopPropagation() in trigger.click prevented the document-click
handler from firing, so trigger A's open menu had no way to learn
that something else was clicked. Net effect: state diverged across
the two dropdowns the more you clicked.
2. The target-vs-trigger equality check (e.target !== trigger) was
strict. Clicking the chevron <svg> inside the button reports the
svg or its <path> child as e.target — not the button — so removing
stopPropagation alone would trip the close branch in the same
click that just opened the panel.
Fix both at once: drop e.stopPropagation() AND switch the doc-handler
guard to trigger.contains(e.target). Now any click outside both the
trigger subtree and the panel subtree closes; any click on another
trigger closes via the OTHER dropdown's doc handler; clicks inside
the trigger (button OR svg child) are fully ignored by the doc
handler and only the trigger's own toggle handler fires.
* feat(ui): canonical blue-gradient hero on every admin page
The UI had a per-page hero pattern on ~10 onboarding/marketing pages
(admin_tokens / profile / install / setup_advanced / marketplace /
my_tokens / store_upload / home_*), each with its own ad-hoc CSS
(.tokens-hero, .profile-hero, .install-hero, .upload-hero, …). The
admin section's index + detail pages had plain H1/H2 with their own
.users-title / .gp-title / .obs-title / .cfg-title / … inline styling.
Net effect: half the app felt like a product, half felt like a
spreadsheet.
Now:
- .page-header--hero CSS upgraded to match the look analysts already
liked from admin_tokens: 28px/32px/24px padding, 14px radius, soft
primary-tinted box-shadow (0 4px 16px rgba(0,115,209,0.2)), 28px
semibold title, optional uppercase eyebrow + 13.5px subtitle.
Narrow-viewport breakpoint included.
- New _page_hero.html partial wraps the boilerplate. Usage:
{% set page_hero_eyebrow = "Users & Access" %}
{% set page_hero_title = "Users" %}
{% set page_hero_subtitle = "…" %}
{% include "_page_hero.html" %}
- 15 admin templates migrated to it: admin_users / admin_groups /
admin_marketplaces / admin_access / admin_sessions /
admin_session_detail / admin_store_submissions /
admin_scheduler_runs / admin_usage / admin_user_detail /
admin_welcome / admin_workspace_prompt / admin_server_config /
activity_center / admin/news_editor. Each gets a grouped eyebrow
(Users & Access / Data / Agent Experience / Activity Center /
Server) matching the Admin dropdown sections so the page identity
is unambiguous at a glance.
Legacy *-title H2/H1 + adjacent subtitle paragraphs deleted; their
per-page CSS rules are dead now (harmless, retire in a follow-up
sweep alongside other inline-style cleanup the reviewers flagged).
admin_tables.html intentionally NOT migrated — it's a standalone
HTML page that doesn't extend base.html; a separate refactor.
Test: test_admin_users_page_renders_for_admin assertion updated
from .users-title to .page-header__title + .page-header--hero (the
canonical pair). All other web/template tests stay green.
* refactor(ui): dedup _humanbytes, drop 267 lines of dead inline CSS
(1) _humanbytes consolidation:
- Add TB branch + optional precision param (default 2 preserves existing
Store detail callers; dashboard uses precision=1 for headline tiles).
- Delete inline _fmt_bytes from dashboard handler — was a copy of
_humanbytes with different rounding. One canonical helper now.
(2) Dead inline-CSS sweep across 17 migrated templates:
- Conservative regex: a CSS rule is deleted only when its primary class
matches one of the known-dead names AND that name is NOT referenced
from any class= attribute in the same file's markup.
- Per-file 'in-use' guard saved several false positives that the deny
list would have nuked (e.g. .users-toolbar, .gp-search, .obs-subtitle,
.marketplaces-toolbar are still in use; only .users-table, .users-search,
.users-title, .modal-btn, etc. that have NO markup left went away).
- Removed: -267 lines across admin_users (-42), admin_marketplaces (-45),
admin_groups (-31), my_tokens (-38), admin_tokens (-29), admin_access
(-9), admin_user_detail (-6), admin_welcome (-8), admin_workspace_prompt
(-8), admin_server_config (-2), admin_sessions (-1), admin_session_detail
(-1), admin_usage (-1), admin_store_submissions (-3), admin_scheduler_runs
(-3), activity_center (-4), corporate_memory_admin (-36).
Contract test stays green (9/9); all web/template/render/user_management
tests pass.
* feat(ui): canonical hero on /catalog (Data Packages)
Same .page-header--hero treatment as the admin pages — Data eyebrow,
Data Packages title, Browse-the-data-sources subtitle. Removes the
ad-hoc .page-title block (h1 / p / wrapper-div) and its CSS rules
(now dead, 3 rule blocks deleted).
* fix(nav): load app.js from _app_header.html — works on standalone pages
The previous nav-fix commit moved the inline dropdown script from
_app_header.html into app/web/static/app.js + added <script src=…>
to base.html. That broke EVERY page that includes _app_header.html
WITHOUT extending base.html (catalog, corporate_memory*,
admin_tables, install). They got the nav markup but no JS → both
Admin and AD dropdowns dead on those pages.
Fix: emit the <script src=app.js defer> directly inside the
_app_header.html partial. Any page that includes the header now
gets the script automatically — base.html-extenders AND standalone
HTML pages alike. base.html's duplicate <script> line removed.
Also fixes the wide-hero on /catalog: .page-header--hero now sets
its own max-width: var(--width-app) (1280px) so standalone pages
without a .container parent don't render the gradient edge-to-edge.
catalog's .source-cards bumped from 900px → 1280px to match the
hero, otherwise the page reads two-tier (wide blue band, narrow
content) which the user flagged.
Verified locally via agent-browser: Admin + AD dropdowns now click
through on /catalog, /admin/tables, /corporate-memory.
* docs(plan): standalone pages → base.html framework migration plan
Plan + Plan-agent review (8 must-fix items applied) for converting
the 5 templates that ship their own <html><head><body> scaffold
(catalog, install, corporate_memory, corporate_memory_admin,
admin_tables) to extend base.html. Root cause of yesterday's
'dropdown dead on /catalog' regression: shared infrastructure in
base.html doesn't propagate to standalones.
* feat(base): body_attrs block + migrate install.html to extend base
base.html: new {% block body_attrs %}{% endblock %} slot so pages
that need <body> attributes (admin_tables has data-source-type)
can carry them through extends.
install.html: convert from standalone <html><head><body> scaffold
to {% extends "base.html" %} with title / body_attrs / head_extra
/ layout / scripts blocks. Drops:
- <!DOCTYPE>, <html>, </html>, <head>, </head>
- <meta charset>, <meta viewport>
- Duplicate <link rel="stylesheet" href="...style-custom.css">
(base.html already provides one)
- <body> opening + closing tags
- Leading _app_header.html include + _version_badge.html include
(base.html handles both)
Preserves per-page CSS (in head_extra), per-page JS (in scripts),
the Inter font preconnect (kept inline; not hoisted to base in
this PR — separate decision).
Pilots the migration recipe before the 4 larger pages.
* refactor(memory): extend base.html
Same recipe as install.html. corporate_memory.html now inherits
<html>/<head>/<body> + nav + app.js script tag from base.html.
Page-specific CSS and JS preserved in head_extra + scripts blocks.
* refactor(memory-admin): extend base.html
Same recipe as install/corporate_memory. Curation page now in the
shared rendering pipeline.
* refactor(catalog): extend base.html
catalog.html had the most complexity: 7 head-level assets (chart.js,
Prism, prism-sql, metric_modal.css link + 2 preconnects + Inter
stylesheet), 5 body-level <script> blocks including a <script type=
"module"> for the metric modal, 2 duplicate style-custom.css links
in <head>. The migration script preserved all of them — head-level
externals hoisted to {% block head_extra %} in source order, body
scripts relocated to {% block scripts %} in source order (so chart.js
loads before the IIFE that builds Chart instances), duplicate
style-custom.css links dropped (base.html provides one).
* refactor(admin-tables): extend base.html + carry data-source-type
The biggest of the 5 standalones at 3563 lines. <body data-source-
type="{{ data_source_type }}"> attribute carried through via the
new {% block body_attrs %} slot (admin_tables JS reads
document.body.dataset.sourceType to switch between keboola and
bigquery rendering paths).
* release: 0.54.10 — UI design system unification + homepage status frame + initial workspace override + store guardrails
Co-Authored-By: zdenek.srotyr <zdenek.srotyr@keboola.com>
* refactor(web): migrate remaining templates to canonical design primitives
- admin_group_detail: .data-table, .btn family, appToast(), remove duplicate table/button/toast CSS
- admin_store_submission_detail: .data-table, .btn family, appToast(), remove duplicate btn/toast CSS
- profile_sessions: .data-table, _page_hero.html, remove duplicate table/title CSS
- me_debug: .data-table, .btn family, remove duplicate table/button CSS
- marketplace: .btn-primary/.btn-secondary, remove duplicate button CSS
- store_edit: remove duplicate .btn-primary/.btn-link CSS, canonical button classes
- store_upload: remove duplicate .btn-primary/.btn-secondary/.btn-link CSS
Co-Authored-By: zdenek.srotyr <zdenek.srotyr@keboola.com>
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
4367 lines
97 KiB
CSS
4367 lines
97 KiB
CSS
/* Design System - Data Analyst Portal v2 */
|
||
|
||
:root {
|
||
/* Colors */
|
||
--primary: #0073D1;
|
||
--primary-light: rgba(0, 115, 209, 0.1);
|
||
--primary-dark: #005BA3;
|
||
--text-primary: #1A253C;
|
||
--text-secondary: #6B7280;
|
||
--background: #F5F7FA;
|
||
--surface: #FFFFFF;
|
||
--border: #E5E7EB;
|
||
--border-light: #F3F4F6;
|
||
--success: #10B77F;
|
||
--warning: #F59F0A;
|
||
--error: #EA580C;
|
||
|
||
/* Typography */
|
||
--font-primary: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 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 (home install-hero used a
|
||
custom blue-tinted shadow before this token landed). */
|
||
--shadow-sm: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||
--shadow-md: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.1) 0px 1px 2px -1px;
|
||
--shadow-card: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px -1px rgba(0, 0, 0, 0.05);
|
||
--shadow-elevated: 0 8px 24px rgba(0, 86, 163, 0.18);
|
||
|
||
/* Focus + interaction */
|
||
--focus-ring: 0 0 0 3px rgba(0, 115, 209, 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. Bumped from the legacy 800px (which
|
||
was a relic of single-form login pages) to --width-app (1280px)
|
||
— generous enough for admin dashboards, dense tables, and
|
||
catalog grids without feeling wasteful at 1440-1600px screens.
|
||
Pages that genuinely need narrower line-length (setup wizards,
|
||
single-form flows) opt in via .container--narrow. */
|
||
max-width: var(--width-app);
|
||
margin: 0 auto;
|
||
padding: var(--space-6) var(--space-5);
|
||
}
|
||
|
||
/* 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 {
|
||
background-color: var(--primary);
|
||
color: white;
|
||
font-weight: 500;
|
||
transition: all 0.15s ease;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: var(--primary-dark);
|
||
box-shadow: 0 2px 8px rgba(26, 115, 232, 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: #c4c4c4;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.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: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace;
|
||
font-size: 0.875rem;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.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;
|
||
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(0, 115, 209, 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(0, 115, 209, 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(0, 115, 209, 0.1); color: #0073D1; }
|
||
.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(0, 115, 209, 0.1); color: #0073D1; }
|
||
.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: #0073D1;
|
||
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: #c4c4c4;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.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: 13px;
|
||
font-weight: 500;
|
||
font-family: inherit;
|
||
color: var(--text-secondary, #6b7280);
|
||
text-decoration: none;
|
||
padding: 6px 12px;
|
||
border-radius: 8px;
|
||
border: 0;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
line-height: 1.2;
|
||
transition: all 0.15s ease;
|
||
}
|
||
.app-nav-link:hover {
|
||
color: var(--text-primary, #111827);
|
||
background: var(--border-light, #f3f4f6);
|
||
}
|
||
.app-nav-link.is-active,
|
||
.app-nav-link[aria-expanded="true"] {
|
||
color: var(--primary, #0073D1);
|
||
background: var(--primary-light, rgba(0, 115, 209, 0.1));
|
||
}
|
||
.app-nav-link:focus { outline: none; }
|
||
.app-nav-link:focus-visible {
|
||
outline: none;
|
||
box-shadow: 0 0 0 3px rgba(0, 115, 209, 0.25);
|
||
}
|
||
|
||
.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(0, 115, 209, 0.08); color: var(--primary, #0073D1); 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: rgba(0, 115, 209, 0.08); color: var(--primary, #0073D1); 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; }
|
||
|
||
@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. */
|
||
.container:has(.page-shell) {
|
||
max-width: 1280px;
|
||
margin: 0 auto;
|
||
padding: 28px 32px 48px;
|
||
}
|
||
.container:has(.page-shell) > main { margin: 0; padding: 0; }
|
||
|
||
|
||
/* ==============================================================
|
||
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 #0073D1;
|
||
background: #E6F3FC;
|
||
color: #111827;
|
||
padding: 12px 16px;
|
||
border-radius: 6px;
|
||
margin: 12px 0;
|
||
}
|
||
.callout-info { border-left-color: #0073D1; background: #E6F3FC; }
|
||
.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: #0073D1;
|
||
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, #0073D1 0%, #0056A3 100%);
|
||
color: white;
|
||
border-radius: 16px;
|
||
padding: 28px 32px;
|
||
margin: 18px 0;
|
||
box-shadow: 0 8px 24px rgba(0, 86, 163, 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 8–14 strip those.
|
||
===================================================== */
|
||
|
||
.btn-primary {
|
||
background-color: var(--primary);
|
||
color: #fff;
|
||
border: 1px solid var(--primary);
|
||
transition: all 0.15s ease;
|
||
}
|
||
.btn-primary:hover,
|
||
.btn-primary-v2:hover,
|
||
.modal-btn.primary:hover {
|
||
background-color: var(--primary-dark);
|
||
border-color: var(--primary-dark);
|
||
box-shadow: 0 2px 8px rgba(0, 115, 209, 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;
|
||
}
|
||
|
||
.form-input:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
box-shadow: var(--focus-ring);
|
||
}
|
||
|
||
.form-input::placeholder { color: var(--text-muted); }
|
||
|
||
/* =====================================================
|
||
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;
|
||
margin: var(--space-5) auto;
|
||
/* Self-constrain to the canonical app width so the gradient stays
|
||
visually balanced even on standalone pages (catalog, corporate-memory,
|
||
install, dashboard) that don't wrap content in `.container`. On
|
||
templates that DO use `.container` (admin pages), the parent's
|
||
max-width is the same or tighter, so this is a no-op there. */
|
||
max-width: var(--width-app);
|
||
box-sizing: border-box;
|
||
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
||
color: #fff;
|
||
border-radius: 14px;
|
||
box-shadow: 0 4px 16px rgba(0, 115, 209, 0.2);
|
||
position: relative;
|
||
}
|
||
.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 {
|
||
color: rgba(255, 255, 255, 0.75);
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.8px;
|
||
margin: 0 0 6px;
|
||
}
|
||
.page-header--hero .page-header__actions a,
|
||
.page-header--hero .page-header__actions button {
|
||
color: #fff;
|
||
}
|
||
@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);
|
||
}
|