UI Phenikaa
This commit is contained in:
@@ -1,30 +1,31 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import {
|
||||
Activity,
|
||||
AlertCircle,
|
||||
Box,
|
||||
CheckCircle2,
|
||||
Clipboard,
|
||||
Cpu,
|
||||
Download,
|
||||
ExternalLink,
|
||||
HardDrive,
|
||||
Loader2,
|
||||
PackageCheck,
|
||||
Play,
|
||||
PlugZap,
|
||||
RefreshCcw,
|
||||
RotateCcw,
|
||||
Search,
|
||||
Server,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
WifiOff,
|
||||
X,
|
||||
XCircle
|
||||
} from 'lucide-react';
|
||||
ArrowCounterclockwise20Regular as RotateCcw,
|
||||
ArrowDownload20Regular as Download,
|
||||
ArrowSync20Regular as RefreshCcw,
|
||||
Board20Regular as Cpu,
|
||||
Bot24Filled as PackageCheck,
|
||||
Box20Regular as Box,
|
||||
CheckmarkCircle20Filled as CheckCircle2,
|
||||
Clipboard20Regular as Clipboard,
|
||||
Delete20Regular as Trash2,
|
||||
Dismiss20Regular as X,
|
||||
ErrorCircle20Filled as XCircle,
|
||||
HardDrive20Regular as HardDrive,
|
||||
Open20Regular as ExternalLink,
|
||||
Play20Filled as Play,
|
||||
PlugConnected20Regular as PlugZap,
|
||||
Pulse20Regular as Activity,
|
||||
Search20Regular as Search,
|
||||
Server20Regular as Server,
|
||||
Settings20Regular as Settings,
|
||||
ShieldCheckmark20Filled as ShieldCheck,
|
||||
SpinnerIos20Regular as Loader2,
|
||||
Warning20Filled as AlertCircle,
|
||||
WifiOff20Regular as WifiOff
|
||||
} from '@fluentui/react-icons';
|
||||
import '@fontsource-variable/montserrat';
|
||||
import {
|
||||
DEFAULT_AGENT_BASE_URL,
|
||||
DEFAULT_PACKAGE_BASE_URL,
|
||||
@@ -755,7 +756,7 @@ function App() {
|
||||
<aside className="sidebar">
|
||||
<div className="brand-block">
|
||||
<div className="brand-mark">
|
||||
<PackageCheck size={20} aria-hidden="true" />
|
||||
<PackageCheck fontSize={20} aria-hidden="true" />
|
||||
</div>
|
||||
<div className="brand-copy">
|
||||
<strong>Robot Installer</strong>
|
||||
@@ -790,7 +791,7 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-secondary full" type="button" onClick={openEndpointDialog}>
|
||||
<Settings size={15} aria-hidden="true" />
|
||||
<Settings fontSize={15} aria-hidden="true" />
|
||||
Endpoint settings
|
||||
</button>
|
||||
</div>
|
||||
@@ -810,10 +811,10 @@ function App() {
|
||||
tone={agentStatusTone}
|
||||
/>
|
||||
<a className="icon-button" href={joinUrl(packageBaseUrl, '/api/apps')} target="_blank" rel="noreferrer" title="Open package API">
|
||||
<ExternalLink size={17} aria-hidden="true" />
|
||||
<ExternalLink fontSize={17} aria-hidden="true" />
|
||||
</a>
|
||||
<button className="btn btn-secondary" type="button" onClick={refreshAll}>
|
||||
<RefreshCcw size={15} aria-hidden="true" />
|
||||
<RefreshCcw fontSize={15} aria-hidden="true" />
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
@@ -832,13 +833,13 @@ function App() {
|
||||
|
||||
{canUseAgentEndpoint && !agentHealth && (
|
||||
<div className="offline-banner">
|
||||
<AlertCircle size={19} aria-hidden="true" />
|
||||
<AlertCircle fontSize={19} aria-hidden="true" />
|
||||
<div>
|
||||
<strong>{agentTargetLabel} is offline</strong>
|
||||
<code>{agentCommand}</code>
|
||||
</div>
|
||||
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
||||
<Clipboard size={15} aria-hidden="true" />
|
||||
<Clipboard fontSize={15} aria-hidden="true" />
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
@@ -846,13 +847,13 @@ function App() {
|
||||
|
||||
{canUseAgentEndpoint && agentNeedsUpdate && (
|
||||
<div className="offline-banner agent-update-banner">
|
||||
<AlertCircle size={19} aria-hidden="true" />
|
||||
<AlertCircle fontSize={19} aria-hidden="true" />
|
||||
<div>
|
||||
<strong>Agent {latestAgentPackage.version} is ready</strong>
|
||||
<code>{agentCommand}</code>
|
||||
</div>
|
||||
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
||||
<Clipboard size={15} aria-hidden="true" />
|
||||
<Clipboard fontSize={15} aria-hidden="true" />
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
@@ -864,7 +865,7 @@ function App() {
|
||||
<label className="filter-field wide">
|
||||
<span>Search</span>
|
||||
<div className="input-with-icon">
|
||||
<Search size={15} aria-hidden="true" />
|
||||
<Search fontSize={15} aria-hidden="true" />
|
||||
<input
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
@@ -952,7 +953,7 @@ function App() {
|
||||
runAppAction('install', app);
|
||||
}}
|
||||
>
|
||||
{installBusy ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <Play size={14} aria-hidden="true" />}
|
||||
{installBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <Play fontSize={14} aria-hidden="true" />}
|
||||
Install
|
||||
</button>
|
||||
)}
|
||||
@@ -966,7 +967,7 @@ function App() {
|
||||
runAppAction('update', app);
|
||||
}}
|
||||
>
|
||||
{updateBusy ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <RotateCcw size={14} aria-hidden="true" />}
|
||||
{updateBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <RotateCcw fontSize={14} aria-hidden="true" />}
|
||||
Retry
|
||||
</button>
|
||||
)}
|
||||
@@ -980,7 +981,7 @@ function App() {
|
||||
runAppAction('update', app);
|
||||
}}
|
||||
>
|
||||
{updateBusy ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <RotateCcw size={14} aria-hidden="true" />}
|
||||
{updateBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <RotateCcw fontSize={14} aria-hidden="true" />}
|
||||
Update
|
||||
</button>
|
||||
)}
|
||||
@@ -993,7 +994,7 @@ function App() {
|
||||
title={`Open ${app.appName}`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<ExternalLink size={14} aria-hidden="true" />
|
||||
<ExternalLink fontSize={14} aria-hidden="true" />
|
||||
Open App
|
||||
</a>
|
||||
)}
|
||||
@@ -1008,7 +1009,7 @@ function App() {
|
||||
runAppAction('remove', app);
|
||||
}}
|
||||
>
|
||||
{removeBusy ? <Loader2 className="spin" size={16} aria-hidden="true" /> : <Trash2 size={16} aria-hidden="true" />}
|
||||
{removeBusy ? <Loader2 className="spin" fontSize={16} aria-hidden="true" /> : <Trash2 fontSize={16} aria-hidden="true" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -1084,7 +1085,7 @@ function AgentStatusButton({ title, detail, tone, onClick }) {
|
||||
return (
|
||||
<button className={`agent-status-button tone-${tone || 'info'}`} type="button" onClick={onClick}>
|
||||
<span className="agent-status-icon">
|
||||
<Icon className={tone === 'info' ? 'spin' : ''} size={16} aria-hidden="true" />
|
||||
<Icon className={tone === 'info' ? 'spin' : ''} fontSize={16} aria-hidden="true" />
|
||||
</span>
|
||||
<span className="agent-status-copy">
|
||||
<strong>{title}</strong>
|
||||
@@ -1130,7 +1131,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
||||
<p>Changes only apply after you press Apply.</p>
|
||||
</div>
|
||||
<button className="icon-button subtle" type="button" title="Close" onClick={onCancel}>
|
||||
<X size={16} aria-hidden="true" />
|
||||
<X fontSize={16} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1163,7 +1164,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-primary" type="submit">
|
||||
<Settings size={15} aria-hidden="true" />
|
||||
<Settings fontSize={15} aria-hidden="true" />
|
||||
Apply endpoints
|
||||
</button>
|
||||
</div>
|
||||
@@ -1176,7 +1177,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
||||
function StatusBox({ icon: Icon, title, detail, tone }) {
|
||||
return (
|
||||
<div className={`status-box tone-${tone || 'muted'}`}>
|
||||
<span className="status-icon"><Icon size={16} aria-hidden="true" /></span>
|
||||
<span className="status-icon"><Icon fontSize={16} aria-hidden="true" /></span>
|
||||
<div>
|
||||
<strong>{title}</strong>
|
||||
<span>{detail}</span>
|
||||
@@ -1188,7 +1189,7 @@ function StatusBox({ icon: Icon, title, detail, tone }) {
|
||||
function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand, osLabel }) {
|
||||
return (
|
||||
<div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}>
|
||||
<AlertCircle size={19} aria-hidden="true" />
|
||||
<AlertCircle fontSize={19} aria-hidden="true" />
|
||||
<div>
|
||||
<strong>{isWindows ? 'Remote Ubuntu endpoint needed' : 'Local Agent requires Linux'}</strong>
|
||||
<p>
|
||||
@@ -1200,7 +1201,7 @@ function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand
|
||||
</div>
|
||||
{canShowCommand && (
|
||||
<button className="btn btn-secondary" type="button" onClick={onCopyCommand}>
|
||||
<Clipboard size={15} aria-hidden="true" />
|
||||
<Clipboard fontSize={15} aria-hidden="true" />
|
||||
Copy
|
||||
</button>
|
||||
)}
|
||||
@@ -1268,11 +1269,11 @@ function TaskPanel({ task, onClear, onRefresh }) {
|
||||
<div className="panel-actions">
|
||||
<span className={statusBadgeClass(task.status)}>{task.status || 'queued'}</span>
|
||||
<button className="icon-button subtle" type="button" title="Refresh task" onClick={onRefresh} disabled={!canRefresh}>
|
||||
<RefreshCcw size={16} aria-hidden="true" />
|
||||
<RefreshCcw fontSize={16} aria-hidden="true" />
|
||||
</button>
|
||||
{canClear && (
|
||||
<button className="icon-button subtle" type="button" title="Clear task" onClick={onClear}>
|
||||
<XCircle size={16} aria-hidden="true" />
|
||||
<XCircle fontSize={16} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -1305,7 +1306,7 @@ function TaskPanel({ task, onClear, onRefresh }) {
|
||||
|
||||
<div className="component-list task-components">
|
||||
<div className="component-list-title">
|
||||
<Activity size={15} aria-hidden="true" />
|
||||
<Activity fontSize={15} aria-hidden="true" />
|
||||
Components
|
||||
</div>
|
||||
{components.map((component) => (
|
||||
@@ -1351,7 +1352,7 @@ function ServiceChecks({ checks = [], status = 'not-checked', showSource = false
|
||||
return (
|
||||
<div className="service-check-list">
|
||||
<div className="service-check-empty">
|
||||
<Activity size={14} aria-hidden="true" />
|
||||
<Activity fontSize={14} aria-hidden="true" />
|
||||
<span>{message || 'Service status unavailable'}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1364,8 +1365,8 @@ function ServiceChecks({ checks = [], status = 'not-checked', showSource = false
|
||||
<div className="service-check-row" key={`${check.componentId || ''}:${check.serviceName}`}>
|
||||
<div className="service-check-name">
|
||||
{check.healthy
|
||||
? <CheckCircle2 className="service-check-icon healthy" size={14} aria-hidden="true" />
|
||||
: <AlertCircle className="service-check-icon unhealthy" size={14} aria-hidden="true" />}
|
||||
? <CheckCircle2 className="service-check-icon healthy" fontSize={14} aria-hidden="true" />
|
||||
: <AlertCircle className="service-check-icon unhealthy" fontSize={14} aria-hidden="true" />}
|
||||
<span>
|
||||
<strong>{check.serviceName}</strong>
|
||||
<small>
|
||||
@@ -1412,9 +1413,9 @@ function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPa
|
||||
<p>{status.message || endpoint || '127.0.0.1:5010'}</p>
|
||||
</div>
|
||||
<div className="panel-actions">
|
||||
{needsUpdate ? <AlertCircle className="panel-state warning" size={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" size={20} aria-hidden="true" /> : <XCircle className="panel-state danger" size={20} aria-hidden="true" />}
|
||||
{needsUpdate ? <AlertCircle className="panel-state warning" fontSize={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" fontSize={20} aria-hidden="true" /> : <XCircle className="panel-state danger" fontSize={20} aria-hidden="true" />}
|
||||
<button className="icon-button subtle" type="button" title="Close" onClick={onClose}>
|
||||
<X size={16} aria-hidden="true" />
|
||||
<X fontSize={16} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1454,18 +1455,18 @@ function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPa
|
||||
</div>
|
||||
</dl>
|
||||
<div className="agent-metrics">
|
||||
<span><Cpu size={14} aria-hidden="true" /> {systemInfo?.kernel || 'kernel -'}</span>
|
||||
<span><HardDrive size={14} aria-hidden="true" /> {systemInfo?.diskFree || 'disk -'}</span>
|
||||
<span><Cpu fontSize={14} aria-hidden="true" /> {systemInfo?.kernel || 'kernel -'}</span>
|
||||
<span><HardDrive fontSize={14} aria-hidden="true" /> {systemInfo?.diskFree || 'disk -'}</span>
|
||||
</div>
|
||||
{showAgentActions && (
|
||||
<div className="agent-update-action">
|
||||
<button className={`btn ${needsUpdate ? 'btn-warning' : 'btn-secondary'}`} type="button" onClick={onCopyUpdate}>
|
||||
<Clipboard size={15} aria-hidden="true" />
|
||||
<Clipboard fontSize={15} aria-hidden="true" />
|
||||
{needsUpdate ? 'Copy update command' : 'Copy Agent command'}
|
||||
</button>
|
||||
{needsUpdate && latestAgentPackage?.downloadUrl && (
|
||||
<a className="btn btn-secondary" href={latestAgentPackage.downloadUrl} target="_blank" rel="noreferrer">
|
||||
<Download size={15} aria-hidden="true" />
|
||||
<Download fontSize={15} aria-hidden="true" />
|
||||
Latest .deb
|
||||
</a>
|
||||
)}
|
||||
@@ -1488,7 +1489,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
||||
<h2>{app?.appName || 'App detail'}</h2>
|
||||
<p>{app?.appCode || app?.appId || status.message || packageBaseUrl}</p>
|
||||
</div>
|
||||
{status.state === 'loading' ? <Loader2 className="spin panel-state" size={20} aria-hidden="true" /> : <Box className="panel-state" size={20} aria-hidden="true" />}
|
||||
{status.state === 'loading' ? <Loader2 className="spin panel-state" fontSize={20} aria-hidden="true" /> : <Box className="panel-state" fontSize={20} aria-hidden="true" />}
|
||||
</div>
|
||||
|
||||
{app ? (
|
||||
@@ -1510,7 +1511,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
||||
|
||||
<div className="component-list">
|
||||
<div className="component-list-title">
|
||||
<ShieldCheck size={15} aria-hidden="true" />
|
||||
<ShieldCheck fontSize={15} aria-hidden="true" />
|
||||
Components
|
||||
</div>
|
||||
{status.state === 'danger' && (
|
||||
@@ -1533,7 +1534,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
||||
{app.installed && installedServiceStatus !== 'not-checked' && (
|
||||
<div className="service-health-panel">
|
||||
<div className="component-list-title">
|
||||
<Activity size={15} aria-hidden="true" />
|
||||
<Activity fontSize={15} aria-hidden="true" />
|
||||
Service health
|
||||
<span className={`badge badge-${installedServiceStatus === 'healthy' ? 'success' : installedServiceStatus === 'unhealthy' ? 'danger' : 'muted'}`}>
|
||||
{serviceCheckSummary(installedServiceStatus, installedServiceChecks)}
|
||||
@@ -1559,7 +1560,7 @@ function Toast({ toast }) {
|
||||
const Icon = tone === 'danger' ? AlertCircle : tone === 'success' ? CheckCircle2 : Activity;
|
||||
return (
|
||||
<div className={`toast tone-${tone || 'info'}`}>
|
||||
<Icon size={17} aria-hidden="true" />
|
||||
<Icon fontSize={17} aria-hidden="true" />
|
||||
<span>{toast.message}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,93 @@
|
||||
:root {
|
||||
--primary: #3755c3;
|
||||
--primary-dim: #2848b7;
|
||||
--primary-container: #dde1ff;
|
||||
--on-primary: #f8f7ff;
|
||||
--background: #f7f9fb;
|
||||
--surface-lowest: #ffffff;
|
||||
--surface-low: #f0f4f7;
|
||||
--surface: #e8eff3;
|
||||
--surface-high: #e1e9ee;
|
||||
--on-surface: #2a3439;
|
||||
--on-surface-variant: #566166;
|
||||
--outline-variant: #a9b4b9;
|
||||
--danger: #b42318;
|
||||
--danger-bg: #fee4e2;
|
||||
--success: #067647;
|
||||
--success-bg: #dcfae6;
|
||||
--warning: #b54708;
|
||||
--warning-bg: #fef0c7;
|
||||
--info: #175cd3;
|
||||
--info-bg: #d1e9ff;
|
||||
--muted-bg: #e2e8f0;
|
||||
--radius: 8px;
|
||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
|
||||
--shadow-lg: 0 24px 60px rgba(15, 23, 42, 0.18);
|
||||
color-scheme: light;
|
||||
|
||||
/* Phenikaa-X universal tokens — design-language.md v0.4.4 */
|
||||
--blue-50: #eaedf5;
|
||||
--blue-100: #c5cce0;
|
||||
--blue-200: #8c9ac4;
|
||||
--blue-300: #6e7ca8;
|
||||
--blue-500: #223771;
|
||||
--blue-700: #1a2a57;
|
||||
--blue-900: #1e2243;
|
||||
--orange-100: #fcd9c4;
|
||||
--orange-300: #f58220;
|
||||
--orange-500: #f26522;
|
||||
--orange-700: #c44e14;
|
||||
--neutral-0: #ffffff;
|
||||
--neutral-50: #f7f8fb;
|
||||
--neutral-100: #eeedf6;
|
||||
--neutral-200: #dddfea;
|
||||
--neutral-300: #c5c8d8;
|
||||
--neutral-400: #9fa3b8;
|
||||
--neutral-450: #8c90a8;
|
||||
--neutral-500: #757a91;
|
||||
--neutral-600: #565b70;
|
||||
--neutral-700: #3c4054;
|
||||
--neutral-800: #272a3a;
|
||||
--neutral-900: #161824;
|
||||
|
||||
--surface-base: var(--neutral-0);
|
||||
--surface-raised: var(--neutral-50);
|
||||
--surface-brand-tint: var(--neutral-100);
|
||||
--text-primary: var(--neutral-900);
|
||||
--text-secondary: var(--neutral-600);
|
||||
--text-tertiary: var(--neutral-500);
|
||||
--border-subtle: var(--neutral-300);
|
||||
--border-strong: var(--neutral-450);
|
||||
--brand-primary: var(--blue-500);
|
||||
--brand-primary-strong: var(--blue-700);
|
||||
--brand-accent: var(--orange-500);
|
||||
--brand-accent-strong: var(--orange-700);
|
||||
|
||||
--semantic-success: #2e7d32;
|
||||
--semantic-success-bg: #e8f5e9;
|
||||
--semantic-warning: #9a5b00;
|
||||
--semantic-warning-icon: #cb6119;
|
||||
--semantic-warning-bg: #fff4db;
|
||||
--semantic-error: #c62828;
|
||||
--semantic-error-bg: #ffebee;
|
||||
--semantic-info: #1565c0;
|
||||
--semantic-info-bg: #e7f1fc;
|
||||
|
||||
--space-xxs: 4px;
|
||||
--space-xs: 8px;
|
||||
--space-sm: 12px;
|
||||
--space-md: 16px;
|
||||
--space-lg: 24px;
|
||||
--space-xl: 32px;
|
||||
--space-2xl: 48px;
|
||||
--radius-sm: 2px;
|
||||
--radius-md: 4px;
|
||||
--radius-lg: 8px;
|
||||
--elevation-1: 0 1px 3px rgba(22, 24, 36, 0.12);
|
||||
--elevation-2: 0 4px 12px rgba(22, 24, 36, 0.18);
|
||||
|
||||
/* Compatibility aliases used by the existing components. */
|
||||
--primary: var(--brand-primary);
|
||||
--primary-dim: var(--brand-primary-strong);
|
||||
--primary-container: var(--blue-50);
|
||||
--on-primary: var(--neutral-0);
|
||||
--background: var(--surface-raised);
|
||||
--surface-lowest: var(--surface-base);
|
||||
--surface-low: var(--surface-raised);
|
||||
--surface: var(--surface-brand-tint);
|
||||
--surface-high: var(--neutral-200);
|
||||
--on-surface: var(--text-primary);
|
||||
--on-surface-variant: var(--text-secondary);
|
||||
--outline: var(--border-strong);
|
||||
--outline-variant: var(--border-subtle);
|
||||
--danger: var(--semantic-error);
|
||||
--danger-bg: var(--semantic-error-bg);
|
||||
--success: var(--semantic-success);
|
||||
--success-bg: var(--semantic-success-bg);
|
||||
--warning: var(--semantic-warning);
|
||||
--warning-bg: var(--semantic-warning-bg);
|
||||
--info: var(--semantic-info);
|
||||
--info-bg: var(--semantic-info-bg);
|
||||
--muted-bg: var(--neutral-200);
|
||||
--radius: var(--radius-md);
|
||||
--shadow-sm: var(--elevation-1);
|
||||
--shadow-lg: var(--elevation-2);
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -40,8 +105,9 @@ body {
|
||||
margin: 0;
|
||||
background: var(--background);
|
||||
color: var(--on-surface);
|
||||
font-family: "Inter", Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -75,7 +141,7 @@ p {
|
||||
h1,
|
||||
h2,
|
||||
.brand-copy strong {
|
||||
font-family: "Manrope", Arial, sans-serif;
|
||||
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
code {
|
||||
@@ -1446,3 +1512,290 @@ tbody tr.selected-row td.action-col {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Phenikaa-X application theme
|
||||
Keeps the existing component contract while applying the Layer-1 language.
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
::selection {
|
||||
background: var(--orange-100);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--blue-500);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: var(--blue-900);
|
||||
border-color: var(--neutral-700);
|
||||
}
|
||||
|
||||
.brand-block {
|
||||
border-bottom: 1px solid rgba(197, 204, 224, 0.18);
|
||||
min-height: 72px;
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
background: var(--brand-accent);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: none;
|
||||
color: var(--neutral-0);
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.brand-copy strong,
|
||||
.sidebar .status-box strong,
|
||||
.sidebar .endpoint-summary-row strong {
|
||||
color: var(--neutral-0);
|
||||
}
|
||||
|
||||
.brand-copy strong {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.brand-copy span,
|
||||
.sidebar .nav-label,
|
||||
.sidebar .endpoint-summary-row span,
|
||||
.sidebar .status-box div > span {
|
||||
color: var(--blue-100);
|
||||
}
|
||||
|
||||
.brand-copy span,
|
||||
.nav-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.sidebar .status-box,
|
||||
.sidebar .endpoint-summary {
|
||||
background: rgba(255, 255, 255, 0.055);
|
||||
border-color: rgba(197, 204, 224, 0.2);
|
||||
}
|
||||
|
||||
.sidebar .status-box:hover,
|
||||
.sidebar .endpoint-summary:hover {
|
||||
background: rgba(255, 255, 255, 0.085);
|
||||
border-color: rgba(197, 204, 224, 0.35);
|
||||
}
|
||||
|
||||
.sidebar .btn-secondary {
|
||||
background: transparent;
|
||||
border-color: var(--blue-300);
|
||||
color: var(--neutral-0);
|
||||
}
|
||||
|
||||
.sidebar .btn-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--blue-100);
|
||||
}
|
||||
|
||||
.topbar {
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border-color: var(--border-subtle);
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.topbar-title span {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.topbar-title strong {
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.page h1 {
|
||||
color: var(--text-primary);
|
||||
font-size: 25px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.panel,
|
||||
.table-panel,
|
||||
.dialog-panel,
|
||||
.offline-banner,
|
||||
.agent-status-button {
|
||||
border-color: var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--elevation-1);
|
||||
}
|
||||
|
||||
.panel,
|
||||
.table-panel,
|
||||
.dialog-panel {
|
||||
background: var(--surface-base);
|
||||
}
|
||||
|
||||
.panel-header,
|
||||
.dialog-header,
|
||||
.dialog-actions,
|
||||
.table-wrap thead,
|
||||
.page-filters {
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
|
||||
.btn,
|
||||
.icon-button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--brand-primary);
|
||||
color: var(--neutral-0);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--brand-primary-strong);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: var(--brand-accent);
|
||||
color: var(--neutral-900);
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: var(--brand-accent-strong);
|
||||
color: var(--neutral-0);
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.icon-button {
|
||||
background: var(--surface-base);
|
||||
border-color: var(--border-subtle);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover,
|
||||
.icon-button:hover,
|
||||
.icon-button.subtle:hover {
|
||||
background: var(--blue-50);
|
||||
border-color: var(--blue-200);
|
||||
color: var(--brand-primary);
|
||||
}
|
||||
|
||||
.settings-field input,
|
||||
.filter-field input,
|
||||
.filter-field select,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
background: var(--surface-base);
|
||||
border-color: var(--border-strong);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.settings-field input:focus,
|
||||
.filter-field input:focus,
|
||||
.filter-field select:focus,
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: var(--brand-primary);
|
||||
box-shadow: 0 0 0 3px rgba(34, 55, 113, 0.16);
|
||||
}
|
||||
|
||||
table {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
thead {
|
||||
background: var(--surface-brand-tint);
|
||||
}
|
||||
|
||||
th {
|
||||
color: var(--blue-700);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.045em;
|
||||
}
|
||||
|
||||
td {
|
||||
border-color: var(--neutral-200);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
tbody tr:hover,
|
||||
.selected-row {
|
||||
background: var(--blue-50);
|
||||
}
|
||||
|
||||
.badge {
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: var(--semantic-success-bg);
|
||||
color: var(--semantic-success);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: var(--semantic-error-bg);
|
||||
color: var(--semantic-error);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: var(--semantic-warning-bg);
|
||||
color: var(--semantic-warning);
|
||||
}
|
||||
|
||||
.badge-info,
|
||||
.badge-primary {
|
||||
background: var(--semantic-info-bg);
|
||||
color: var(--semantic-info);
|
||||
}
|
||||
|
||||
code,
|
||||
.task-log-list {
|
||||
background: var(--blue-900);
|
||||
color: var(--neutral-100);
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.sidebar {
|
||||
border-bottom-color: var(--neutral-700);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.page {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
scroll-behavior: auto !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user