Compare commits
2 Commits
8341503a39
...
agent/hex/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
040d4cb54d | ||
|
|
47cbeed456 |
@@ -8,11 +8,12 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.14" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
<!-- ============================================================================
|
|
||||||
Agent Card — Final Integration (CUB-45)
|
|
||||||
Assembles: AgentStatusBadge, TaskProgressBar, QuickJumpButton
|
|
||||||
Layout: left-border accent, aria-labels, role="article"
|
|
||||||
========================================================================== -->
|
|
||||||
|
|
||||||
<article
|
|
||||||
class="agent-card"
|
|
||||||
[class]="'agent-card--' + status()"
|
|
||||||
[attr.aria-label]="'Agent card: ' + status() + ' status, task: ' + (task() || 'none')"
|
|
||||||
[attr.role]="'article'"
|
|
||||||
[style.--agent-status-color]="statusColorVar()"
|
|
||||||
[style.--agent-status-bg]="statusBgVar()"
|
|
||||||
>
|
|
||||||
<!-- Left border accent (4px, matching status color) -->
|
|
||||||
<div
|
|
||||||
class="agent-card__accent"
|
|
||||||
[attr.aria-hidden]="'true'"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- Card body -->
|
|
||||||
<div class="agent-card__body">
|
|
||||||
|
|
||||||
<!-- Header row: Status Badge + channel + last activity -->
|
|
||||||
<div class="agent-card__header">
|
|
||||||
<div class="agent-card__status-row">
|
|
||||||
<!-- Sub-component: Agent Status Badge -->
|
|
||||||
<app-agent-status-badge
|
|
||||||
[status]="status()"
|
|
||||||
[attr.aria-label]="status() + ' status'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="agent-card__meta">
|
|
||||||
@if (channel()) {
|
|
||||||
<span
|
|
||||||
class="agent-card__channel text-mono"
|
|
||||||
[attr.aria-label]="'Channel: ' + channel()"
|
|
||||||
>
|
|
||||||
<mat-icon
|
|
||||||
class="agent-card__channel-icon"
|
|
||||||
fontIcon="forum"
|
|
||||||
[inline]="true"
|
|
||||||
[attr.aria-hidden]="'true'"
|
|
||||||
></mat-icon>
|
|
||||||
{{ channel() }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
<span
|
|
||||||
class="agent-card__last-activity text-mono"
|
|
||||||
[attr.aria-label]="'Last activity: ' + lastActivityText()"
|
|
||||||
>
|
|
||||||
{{ lastActivityText() }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Task description -->
|
|
||||||
@if (task()) {
|
|
||||||
<p
|
|
||||||
class="agent-card__task"
|
|
||||||
[attr.aria-label]="'Current task: ' + task()"
|
|
||||||
>
|
|
||||||
{{ task() }}
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Sub-component: Task Progress Bar -->
|
|
||||||
@if (showProgress()) {
|
|
||||||
<div
|
|
||||||
class="agent-card__progress"
|
|
||||||
[attr.aria-label]="'Task progress: ' + progress() + '%'"
|
|
||||||
>
|
|
||||||
<app-task-progress-bar
|
|
||||||
[progress]="progress()"
|
|
||||||
[showElapsed]="showElapsed()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Footer: session key (truncated) + Quick-Jump button -->
|
|
||||||
<div class="agent-card__footer">
|
|
||||||
<span
|
|
||||||
class="agent-card__session text-mono"
|
|
||||||
[matTooltip]="sessionKey()"
|
|
||||||
[attr.aria-label]="'Session key: ' + sessionKey()"
|
|
||||||
>
|
|
||||||
{{ truncatedSessionKey() }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<!-- Sub-component: Quick-Jump Button -->
|
|
||||||
<app-quick-jump-button
|
|
||||||
[sessionKey]="sessionKey()"
|
|
||||||
(jumpClick)="onJumpClick($event)"
|
|
||||||
[attr.aria-label]="'Jump to agent session'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Agent Card Styles — M3 Tactical Dark (CUB-45 Final Integration)
|
|
||||||
// Assembles: AgentStatusBadge, TaskProgressBar, QuickJumpButton
|
|
||||||
// Uses CUB-47 dark mode CSS variables.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Card Shell
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
min-width: var(--cc-card-min-width, 320px);
|
|
||||||
border-radius: var(--cc-card-border-radius, 16px);
|
|
||||||
background-color: var(--cc-surface-container);
|
|
||||||
overflow: hidden;
|
|
||||||
transition: background-color 150ms ease, box-shadow 150ms ease;
|
|
||||||
|
|
||||||
// M3 state layer overlay on hover (8% primary)
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
border-radius: inherit;
|
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 150ms ease;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus-visible outline for keyboard navigation
|
|
||||||
&:focus-visible {
|
|
||||||
outline: 3px solid var(--status-active);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Left-border accent (4px, matching status color)
|
|
||||||
// Uses --agent-status-color set via [style.--agent-status-color] binding
|
|
||||||
// in the template. Color map:
|
|
||||||
// Active → --status-active (#38BDF8)
|
|
||||||
// Idle → --status-idle (#2DD4BF)
|
|
||||||
// Thinking → --status-thinking (#A78BFA)
|
|
||||||
// Error → --status-error (#F87171)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card__accent {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 4px;
|
|
||||||
background-color: var(--agent-status-color);
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Card Body
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card__body {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
padding: var(--cc-card-padding, 20px);
|
|
||||||
min-width: 0; // Prevent flex blowout for long text
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Header Row
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__status-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__channel {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__channel-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__last-activity {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Task Description
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card__task {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--cc-on-surface);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Progress Bar Container
|
|
||||||
// Overrides the sub-component's bar to use the agent's status color
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card__progress {
|
|
||||||
// Override TaskProgressBar's active indicator to match agent status color
|
|
||||||
--mat-progress-bar-active-indicator-color: var(--agent-status-color);
|
|
||||||
--mat-progress-bar-track-color: var(--agent-status-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Footer Row
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card__footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: auto; // Push footer to bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__session {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
opacity: 0.6;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Status-specific card backgrounds (subtle tint)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.agent-card--active {
|
|
||||||
background-color: var(--status-active-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card--thinking {
|
|
||||||
background-color: var(--status-thinking-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card--error {
|
|
||||||
background-color: var(--status-error-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Idle and offline use default surface-container
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Accessibility: Reduced Motion
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.agent-card {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Responsive: Mobile adjustments
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
@media (max-width: 599px) {
|
|
||||||
.agent-card {
|
|
||||||
min-width: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__body {
|
|
||||||
padding: 16px;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Agent Card Component — Final Integration (CUB-45)
|
|
||||||
// Assembles: AgentStatusBadge, TaskProgressBar, QuickJumpButton
|
|
||||||
// Layout: left-border accent, aria-labels, role="article"
|
|
||||||
// Uses tactical dark mode CSS variables from CUB-47.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Output,
|
|
||||||
computed,
|
|
||||||
input,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
||||||
|
|
||||||
import { AgentStatus } from '../../models';
|
|
||||||
import { AgentStatusBadgeComponent } from '../agent-status-badge/agent-status-badge.component';
|
|
||||||
import { TaskProgressBarComponent } from '../task-progress-bar/task-progress-bar.component';
|
|
||||||
import { QuickJumpButtonComponent } from '../quick-jump-button/quick-jump-button.component';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentCard displays a single agent's status in the Command Hub grid.
|
|
||||||
*
|
|
||||||
* Composes three sub-components:
|
|
||||||
* - AgentStatusBadge — colored pill with pulse animation
|
|
||||||
* - TaskProgressBar — determinate progress with optional elapsed time
|
|
||||||
* - QuickJumpButton — M3 FilledTonalIconButton for session navigation
|
|
||||||
*
|
|
||||||
* Inputs:
|
|
||||||
* - status: AgentStatus — current agent status (required)
|
|
||||||
* - task: string — current task description
|
|
||||||
* - progress: number — task progress percentage (0–100)
|
|
||||||
* - sessionKey: string — full session key (required)
|
|
||||||
* - channel: string — communication channel (e.g., "telegram")
|
|
||||||
* - lastActivity: Date — timestamp of last activity
|
|
||||||
*
|
|
||||||
* Accessibility:
|
|
||||||
* - role="article" on the card element
|
|
||||||
* - aria-labels for status badge, progress bar, and jump button
|
|
||||||
* - focus-visible outlines for keyboard navigation
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'app-agent-card',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
AgentStatusBadgeComponent,
|
|
||||||
TaskProgressBarComponent,
|
|
||||||
QuickJumpButtonComponent,
|
|
||||||
],
|
|
||||||
templateUrl: './agent-card.component.html',
|
|
||||||
styleUrl: './agent-card.component.scss',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class AgentCardComponent {
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Required Inputs
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Current agent status — drives left-border accent color and badge. */
|
|
||||||
readonly status = input.required<AgentStatus>();
|
|
||||||
|
|
||||||
/** Full session key — displayed subtly and passed to QuickJumpButton. */
|
|
||||||
readonly sessionKey = input.required<string>();
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Optional Inputs
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Current task description, e.g. "Reviewing PR #42". */
|
|
||||||
readonly task = input<string>('');
|
|
||||||
|
|
||||||
/** Task progress percentage (0–100). Shown when status is active/thinking. */
|
|
||||||
readonly progress = input<number>(0);
|
|
||||||
|
|
||||||
/** Communication channel, e.g. "telegram". */
|
|
||||||
readonly channel = input<string>('');
|
|
||||||
|
|
||||||
/** Timestamp of last activity. Formatted as relative time. */
|
|
||||||
readonly lastActivity = input<Date>(new Date());
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Outputs
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Emitted when the Quick-Jump button is clicked with the session key. */
|
|
||||||
@Output()
|
|
||||||
readonly jumpClick = new EventEmitter<string>();
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Computed Values
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Map status to CSS custom property for dynamic left-border accent color. */
|
|
||||||
readonly statusColorVar = computed(() => {
|
|
||||||
const map: Record<AgentStatus, string> = {
|
|
||||||
active: 'var(--status-active)',
|
|
||||||
idle: 'var(--status-idle)',
|
|
||||||
thinking: 'var(--status-thinking)',
|
|
||||||
error: 'var(--status-error)',
|
|
||||||
offline: 'var(--status-offline)',
|
|
||||||
};
|
|
||||||
return map[this.status()];
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Map status to background tint CSS variable. */
|
|
||||||
readonly statusBgVar = computed(() => {
|
|
||||||
const map: Record<AgentStatus, string> = {
|
|
||||||
active: 'var(--status-active-bg)',
|
|
||||||
idle: 'var(--status-idle-bg)',
|
|
||||||
thinking: 'var(--status-thinking-bg)',
|
|
||||||
error: 'var(--status-error-bg)',
|
|
||||||
offline: 'var(--cc-surface-container)',
|
|
||||||
};
|
|
||||||
return map[this.status()];
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Format last activity as relative time string. */
|
|
||||||
readonly lastActivityText = computed(() => {
|
|
||||||
const now = Date.now();
|
|
||||||
const then = this.lastActivity().getTime();
|
|
||||||
const diffMs = now - then;
|
|
||||||
const diffMin = Math.floor(diffMs / 60000);
|
|
||||||
if (diffMin < 1) return 'Just now';
|
|
||||||
if (diffMin < 60) return `${diffMin}m ago`;
|
|
||||||
const diffHr = Math.floor(diffMin / 60);
|
|
||||||
if (diffHr < 24) return `${diffHr}h ago`;
|
|
||||||
return `${Math.floor(diffHr / 24)}d ago`;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Whether to show the progress bar — visible for active/thinking with progress > 0. */
|
|
||||||
readonly showProgress = computed(() => {
|
|
||||||
const s = this.status();
|
|
||||||
return (s === 'active' || s === 'thinking') && this.progress() > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Whether to show the elapsed time on the progress bar. */
|
|
||||||
readonly showElapsed = computed(() => this.showProgress());
|
|
||||||
|
|
||||||
/** Truncate session key for display. */
|
|
||||||
readonly truncatedSessionKey = computed(() => {
|
|
||||||
const key = this.sessionKey();
|
|
||||||
return key.length > 28 ? key.substring(0, 28) + '…' : key;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Event Handlers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Forward QuickJumpButton's jump event with the session key. */
|
|
||||||
onJumpClick(sessionKey: string): void {
|
|
||||||
this.jumpClick.emit(sessionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Agent Card — Barrel Export (CUB-45)
|
|
||||||
// ============================================================================
|
|
||||||
export { AgentCardComponent } from './agent-card.component';
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!-- Agent Status Badge: colored pill with pulse animation -->
|
|
||||||
<span
|
|
||||||
class="status-badge"
|
|
||||||
[class]="statusClass"
|
|
||||||
[attr.aria-label]="statusLabels[status] + ' status'"
|
|
||||||
[attr.role]="'status'"
|
|
||||||
>
|
|
||||||
<span class="status-badge__dot"></span>
|
|
||||||
<span *ngIf="showLabel" class="status-badge__label">
|
|
||||||
{{ statusLabels[status] }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Agent Status Badge — Pill Style with Pulse Animation
|
|
||||||
// Per CUB-48: Color mapping + animation durations:
|
|
||||||
// Active → --color-primary (#38BDF8) 2s pulse
|
|
||||||
// Idle → --color-secondary (#2DD4BF) no animation
|
|
||||||
// Thinking → --color-accent (#A78BFA) 3s pulse
|
|
||||||
// Error → --color-danger (#F87171) 0.8s pulse
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Badge Container
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.status-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
// --- Status-specific backgrounds & text ---
|
|
||||||
&--active {
|
|
||||||
background-color: var(--status-active-bg);
|
|
||||||
color: var(--status-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--idle {
|
|
||||||
background-color: var(--status-idle-bg);
|
|
||||||
color: var(--status-idle);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--thinking {
|
|
||||||
background-color: var(--status-thinking-bg);
|
|
||||||
color: var(--status-thinking);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--error {
|
|
||||||
background-color: var(--status-error-bg);
|
|
||||||
color: var(--status-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--offline {
|
|
||||||
background-color: rgba(100, 116, 139, 0.12);
|
|
||||||
color: var(--status-offline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Status Dot (inner indicator circle)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.status-badge__dot {
|
|
||||||
display: inline-block;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
.status-badge--active & {
|
|
||||||
background-color: var(--status-active);
|
|
||||||
animation: badge-pulse-active 2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge--idle & {
|
|
||||||
background-color: var(--status-idle);
|
|
||||||
// Idle: no animation — steady dot
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge--thinking & {
|
|
||||||
background-color: var(--status-thinking);
|
|
||||||
animation: badge-pulse-thinking 3s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge--error & {
|
|
||||||
background-color: var(--status-error);
|
|
||||||
animation: badge-pulse-error 0.8s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge--offline & {
|
|
||||||
background-color: var(--status-offline);
|
|
||||||
// Offline: no animation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Label Text
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.status-badge__label {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Badge Pulse Keyframes
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// These are scoped to the badge component rather than reusing the global
|
|
||||||
// .status-dot animations, because the badge pulse is subtler (scale + opacity
|
|
||||||
// blend for a pill context vs. standalone dot context).
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@keyframes badge-pulse-active {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 0.7;
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes badge-pulse-thinking {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes badge-pulse-error {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Accessibility: Reduced Motion
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.status-badge__dot {
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Agent Status Badge Component
|
|
||||||
// Per CUB-48: Colored pill badge with pulse animation for agent statuses.
|
|
||||||
// Displays Active, Idle, Thinking, or Error with correct color mapping.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { AgentStatus } from '../../models';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reusable status badge that renders a colored pill with label text
|
|
||||||
* and a subtle pulse animation for Active, Thinking, and Error states.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* ```html
|
|
||||||
* <app-agent-status-badge status="active" />
|
|
||||||
* <app-agent-status-badge status="error" />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'app-agent-status-badge',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule],
|
|
||||||
templateUrl: './agent-status-badge.component.html',
|
|
||||||
styleUrl: './agent-status-badge.component.scss',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class AgentStatusBadgeComponent {
|
|
||||||
/** Agent status to display. Maps to a color and animation. */
|
|
||||||
@Input({ required: true })
|
|
||||||
status!: AgentStatus;
|
|
||||||
|
|
||||||
/** Whether to show the status label text alongside the dot. Defaults to true. */
|
|
||||||
@Input()
|
|
||||||
showLabel = true;
|
|
||||||
|
|
||||||
/** Mapping from status to human-readable label. */
|
|
||||||
readonly statusLabels: Record<AgentStatus, string> = {
|
|
||||||
active: 'Active',
|
|
||||||
idle: 'Idle',
|
|
||||||
thinking: 'Thinking',
|
|
||||||
error: 'Error',
|
|
||||||
offline: 'Offline',
|
|
||||||
};
|
|
||||||
|
|
||||||
/** CSS class string for the badge container based on current status. */
|
|
||||||
get statusClass(): string {
|
|
||||||
return `status-badge--${this.status}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Agent Status Badge — Barrel Export
|
|
||||||
// CUB-48
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export { AgentStatusBadgeComponent } from './agent-status-badge.component';
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Component Barrel Exports
|
|
||||||
// ============================================================================
|
|
||||||
export * from './agent-card/index';
|
|
||||||
export * from './agent-status-badge/index';
|
|
||||||
export * from './quick-jump-button/quick-jump-button.component';
|
|
||||||
export * from './task-progress-bar/index';
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
class="quick-jump-button"
|
|
||||||
[attr.aria-label]="'Jump to agent session'"
|
|
||||||
(click)="onJumpClick()"
|
|
||||||
>
|
|
||||||
<mat-icon>arrow_forward</mat-icon>
|
|
||||||
</button>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Quick-Jump Button — M3 FilledTonalIconButton
|
|
||||||
// Per spec Section 7.3: Agent Card Quick-Jump action
|
|
||||||
// M3 spec: FilledTonalIconButton uses secondary container color
|
|
||||||
// with 8% state layer overlay for hover/focus.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
.quick-jump-button {
|
|
||||||
// M3 FilledTonalIconButton: secondary-container background
|
|
||||||
// Angular Material mat-icon-button sets up the base shape (40x40, round).
|
|
||||||
// We override the color tokens to match FilledTonal style.
|
|
||||||
--mdc-icon-button-icon-color: var(--mat-sys-on-secondary-container);
|
|
||||||
background-color: var(--mat-sys-secondary-container);
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
|
|
||||||
// M3 State Layer: 8% overlay on hover
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--mat-sys-secondary-container);
|
|
||||||
// State layer overlay using a pseudo-element for precise 8% opacity
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--mat-sys-on-secondary-container);
|
|
||||||
opacity: 0.08;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// M3 State Layer: 12% overlay on focus-visible (slightly stronger for accessibility)
|
|
||||||
&:focus-visible {
|
|
||||||
background-color: var(--mat-sys-secondary-container);
|
|
||||||
outline: 3px solid var(--status-active);
|
|
||||||
outline-offset: 2px;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--mat-sys-on-secondary-container);
|
|
||||||
opacity: 0.12;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// M3 State Layer: 12% overlay on active/pressed
|
|
||||||
&:active {
|
|
||||||
background-color: var(--mat-sys-secondary-container);
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--mat-sys-on-secondary-container);
|
|
||||||
opacity: 0.12;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Icon color stays on-secondary-container across all states
|
|
||||||
.mat-icon {
|
|
||||||
color: var(--mat-sys-on-secondary-container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
|
||||||
import { MatIconButton } from '@angular/material/button';
|
|
||||||
import { MatIcon } from '@angular/material/icon';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quick-Jump Button — M3 FilledTonalIconButton
|
|
||||||
*
|
|
||||||
* An icon button that emits a navigation event for jumping to an agent session.
|
|
||||||
* Uses the Material Design 3 FilledTonalIconButton style with 8% state layer
|
|
||||||
* overlay on hover and focus.
|
|
||||||
*
|
|
||||||
* Per spec Section 7.3: Agent Card Component Interface
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'app-quick-jump-button',
|
|
||||||
standalone: true,
|
|
||||||
imports: [MatIconButton, MatIcon],
|
|
||||||
templateUrl: './quick-jump-button.component.html',
|
|
||||||
styleUrl: './quick-jump-button.component.scss',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class QuickJumpButtonComponent {
|
|
||||||
/** Emitted when the button is clicked, carrying the session key for navigation. */
|
|
||||||
@Output() jumpClick = new EventEmitter<string>();
|
|
||||||
|
|
||||||
/** The session key to navigate to. Set by the parent agent card. */
|
|
||||||
@Input()
|
|
||||||
sessionKey = '';
|
|
||||||
|
|
||||||
onJumpClick(): void {
|
|
||||||
this.jumpClick.emit(this.sessionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Task Progress Bar — Barrel Export
|
|
||||||
// CUB-44
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export { TaskProgressBarComponent } from './task-progress-bar.component';
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<!-- Task Progress Bar: determinate progress with optional elapsed time -->
|
|
||||||
<div class="task-progress-bar">
|
|
||||||
<!-- Info row: percentage + optional elapsed -->
|
|
||||||
<div class="task-progress-bar__info">
|
|
||||||
<span class="task-progress-bar__percent">{{ clampedProgress }}%</span>
|
|
||||||
<span *ngIf="showElapsed" class="task-progress-bar__elapsed">
|
|
||||||
{{ elapsedText }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Angular Material determinate progress bar -->
|
|
||||||
<mat-progress-bar
|
|
||||||
class="task-progress-bar__bar"
|
|
||||||
mode="determinate"
|
|
||||||
[value]="clampedProgress"
|
|
||||||
aria-label="Task progress"
|
|
||||||
></mat-progress-bar>
|
|
||||||
</div>
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Task Progress Bar — Tactical Dark Theme Styling
|
|
||||||
// Per CUB-44: Uses --color-primary for bar fill and --color-surface-light
|
|
||||||
// for track background, mapped to the Control Center's M3 dark tokens.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Container
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.task-progress-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Info row: percentage label + elapsed time
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
.task-progress-bar__info {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-progress-bar__percent {
|
|
||||||
font-family: var(--cc-font-mono, 'Roboto Mono', monospace);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--cc-on-surface, #E2E8F0);
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-progress-bar__elapsed {
|
|
||||||
font-family: var(--cc-font-mono, 'Roboto Mono', monospace);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--cc-on-surface-variant, #8A9BB0);
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Material Progress Bar Overrides
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Map the spec's --color-primary and --color-surface-light to the Control
|
|
||||||
// Center's actual theme tokens. This ensures the bar uses the tactical dark
|
|
||||||
// palette while respecting the spec's variable naming.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
.task-progress-bar__bar {
|
|
||||||
// Override the track (background) to use the surface container
|
|
||||||
--mat-progress-bar-track-height: 6px;
|
|
||||||
--mat-progress-bar-active-indicator-height: 6px;
|
|
||||||
|
|
||||||
// Bar fill color: primary (cyan/sky blue per tactical dark theme)
|
|
||||||
--mat-progress-bar-active-indicator-color: var(--color-primary, var(--mat-sys-primary, #38BDF8));
|
|
||||||
|
|
||||||
// Track background: surface container (dark slate)
|
|
||||||
--mat-progress-bar-track-color: var(--color-surface-light, var(--cc-surface-container, #1C2027));
|
|
||||||
|
|
||||||
// Border radius for a softer bar
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
// Smooth transition on value changes
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rounded ends on the progress bar fill
|
|
||||||
:host ::ng-deep .mdc-linear-progress__bar-inner {
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rounded track background
|
|
||||||
:host ::ng-deep .mdc-linear-progress__track {
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// Task Progress Bar Component
|
|
||||||
// Per CUB-44: Determinate progress bar with optional elapsed time display.
|
|
||||||
// Uses Angular Material mat-progress-bar in determinate mode with tactical
|
|
||||||
// dark theme styling via CSS custom properties.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a determinate progress bar with an optional elapsed time indicator.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* ```html
|
|
||||||
* <app-task-progress-bar [progress]="65" />
|
|
||||||
* <app-task-progress-bar [progress]="42" [showElapsed]="true" />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'app-task-progress-bar',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, MatProgressBarModule],
|
|
||||||
templateUrl: './task-progress-bar.component.html',
|
|
||||||
styleUrl: './task-progress-bar.component.scss',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class TaskProgressBarComponent implements OnInit, OnDestroy {
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Inputs
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Current progress percentage (0–100). Required. */
|
|
||||||
@Input({ required: true })
|
|
||||||
progress!: number;
|
|
||||||
|
|
||||||
/** Whether to show elapsed time next to the percentage. Defaults to false. */
|
|
||||||
@Input()
|
|
||||||
showElapsed = false;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Internal state
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Timestamp when the component initialized — used for elapsed calculation. */
|
|
||||||
startTime = Date.now();
|
|
||||||
|
|
||||||
/** Formatted elapsed time string, e.g. "2m 15s ago". */
|
|
||||||
elapsedText = '';
|
|
||||||
|
|
||||||
/** Interval timer for updating the elapsed display. */
|
|
||||||
private timer: ReturnType<typeof setInterval> | null = null;
|
|
||||||
|
|
||||||
constructor(private cdr: ChangeDetectorRef) {}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Lifecycle
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.updateElapsed();
|
|
||||||
|
|
||||||
if (this.showElapsed) {
|
|
||||||
// Update elapsed time every second
|
|
||||||
this.timer = setInterval(() => {
|
|
||||||
this.updateElapsed();
|
|
||||||
this.cdr.markForCheck();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Clamp progress to 0–100 for safety. */
|
|
||||||
get clampedProgress(): number {
|
|
||||||
return Math.max(0, Math.min(100, this.progress ?? 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Recalculate the elapsed time string. */
|
|
||||||
private updateElapsed(): void {
|
|
||||||
const elapsedMs = Date.now() - this.startTime;
|
|
||||||
const totalSeconds = Math.floor(elapsedMs / 1000);
|
|
||||||
const minutes = Math.floor(totalSeconds / 60);
|
|
||||||
const seconds = totalSeconds % 60;
|
|
||||||
|
|
||||||
if (minutes > 0) {
|
|
||||||
this.elapsedText = `${minutes}m ${seconds}s ago`;
|
|
||||||
} else {
|
|
||||||
this.elapsedText = `${seconds}s ago`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -48,17 +48,6 @@ html {
|
|||||||
// These are NOT part of the M3 tonal palette; they are semantic overrides.
|
// These are NOT part of the M3 tonal palette; they are semantic overrides.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
:root {
|
:root {
|
||||||
// --- Tactical Dark Mode color palette (CUB-47) ---
|
|
||||||
--color-surface: #0F172A;
|
|
||||||
--color-surface-light: #1E293B;
|
|
||||||
--color-primary: #38BDF8;
|
|
||||||
--color-secondary: #2DD4BF;
|
|
||||||
--color-accent: #A78BFA;
|
|
||||||
--color-danger: #F87171;
|
|
||||||
--color-text-primary: #FFFFFF;
|
|
||||||
--color-text-secondary: #94A3B8;
|
|
||||||
--color-border: #334155;
|
|
||||||
|
|
||||||
// --- Status colors ---
|
// --- Status colors ---
|
||||||
--status-active: #38BDF8;
|
--status-active: #38BDF8;
|
||||||
--status-idle: #2DD4BF;
|
--status-idle: #2DD4BF;
|
||||||
@@ -101,7 +90,7 @@ html {
|
|||||||
// Global Body Styles
|
// Global Body Styles
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
body {
|
body {
|
||||||
background-color: var(--color-surface);
|
background-color: var(--cc-background);
|
||||||
color: var(--cc-on-surface);
|
color: var(--cc-on-surface);
|
||||||
font-family: 'Inter', 'Roboto', sans-serif;
|
font-family: 'Inter', 'Roboto', sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user