();
+
+ get displayLabel(): string {
+ return this.label() ?? this.titleCase(this.status());
+ }
+
+ /** CSS class driven by the current status value. */
+ get statusClass(): string {
+ return `badge--${this.status()}`;
+ }
+
+ /** Whether the pulse animation should be active for the current status. */
+ get hasPulse(): boolean {
+ return this.status() === 'active' || this.status() === 'thinking' || this.status() === 'error';
+ }
+
+ private titleCase(value: string): string {
+ return value.charAt(0).toUpperCase() + value.slice(1);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/agent-status-badge/index.ts b/frontend/src/app/components/agent-status-badge/index.ts
new file mode 100644
index 0000000..e531d31
--- /dev/null
+++ b/frontend/src/app/components/agent-status-badge/index.ts
@@ -0,0 +1 @@
+export { AgentStatusBadgeComponent } from './agent-status-badge.component';
\ No newline at end of file
diff --git a/frontend/src/app/components/index.ts b/frontend/src/app/components/index.ts
new file mode 100644
index 0000000..09c8fb9
--- /dev/null
+++ b/frontend/src/app/components/index.ts
@@ -0,0 +1 @@
+export { AgentStatusBadgeComponent } from './agent-status-badge/agent-status-badge.component';
\ No newline at end of file
diff --git a/frontend/src/app/pages/hub/hub-page.component.scss b/frontend/src/app/pages/hub/hub-page.component.scss
new file mode 100644
index 0000000..1b2c65f
--- /dev/null
+++ b/frontend/src/app/pages/hub/hub-page.component.scss
@@ -0,0 +1,28 @@
+// ============================================================================
+// Hub Page — Responsive AgentCard Grid
+// Desktop (≥1024px): 2×2 grid
+// Mobile (<1024px): single-column stack
+// ============================================================================
+
+.hub-page {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 16px;
+ padding: var(--cc-section-padding, 16px);
+ min-height: 400px;
+ overflow-x: hidden;
+}
+
+.hub-page__placeholder {
+ color: var(--cc-on-surface-variant);
+ font-size: 16px;
+ text-align: center;
+ padding: 24px 0;
+}
+
+// Desktop / kiosk breakpoint — 2-column grid
+@media (min-width: 1024px) {
+ .hub-page {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/pages/hub/hub-page.component.ts b/frontend/src/app/pages/hub/hub-page.component.ts
index 7819be4..1749b94 100644
--- a/frontend/src/app/pages/hub/hub-page.component.ts
+++ b/frontend/src/app/pages/hub/hub-page.component.ts
@@ -9,18 +9,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
Command Hub — Fleet status grid will render here
`,
- styles: [`
- .hub-page {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 400px;
- }
- .hub-page__placeholder {
- color: var(--cc-on-surface-variant);
- font-size: 16px;
- }
- `],
+ styleUrl: './hub-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HubPageComponent {}
\ No newline at end of file
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss
index af13a84..a581a28 100644
--- a/frontend/src/styles.scss
+++ b/frontend/src/styles.scss
@@ -48,6 +48,17 @@ html {
// These are NOT part of the M3 tonal palette; they are semantic overrides.
// ---------------------------------------------------------------------------
: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-active: #38BDF8;
--status-idle: #2DD4BF;
@@ -90,7 +101,7 @@ html {
// Global Body Styles
// ---------------------------------------------------------------------------
body {
- background-color: var(--cc-background);
+ background-color: var(--color-surface);
color: var(--cc-on-surface);
font-family: 'Inter', 'Roboto', sans-serif;
margin: 0;