* feat(unified-stack): Browse + My Stack + Recipes + RBAC matrix (v49–v55)
Squash of 94 commits spanning the v49 → v55 unified-stack rewrite.
Full per-feature breakdown lives in CHANGELOG.md under [Unreleased].
Major buckets:
* v49 schema — first-class user_groups + user_group_members +
resource_grants; admin can CRUD groups and grants; Google
Workspace nightly sync writes into the new tables.
* v49 data_packages — admin-curated bundles of tables, RBAC-gated,
first-class section on /catalog Browse + My Stack.
* v49 memory_domains — row-backed (replaces hardcoded VALID_DOMAINS
enum); admin can CRUD; grants follow the same shape as tables and
packages.
* v50 cover_image_url + admin sidebar collapsibles + per-row Mode
tooltip + admin queue domain badges + admin "+ New Item" seed flow.
* v51 lifecycle status (prod/poc/coming-soon/draft) + category +
palette swatches on admin modals.
* v52 per-table detail page /catalog/t/<id>.
* v53 Recipes — admin-curated SQL templates as a second tab on
/catalog with full Edit/Delete admin affordances.
* v54 soft-delete (deleted_at) + Undo toast for packages, memory
domains, and recipes; hard_delete() retained as escape hatch.
* v55 Recipes RBAC — ResourceType.RECIPE registered, inline Group
Access matrix on Create + Edit Recipe modals (mirrors the Memory
Domain pattern).
* Activity Center per-resource filter (resource_prefix LIKE-anchored
on audit_log.resource); admin nav g+letter keyboard shortcuts;
loadAdminTablesLayout N+1 → single endpoint; /api/memory 30s
page-level cache.
* CI hardening — Keboola legacy tests pytest.importorskip; perf-
smoke threshold widened to stop cold-cache flake.
5002 tests passing, 35 skipped.
* feat(p2 backlog): Cmd-K palette + suggest-a-domain + nightly E2E + v55 schema
10-item P2 sweep on top of the unified-stack squash. New behaviour:
* Cmd-K admin command palette (base.html) — fuzzy-search overlay over
admin + user-facing routes. Arrows/Enter to navigate, Esc to close.
* Stack-tabs digit shortcuts — 1/2/3 switch Browse / My Stack /
Recipes on /catalog + /corporate-memory.
* Friendlier non-admin empty state on /corporate-memory, plus a
"Suggest a domain" CTA → POST /api/memory-domain-suggestions, admin
queue with approve/reject. Backed by a new memory_domain_suggestions
table (schema v55).
* /admin/corporate-memory 7-tab strip grouped under Moderation /
Catalog parent labels.
* Bulk-assign table → package dropdown annotates each option with
"(N of M tables already in)" so the existing distribution is visible
before picking a target.
* GET /api/memory + /tree accept is_required filter; admin status
dropdowns route the "Required" sentinel onto it (status no longer
holds 'mandatory' post-v49, so the old dropdown returned nothing).
* chip-input.js is now opt-in per template via {% block extra_scripts %}
instead of loaded globally on every page from base.html.
* Edit-modal close helpers consolidated onto _closeEditModalById();
docs the per-source-type modal architecture decision.
* New .github/workflows/e2e-nightly.yml runs agent-browser smoke
scripts (scripts/e2e/smoke_*.sh) against a docker-compose stack
nightly at 04:30 UTC; failures open an agent-browser-nightly issue.
5012 tests passing, 35 skipped.
* fix(visual audit): 6 page regressions on memory + data-package surfaces
agent-browser walkthrough of every memory + data-package page in the PR
turned up 6 real bugs. Fixes:
1. Admin memory modals were dead. Duplicate `let _cmdNewDomainId`
declarations from the deprecated step-2 RBAC stubs in
admin_corporate_memory.html collided with the live state vars
declared earlier in the same <script> → SyntaxError on parse →
the entire second script block silently failed → every inline
onclick= handler defined there (`+ New Memory Domain`, Edit, etc.)
was a no-op. Removed the duplicate stubs.
2. /catalog/t/<table_id> + /catalog/r/<slug> rendered unstyled.
Both templates injected their CSS via {% block head %} but
base.html exposes {% block head_extra %} — wrong block name
meant <style> rules never reached the rendered HTML. Renamed
to head_extra. Hero card, section cards, dark SQL block, proper
full-width inputs all now render as designed.
3. L49 leak — "MANDATORY" KPI label + "Make Mandatory" row buttons
on /admin/corporate-memory still used the old word. Renamed to
"Required" / "Mark as Required" so UI matches the data model
(v49 split moved the Required tier onto the orthogonal
is_required boolean; status no longer holds 'mandatory').
4. Activity Center Resource dropdown didn't know the v55
`memory_domain_suggestion:` namespace — added it.
5. Tab strip on /admin/corporate-memory wrapped text 2× per button
on narrow viewports after the L50 MODERATION/CATALOG group
labels pushed total width past most viewports. Switched the
strip to flex-wrap:nowrap + overflow-x:auto with
white-space:nowrap + flex-shrink:0 on every direct child so the
tabs stay one row and slide horizontally when they overflow.
5012 tests passing, 35 skipped.
* rebase-cleanup: align with main's 0.54.25-27 API design + comment fix
Three follow-on fixes after rebasing onto origin/main (0.54.27):
* admin_tables.html: dropped a stray nested ``{% if data_source_type
== 'keboola' %}`` around ``prefillFromKeboolaTable`` (main never had
it; the outer Phase F2 guard already covers it) and reworded a JS
comment that contained literal ``{% %}`` tokens which Jinja was
parsing as a real tag → unbalanced if/endif → 30 template render
failures across the suite.
* /api/stack/subscription/{type}/{id}: DELETE now returns 204 instead
of 200 per the 0.54.26 design rules. CLI client + parity tests
updated to accept 2xx / assert 204.
* Memory-domain suggestion approve/reject paths added to
``_VERB_PATH_ALLOWLIST`` — they are pending → approved/rejected
state-machine transitions (approve also creates the real
memory_domains row as a side effect), so the RPC shape is
intentional rather than a missed PATCH refactor.
5035 tests passing, 35 skipped.
* fix(catalog_table_detail): real polish pass — hero glyph, dedup pills, rows/size meta, scoped sync CTA
The previous fix only got the block-name typo so the existing CSS rendered.
The actual layout was still wireframe-tier on close inspection:
* No cover glyph in the hero (a flat white card with title + meta line);
data-package + memory-domain detail pages both have a colored icon
square. Restored parity — table.icon emoji if set, otherwise initials
on a colored square using table.color.
* "INTERNAL" pill rendered twice for agnes_audit etc. — the mode pill
and the source-type pill happened to be identical strings. Now skip
the source pill when it matches the mode (`internal == internal`).
* Bucket / source_table code chip showed `Agnes Internal.audit_log` for
internal rows — meaningless to a user. Hidden when source_type is
internal.
* `pairs_well_with` admin input was a comma-separated `<input>` always
visible. Wrapped all 4 sections in an Edit-on-demand toggle: read-
only display by default, "+ Add" / "Edit" button on the right edge
of each section header reveals the inline form, Cancel hides it.
* "Trigger sync now" was a cramped link squashed into the empty-state
flex row (visible as `Tr…` overflow before). Promoted to a proper
btn-primary button under the empty-state copy. Hidden entirely for
internal tables (which are server-managed — no upstream to pull).
* Hero meta now surfaces row count + payload size (when sync_state has
them) + last sync timestamp on a single line — was missing from the
original.
* Mode pills colored by tier (local=green, remote=amber, materialized=
blue, internal=gray) so the basic fact about a table reads at a
glance, not from upper-cased ALL-CAPS text alone.
* tests(v56): TDD baseline for extended data-packages content + per-table docs
68 failing tests across 8 files spec the v56 surface before any
implementation lands:
* test_schema_v55_to_v56_migration.py — schema bump, additive ALTERs
on data_packages + table_registry, idempotency, sequential-upgrade
preservation
* test_data_packages_repo_v56.py — repo create/update/get/list for
owner_name, owner_team, tags, long_description, when_to_use,
when_not_to_use, example_questions (JSON list round-trip, empty
defaults, partial-update preservation)
* test_table_registry_v56_docs.py — update_docs for grain, platforms,
partition_col, history, gotchas; preserves v52 docs columns
* test_api_data_packages_v56.py — PUT/POST/GET for all new fields,
field-level validation (tag count, bullet length, description size),
virtual badge derivation (curated/new)
* test_api_registry_docs_v56.py — PATCH /api/admin/registry/{id}/docs
for v56 fields, validation, RBAC unchanged
* test_web_catalog_package_detail_v56.py — /catalog/p/<slug> rewrite
asserts on rendered owner line, tag pills, badges, What it is,
Use it when, Skip it when, Example questions, per-table extended
detail in collapsible row, key-gotcha distinctness, admin-only Edit
* test_web_stack_card_v56_metadata.py — Browse-grid card additions
(owner chip, tag chips, badges) without breaking back-compat for
rows missing the new fields
* test_data_packages_no_vendor_content.py — CI guard: scans app/ +
src/ + cli/ + config/ + scripts/ for Groupon-specific tokens from
the colleague's spec MD; fails if any leak into OSS surfaces
* test_db_schema_version.py — bumped 55 → 56 with rationale
Plus updates schema-version assertion to 56. Implementation lands in
subsequent commits (schema migration → repo → API → templates).
* feat(v56): schema + repo for extended data-packages content
Schema additions (ALTER ADD COLUMN IF NOT EXISTS — additive + idempotent):
* data_packages: owner_name, owner_team, tags, long_description,
when_to_use, when_not_to_use, example_questions (JSON-as-VARCHAR for
the lists)
* table_registry: grain, platforms, partition_col, history, gotchas
(extends the v52 sample_questions / things_to_know / pairs_well_with
docs surface with structured per-table content)
Repo extensions:
* DataPackagesRepository.create + update accept the new fields with
the same Optional-is-no-op contract as v51 (pass an empty list to
clear a JSON column)
* _decode_row decodes the new JSON-list columns to Python lists; NULL
rounds back to [] so callers don't branch
* TableRegistryRepository.update_docs grew the v56 fields alongside
the existing v52 ones — single PATCH can write either tier
atomically
* TableRegistryRepository._decode_row picks up platforms + gotchas in
the same NULL-tolerant decoder
22 repo + migration tests passing. API + UI land in subsequent commits.
* feat(v56): API surface for extended data-packages + per-table docs
CreateDataPackageRequest + UpdateDataPackageRequest grew the v56 fields
(owner_name, owner_team, tags, long_description, when_to_use,
when_not_to_use, example_questions) with per-field validators that
match the Foundry spec checklist:
* tags: ≤8 entries × ≤30 chars
* long_description: ≤4000 chars
* use/skip: ≤8 bullets × ≤200 chars
* example_questions: ≤12 × ≤200 chars
_serialize emits all v56 fields plus a virtual ``badges`` list derived
server-side at render time (no DB column needed): "curated" when the
creator is in the Admin group, "new" within 30 days of created_at.
Backdating created_at or admin-status changes pick up automatically.
PATCH /api/admin/registry/{id}/docs extended with v56 structured
per-table fields (grain, platforms, partition_col, history, gotchas).
gotchas: list of {key: bool, body: str} Pydantic models with the same
≤8 cap; first key=true entry becomes the Key gotcha on the rendered
package detail page. PATCH echoes the fresh state so callers can
re-render without a second GET.
26 API tests passing (16 data-packages + 10 registry-docs).
* feat(v56): /catalog/p/<slug> rewrite + Browse-grid card augmentation
The third (and final) v56 commit lights up the UI surfaces backed by
the schema + API commits earlier in this PR:
* /catalog/p/<slug> template rebuilt around the Foundry spec's
section ladder — hero (icon + name + badges + owner + tags +
description + meta + Add-to-stack), "What it is" markdown body,
paired "Use it when / Skip it when" panels, "Tables in this
package" with collapsible per-table extended detail (grain /
platforms / partition_col / history / gotchas + sample questions),
and an "Example questions you can ask Claude" prompt panel. Each
section guarded by ``{% if pkg.<field> %}`` — empty content fields
hide the section entirely (no "No X yet" placeholder noise on the
public-facing drilldown).
* router catalog_package_detail hydrates per-table v56 fields onto
the tables list + derives the virtual badges (curated / new)
server-side from creator-in-Admin + 30-day created_at.
* StackResolver.ResourceEntry grew owner_name / owner_team / tags /
badges; _fetch_entries pulls the v56 columns + computes badges
once per fetch using a single Admin-group SELECT.
* _data_package_entry_dict adapter passes the new fields through to
the macro; tags are merged source-type pills + admin-authored
category tags per the spec convention.
* _stack_card.html renders the v56 badges (top-left, data-badge=
hooks) + the owner chip (data-card-owner hook) without breaking
back-compat — pre-v56 rows render unchanged.
* Admin PUT handler strips the v56 docs fields from the
read-modify-write merged dict so register() doesn't blow up
with the now-larger row shape (same pattern as the v52 docs
fields stripping).
5115 tests passing (+98 v56 + 18 fixed regressions from the merged-
register PUT path), 35 skipped.
* fix(rbac): Edit-on-package + Group-access 'required' persistence + CI vendor guard
Three related bugs reported on the merged-with-main branch:
1. Clicking Edit on a Data Package card landed on /admin/tables with
a `#<pkg.id>` hash that nothing listened to — admin saw the global
table listing, not the editor for that specific package. Added a
`?edit_package=<pkg_id>` query-param handler in admin_tables.html
(analog to the existing `?edit=<table_id>` and `?assign_to=<pkg_id>`
patterns) that calls openEditDataPackageModal on DOMContentLoaded
after a 250ms layout settle. Updated the package-detail Edit link
to use the new query param.
2. Setting Group Access to 'required' didn't persist — re-opening
the modal showed 'available'. Root cause was the v49
``resource_grants.requirement`` enum existing in the DB but the
POST /api/admin/grants endpoint not surfacing it: ``CreateGrantRequest``
declared only group_id + resource_type + resource_id, so Pydantic
silently dropped the matrix's ``requirement: 'required'`` payload
and the new row landed at the DB column default ('available').
Plumbed ``requirement`` through ``CreateGrantRequest`` →
``ResourceGrantsRepository.create`` so the value persists in one
round-trip. Plus a UNIQUE-constraint race in the matrix
diff-apply: DELETE-old + POST-new ran in parallel via
``Promise.allSettled``, so POST could fire first and trip the
unique check before DELETE freed the slot. Switched to sequential
(await all deletes; then await all writes) across all three
matrices (Edit Data Package, Edit Memory Domain, Edit Recipe).
3. CI vendor-content guard ``test_no_groupon_specific_strings_in_oss``
tripped on two of my own docstrings: a "Foundry Data team" mention
in two src/db.py comments + an ``s1_session_landings`` example in
cli/skills/agnes-table-registration.md. Rephrased the comments to
"extended-descriptions admin spec" and replaced the example with
a generic ``events_daily`` table name.
5164 tests passing, 35 skipped (+4 regression tests pinning the POST
/api/admin/grants requirement contract). Vendor guard back to green.
* fix(catalog): admin Browse path drops v58 card fields
The /catalog and /memory admin god-mode branch built ResourceEntry
instances inline from pkg_repo.list() / domains_repo.list() and skipped
owner_name, owner_team, tags, and derived badges (curated/new). Visible
symptom: a package with an owner + tags rendered with the v56 chrome
for non-admin viewers but as a bare card for admins.
Adds StackResolver.browse_admin(user_id, resource_type) — admin god-mode
Browse that walks the full table but routes through the same
_fetch_entries enrichment pass as browse(), so admin + non-admin Browse
stay visually consistent. Both /catalog and /corporate-memory routes
switch to it.
Regression test in tests/test_stack_resolver_browse_admin.py covers:
owner/tags propagation, new/curated badge derivation, in_stack from
admin subscriptions, all-packages-regardless-of-grants, and the
ValueError for unsupported resource types.
* fix(catalog): three /catalog tab-strip UX bugs
1. Required Remove → red toast
browse_admin passed empty required_ids to _fetch_entries, so the
admin's own required grants surfaced as 'available' and the macro
rendered an actionable Remove button that POST /unsubscribe 400'd
on. Now derives required_ids from the admin's own groups so
Required packages render with the disabled "In stack (required)"
button. Regression test in test_stack_resolver_browse_admin.py.
2. Remove green-toasts but card stays until refresh
The My-Stack empty-state placeholder was only emitted server-side
when stack_entries was empty at render time. Removing the last
card left the tab completely blank — users read that as "Remove
didn't work, let me refresh". Both grid + empty-state are now
always rendered with one of them initially hidden; the JS swaps
visibility on add/remove instead of injecting DOM. Same fix in
/corporate-memory.
3. "What are Recipes?" + ambiguous (admin) suffix
Recipes tab now carries its own curator-block explainer (the
shared one was moved inside Browse view so it doesn't bleed
across tabs). The grey "(admin)" suffix becomes a yellow
.admin-only-hint chip with a title tooltip — visibility hint is
now unambiguous: yellow chip = "only you see this", non-admins
don't see the affordance at all.
* schema: renumber v51..v58 → v52..v59 to make room for main's v51
Main 0.54.29 introduced a NEW v51 (table_registry.bq_fqn — issue #343)
that releases ahead of this branch. The unified-stack chain v51..v58
shifts up by one so main's v51 stays as the released schema and ours
become v52..v59. Function names, internal version bumps, dispatch
ladder thresholds, and the migration-test references all move
together. Subsequent merge with main lands the bq_fqn column at the
freed v51 slot.
* fix(seed): seed admin lands in BOTH Admin AND Everyone groups
The LOCAL_DEV_MODE / SEED_ADMIN_EMAIL bootstrap only added the seed
user to Admin. Everyone-scoped grants — the canonical "every-user-
sees-this" pattern for Required onboarding — didn't surface for the
seed admin's own /catalog because they weren't in Everyone. Symptom:
admin grants a Required-tier package to Everyone, then sees it on
/catalog still rendered with an "Add to stack" button (because the
admin's resolved required_ids was empty for that package).
The dual-membership keeps Admin (authorization) and Everyone
(default-grant target) intentionally separate per the design comment
on UserRepository.create — every membership remains traceable to a
concrete row, just now with a system_seed row in Everyone too. Both
INSERTs go through UserGroupMembersRepository.add_member which is
idempotent on (user_id, group_id), so re-fires on every lifespan
startup don't duplicate rows.
Regression test in test_main_seed_admin_everyone.py.
* style: unify admin-only hints across marketplace + memory detail pages
Replaces three stale ``(admin)`` parentheticals with the same yellow
``admin-only`` chip introduced for /catalog tab actions. Same tooltip
copy ("Visible only to admins — analysts won't see this …") so the
visibility hint is unmistakable wherever it appears:
- Hard delete on marketplace_plugin_detail (admin-only destructive
action — same gating as the original suffix conveyed).
- Hard delete on marketplace_item_detail (same).
- Edit link on memory_domain_detail (title-attr only before; now a
visible chip too).
Non-admin viewers never saw these affordances — the gates are
unchanged. Pure styling pass for consistency.
* fix(catalog): exclude soft-deleted data packages + memory domains from Browse
``StackResolver._fetch_entries`` and ``browse_admin`` were querying
data_packages / memory_domains without a ``deleted_at IS NULL`` guard.
A package soft-deleted via /admin/* (v54 soft-delete contract) stayed
visible on /catalog and /memory until either an Undo or a hard delete
— directly contradicting the soft-delete UX which is supposed to
remove the affordance immediately and only retain the row for the
Undo window.
The repository accessors (DataPackagesRepository.list,
MemoryDomainsRepository.list, list_packages_of_table, etc.) already
filter deleted rows; this commit brings the resolver's direct SQL in
line with that contract.
Regression test in test_stack_resolver_browse_admin.py.
* fix(catalog): Add/Remove updates full card chrome, not just button
The previous _applyStackChange flipped only the footer button label —
the card border (.is-in-stack class), top-right "In stack" badge, and
button color class (--add / --remove) stayed at their server-rendered
state. After Add the user saw the button checkmark but the rest of
the card still looked like "available, not in stack". They read this
as "the change didn't take — let me refresh".
This commit makes the optimistic update mirror what the server-side
macro renders for the new state:
* ``c.classList.toggle('is-in-stack', becameInStack)`` — flips the
border + visual state class.
* Top-right ``.stack-card__req-badge--instack`` badge is injected on
Add, removed on Remove (skipped when ``data-requirement='required'``
— that slot is owned by the Required badge).
* Button text is "Remove" / "+ Add to stack" matching the macro
(was "✓ In stack" which was visually nice but inconsistent).
* Button color class --add / --remove swaps so the destructive Remove
tint kicks in immediately.
The clone-into-My-Stack path applies the same updates so the new card
in My Stack reads identically to a server-rendered in_stack card.
Mirrored in /corporate-memory.
* fix(memory): four Devin-review bugs on /memory drill-down + manifest
PR #333 Devin review surfaced four real bugs that ship a broken
/memory experience even though the unit tests passed.
1. Manifest md5 omits is_required + content (app/api/sync.py:836-840)
_build_memory_domains_section hashed only (id|title|status) per
item. _build_per_domain_markdown routes items between "## Required"
and "## Approved" by is_required and embeds full content — so an
admin edit of either dimension left the manifest md5 unchanged,
`agnes pull` skipped the re-fetch, and the analyst kept a stale
bundle.md. Now both fields participate in the hash.
2. required_count always 0 (src/repositories/memory_domains.py)
list_items_of_domain only SELECTed (id, title, status) so the
`it.get("is_required")` in the manifest builder always evaluated
to None → required_count = 0 regardless of actual state. The
manifest builder advertised a count it could never compute. Now
projects is_required + content too (required by fix 1 anyway).
3. Vote URL 404 (memory_domain_detail.html:289-290)
Constructed `/api/memory/items/{id}/vote` but the route is
`/api/memory/{id}/vote`. Every upvote/downvote button was a
silent no-op.
4. Dismiss/undismiss URL + method both wrong (memory_domain_detail.html:296-305)
Constructed `/api/memory/items/{id}/dismiss` (extra /items/) and
/undismiss (no such route — undismiss is DELETE on /dismiss).
Both buttons silently 404'd. Now POST + DELETE on
`/api/memory/{id}/dismiss` per app/api/memory.py:635/675.
* fix: multi-agent reviewer findings — vendor-token scrubs + manifest md5 predicate + soft-delete filter
Three reviewer findings from the multi-agent review on PR #333,
fixed in-place per CLAUDE.md issue-economy rule.
Reviewer-rules (Important — vendor-agnostic OSS):
- app/main.py:218 comment: replaced 'foundryai-prod' with generic
'a customer prod instance' phrasing. Public OSS repo must not
carry customer-specific tokens (CLAUDE.md § Project conventions).
- tests/test_table_registry_v56_docs.py:70 fixture string:
replaced "user_brand_affiliation = 'groupon'" with 'acme' on
the same rule.
Reviewer-architecture (closes still-unresolved Devin 🚩 ANALYSIS):
- app/api/sync.py _build_memory_domains_section: md5 hash loop now
filters items to the SAME predicate the bundle renderer uses
(is_required OR status='approved'). Pre-fix the hash iterated ALL
items but _build_per_domain_markdown only rendered the union of
required items + approved-non-required items — so an admin edit
to a pending/rejected non-required item flipped the md5 against
an identical-bytes bundle, triggering a wasteful re-fetch on
every analyst's next 'agnes pull'. The earlier commit fixed the
hash-input fields (is_required + content); this closes the
set-of-items asymmetry Devin separately flagged.
Reviewer-RBAC (minor cleanup):
- app/resource_types.py _data_package_blocks and _memory_domain_blocks
now filter 'WHERE deleted_at IS NULL' (v54 soft-delete column) so
the /admin/access UI doesn't surface soft-deleted entities as
grantable. Mirrors the existing filter on _recipe_blocks. No
security leak pre-fix (resolver double-filters and re-checks at
serve time), just UI cleanliness.
- app/services/stack_resolver.py add_to_stack: docstring note
added explaining that authorization is enforced at the API layer
(app/api/stack.py can_access gate), not at the resolver. The
initial review suggested adding a defensive 403 here, but that
broke 5 existing tests that legitimately call add_to_stack
directly without setting up grants first; the docstring captures
the contract instead. stack() already intersects subscriptions
with current available_ids on every read, so a 'zombie' row from
a misuse never leaks into the user-facing manifest.
* release: 0.55.0 — unified Browse + My Stack (Data Packages + Memory), schema v48→v59, 3 BREAKING
4401 lines
98 KiB
CSS
4401 lines
98 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; }
|
||
|
||
/* <details data-section=...> group wrapper — visually identical to the
|
||
pre-collapse render, but each section can fold up. <summary> reuses
|
||
the .app-nav-menu-section style and gets a chevron via :before that
|
||
rotates 90deg when [open] (no JS for the visual). */
|
||
.app-nav-menu-group { margin: 0; }
|
||
.app-nav-menu-group > summary.app-nav-menu-section {
|
||
cursor: pointer;
|
||
user-select: none;
|
||
list-style: none; /* hide native disclosure triangle (most browsers) */
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.app-nav-menu-group > summary.app-nav-menu-section::-webkit-details-marker {
|
||
display: none; /* WebKit fallback */
|
||
}
|
||
.app-nav-menu-group > summary.app-nav-menu-section::before {
|
||
content: "›";
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
color: var(--text-secondary, #6b7280);
|
||
transition: transform 0.12s ease;
|
||
}
|
||
.app-nav-menu-group[open] > summary.app-nav-menu-section::before {
|
||
transform: rotate(90deg);
|
||
}
|
||
/* First-section rounded corners stayed on the original first-child rule,
|
||
but the wrapper <details> is now the first child — re-apply on the
|
||
nested <summary>. */
|
||
.app-nav-menu-group:first-of-type > summary.app-nav-menu-section {
|
||
margin-top: -6px;
|
||
border-radius: 10px 10px 0 0;
|
||
}
|
||
|
||
@media (max-width: 720px) {
|
||
.app-header { padding: 0 16px; gap: 8px; }
|
||
.app-header-email { display: none; }
|
||
.app-nav-link { padding: 6px 8px; }
|
||
}
|
||
|
||
/* ── Standard page shell ─────────────────────────────────────────────
|
||
Shared layout container for pages that extend base.html. Matches the
|
||
dashboard's `.main` dimensions so /dashboard, /marketplace, and the
|
||
marketplace detail pages all align. Add `page-shell` to a page's root
|
||
wrapper to opt in. */
|
||
.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);
|
||
}
|