Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

admin-console.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

admin-console.tsxBlame1484 lines · 1 contributor
11c3ab6ccanty labs1import type { FC } from "hono/jsx";
2
3export interface AdminConsoleProps {
4 user?: any;
5 kpis?: any;
6 activeSection?: string;
7}
8
9export const AdminConsole: FC<AdminConsoleProps> = (props) => {
10 const initialSection = props.activeSection || "overview";
11
12 return (
13 <>
14 <style dangerouslySetInnerHTML={{ __html: css }} />
15 <div class="ac-root">
16 {/* Sidebar */}
17 <aside class="ac-sidebar">
18 <a href="/" class="ac-logo">
19 <span class="ac-logo-mark"></span>
20 gluecron
21 <span class="ac-logo-admin">admin</span>
22 </a>
23 <nav class="ac-nav" id="ac-nav">
24 <button type="button" class="ac-nav-item" data-section="overview" data-active="true">
25 <span class="ac-nav-icon">&#9651;</span>
26 Overview
27 </button>
28 <button type="button" class="ac-nav-item" data-section="diagnostics">
29 <span class="ac-nav-icon">&#9677;</span>
30 Diagnostics
31 <span class="ac-nav-count ac-count-amber" id="ac-diag-count">1</span>
32 </button>
33 <button type="button" class="ac-nav-item" data-section="support">
34 <span class="ac-nav-icon">&#9682;</span>
35 Support
36 <span class="ac-nav-count" id="ac-sup-count">4</span>
37 </button>
38 <button type="button" class="ac-nav-item" data-section="billing">
39 <span class="ac-nav-icon">&#9684;</span>
40 Customers
41 </button>
42 <a href="/admin/layers" class="ac-nav-item ac-nav-link">
43 <span class="ac-nav-icon">&#8593;</span>
44 Deploys / Flags
45 </a>
46 <a href="/trust" class="ac-nav-item ac-nav-link">
47 <span class="ac-nav-icon">&#8801;</span>
48 Audit
49 </a>
50 </nav>
51 <div class="ac-sidebar-footer">
52 <div class="ac-footer-status">
53 <span class="ac-dot ac-dot-green"></span>
54 gluecron.com · healthy
55 </div>
56 <div class="ac-footer-meta">82321e8 · main · up 41d</div>
57 </div>
58 </aside>
59
60 {/* Main content */}
61 <main class="ac-main">
62
63 {/* ═══════════ OVERVIEW ═══════════ */}
64 <div class="ac-section" id="ac-section-overview">
65 <div class="ac-page-header">
66 <div>
67 <div class="ac-eyebrow">Site admin · Overview</div>
68 <h1 class="ac-h1">Platform operations</h1>
69 </div>
70 <div class="ac-header-actions">
71 <button type="button" class="ac-btn-secondary" id="ac-deploy-btn">Trigger deploy</button>
72 <button type="button" class="ac-btn-primary" id="ac-run-diag-btn">Run full diagnosis</button>
73 </div>
74 </div>
75
76 {/* KPI row */}
77 <div class="ac-kpi-grid">
78 <div class="ac-kpi-cell">
79 <div class="ac-kpi-label">Uptime · 30d</div>
80 <div class="ac-kpi-value">99.98%</div>
81 <div class="ac-kpi-delta ac-delta-green">SLO 99.9% — within budget</div>
82 </div>
83 <div class="ac-kpi-cell">
84 <div class="ac-kpi-label">Active customers</div>
85 <div class="ac-kpi-value">212</div>
86 <div class="ac-kpi-delta ac-delta-green">+9 this week</div>
87 </div>
88 <div class="ac-kpi-cell">
89 <div class="ac-kpi-label">Open tickets</div>
90 <div class="ac-kpi-value">4</div>
91 <div class="ac-kpi-delta ac-delta-amber">1 urgent · agent pre-diagnosed all 4</div>
92 </div>
93 <div class="ac-kpi-cell">
94 <div class="ac-kpi-label">MRR</div>
95 <div class="ac-kpi-value">$18,940</div>
96 <div class="ac-kpi-delta ac-delta-meta">internal platforms exempt</div>
97 </div>
98 </div>
99
100 {/* Two-column: diagnostics + support queue */}
101 <div class="ac-two-col">
102 <section>
103 <div class="ac-section-header">
104 <h2 class="ac-h2">Self-diagnosis</h2>
105 <button type="button" class="ac-link-btn" data-goto="diagnostics">Open diagnostics →</button>
106 </div>
107 <div class="ac-card">
108 <div class="ac-diag-row">
109 <span class="ac-dot ac-dot-amber ac-pulse" style="margin-top:6px;flex-shrink:0"></span>
110 <div class="ac-diag-content">
111 <div class="ac-diag-title-row">
112 <span class="ac-diag-name">OCI registry</span>
113 <span class="ac-mono-small">registry.gluecron.com</span>
114 </div>
115 <p class="ac-diag-detail">Intermittent push failures — stale region-scoped auth token. Fix drafted.</p>
116 </div>
117 <button type="button" class="ac-btn-ghost ac-fix-btn" data-fix="incident" id="ac-ov-fix-1">Apply fix</button>
118 </div>
119 <div class="ac-diag-row">
120 <span class="ac-dot ac-dot-amber ac-pulse" style="margin-top:6px;flex-shrink:0"></span>
121 <div class="ac-diag-content">
122 <div class="ac-diag-title-row">
123 <span class="ac-diag-name">Gate workers</span>
124 <span class="ac-mono-small">pool-a · 8 workers</span>
125 </div>
126 <p class="ac-diag-detail">At capacity — one stalled repair loop (pr-214) is holding two workers.</p>
127 </div>
128 <button type="button" class="ac-btn-ghost ac-fix-btn" data-fix="drain" id="ac-ov-fix-2">Drain loop</button>
129 </div>
130 <div class="ac-diag-row">
131 <span class="ac-dot ac-dot-green" style="margin-top:6px;flex-shrink:0"></span>
132 <div class="ac-diag-content">
133 <div class="ac-diag-title-row">
134 <span class="ac-diag-name">Postgres (Neon)</span>
135 <span class="ac-mono-small">primary · us-east</span>
136 </div>
137 <p class="ac-diag-detail">Healthy. 4ms median query, no lock contention in 24h.</p>
138 </div>
139 </div>
140 <div class="ac-diag-row">
141 <span class="ac-dot ac-dot-green" style="margin-top:6px;flex-shrink:0"></span>
142 <div class="ac-diag-content">
143 <div class="ac-diag-title-row">
144 <span class="ac-diag-name">Caddy ingress</span>
145 <span class="ac-mono-small">edge · 2 nodes</span>
146 </div>
147 <p class="ac-diag-detail">Certificates valid 71 days. Rate-limit rejections nominal.</p>
148 </div>
149 </div>
150 <div class="ac-diag-row ac-diag-row-last">
151 <span class="ac-dot ac-dot-green" style="margin-top:6px;flex-shrink:0"></span>
152 <div class="ac-diag-content">
153 <div class="ac-diag-title-row">
154 <span class="ac-diag-name">Semantic index</span>
155 <span class="ac-mono-small">voyage-code-3</span>
156 </div>
157 <p class="ac-diag-detail">US shard healthy. EU shard backfill pending (see SUP-1041).</p>
158 </div>
159 </div>
160 </div>
161 </section>
162
163 <section>
164 <div class="ac-section-header">
165 <h2 class="ac-h2">Support queue</h2>
166 <button type="button" class="ac-link-btn" data-goto="support">Open support →</button>
167 </div>
168 <div class="ac-ticket-list-preview">
169 <button type="button" class="ac-ticket-card" data-goto="support">
170 <div class="ac-ticket-meta">
171 <span class="ac-badge ac-badge-urgent">Urgent</span>
172 <span class="ac-mono-small">SUP-1042</span>
173 <span class="ac-ticket-age">18m</span>
174 </div>
175 <div class="ac-ticket-title">Push rejected — gate timeout on every push since 11:40</div>
176 <div class="ac-ticket-who">northwind-labs · Team plan</div>
177 </button>
178 <button type="button" class="ac-ticket-card" data-goto="support">
179 <div class="ac-ticket-meta">
180 <span class="ac-badge ac-badge-high">High</span>
181 <span class="ac-mono-small">SUP-1041</span>
182 <span class="ac-ticket-age">1h</span>
183 </div>
184 <div class="ac-ticket-title">EU data-region repo shows empty semantic search results</div>
185 <div class="ac-ticket-who">helvetia-fintech · Enterprise</div>
186 </button>
187 <button type="button" class="ac-ticket-card" data-goto="support">
188 <div class="ac-ticket-meta">
189 <span class="ac-badge ac-badge-normal">Normal</span>
190 <span class="ac-mono-small">SUP-1039</span>
191 <span class="ac-ticket-age">3h</span>
192 </div>
193 <div class="ac-ticket-title">How do we exempt a bot account from AI review?</div>
194 <div class="ac-ticket-who">acme-tools · Pro</div>
195 </button>
196 </div>
197 </section>
198 </div>
199 </div>
200
201 {/* ═══════════ DIAGNOSTICS ═══════════ */}
202 <div class="ac-section ac-hidden" id="ac-section-diagnostics">
203 <div class="ac-page-header">
204 <div>
205 <div class="ac-eyebrow">Site admin · Diagnostics</div>
206 <h1 class="ac-h1">Self-healing diagnosis</h1>
207 <p class="ac-subtitle">The same repair engine that fixes customer pushes watches the platform itself. When something drifts, it isolates the cause, drafts the fix, and waits for your one-click apply.</p>
208 </div>
209 <button type="button" class="ac-btn-primary" id="ac-diagnose-btn">Run full diagnosis</button>
210 </div>
211
212 {/* Incident card */}
213 <div class="ac-incident-card">
214 <div class="ac-incident-header">
215 <div class="ac-incident-title-row">
216 <span class="ac-dot ac-dot-amber ac-pulse"></span>
217 <span class="ac-incident-title">OCI registry push failing intermittently</span>
218 <span class="ac-mono-small" style="margin-left:auto">detected 22 min ago</span>
219 </div>
220 <p class="ac-incident-desc">Deterministic local agent traced 4 failed pushes to a stale auth token in the registry middleware — config drift from the 0083 data-region migration, not a network fault.</p>
221 </div>
222 <div class="ac-incident-body">
223 <div class="ac-mono-label">Drafted fix</div>
224 <div class="ac-diff-block">
225 <div class="ac-diff-comment">// src/middleware/registry-auth.ts</div>
226 <div class="ac-diff-remove">− const token = env.REGISTRY_TOKEN;</div>
227 <div class="ac-diff-add">+ const token = await regionScopedToken(repo.dataRegion);</div>
228 </div>
229 <div class="ac-incident-actions">
230 <button type="button" class="ac-btn-primary" id="ac-incident-fix-btn">Apply fix</button>
231 <a href="/admin/layers" class="ac-btn-secondary-link">Open trace</a>
232 <span class="ac-incident-note">Applies as an audited PR against the platform repo — reversible.</span>
233 </div>
234 </div>
235 </div>
236
237 {/* Subsystem table */}
238 <h2 class="ac-h2" style="margin:0 0 16px">Subsystems</h2>
239 <div class="ac-table-card">
240 <div class="ac-table-header ac-subsys-grid">
241 <span>Subsystem</span>
242 <span>Signal</span>
243 <span>P99</span>
244 <span>Status</span>
245 </div>
246 <div class="ac-table-row ac-subsys-grid">
247 <span class="ac-table-name">Git Smart HTTP</span>
248 <span class="ac-table-signal">clone/push/fetch</span>
249 <span class="ac-table-p99">310ms</span>
250 <span class="ac-status-cell ac-status-green"><span class="ac-dot ac-dot-green"></span>healthy</span>
251 </div>
252 <div class="ac-table-row ac-subsys-grid">
253 <span class="ac-table-name">Gate runner</span>
254 <span class="ac-table-signal">push-time gates</span>
255 <span class="ac-table-p99">38s</span>
256 <span class="ac-status-cell ac-status-amber"><span class="ac-dot ac-dot-amber ac-pulse"></span>degraded</span>
257 </div>
258 <div class="ac-table-row ac-subsys-grid">
259 <span class="ac-table-name">Auto-repair engine</span>
260 <span class="ac-table-signal">repair loops</span>
261 <span class="ac-table-p99">2 attempts</span>
262 <span class="ac-status-cell ac-status-amber"><span class="ac-dot ac-dot-amber ac-pulse"></span>1 stalled</span>
263 </div>
264 <div class="ac-table-row ac-subsys-grid">
265 <span class="ac-table-name">PR sandboxes</span>
266 <span class="ac-table-signal">boot + preview</span>
267 <span class="ac-table-p99">24s boot</span>
268 <span class="ac-status-cell ac-status-green"><span class="ac-dot ac-dot-green"></span>healthy</span>
269 </div>
270 <div class="ac-table-row ac-subsys-grid">
271 <span class="ac-table-name">OCI registry</span>
272 <span class="ac-table-signal">image push/pull</span>
273 <span class="ac-table-p99">1.9s</span>
274 <span class="ac-status-cell ac-status-amber"><span class="ac-dot ac-dot-amber ac-pulse"></span>fix drafted</span>
275 </div>
276 <div class="ac-table-row ac-subsys-grid">
277 <span class="ac-table-name">Webhooks</span>
278 <span class="ac-table-signal">outbound HMAC</span>
279 <span class="ac-table-p99">140ms</span>
280 <span class="ac-status-cell ac-status-green"><span class="ac-dot ac-dot-green"></span>healthy</span>
281 </div>
282 <div class="ac-table-row ac-subsys-grid ac-table-row-last">
283 <span class="ac-table-name">Email (Resend)</span>
284 <span class="ac-table-signal">notifications</span>
285 <span class="ac-table-p99">820ms</span>
286 <span class="ac-status-cell ac-status-green"><span class="ac-dot ac-dot-green"></span>healthy</span>
287 </div>
288 </div>
289
290 {/* Flywheel telemetry */}
291 <h2 class="ac-h2" style="margin:32px 0 16px">Flywheel telemetry — last 24h</h2>
292 <div class="ac-flywheel-grid">
293 <div class="ac-flywheel-cell">
294 <div class="ac-kpi-label">Repair cycles completed</div>
295 <div class="ac-flywheel-value">31</div>
296 <div class="ac-kpi-delta ac-delta-green">29 converged · median 2 attempts</div>
297 </div>
298 <div class="ac-flywheel-cell">
299 <div class="ac-kpi-label">Stalled loops</div>
300 <div class="ac-flywheel-value">1</div>
301 <div class="ac-kpi-delta ac-delta-amber">pr-214 · oscillating on gate: types — pinned for review</div>
302 </div>
303 <div class="ac-flywheel-cell">
304 <div class="ac-kpi-label">AI spend</div>
305 <div class="ac-flywheel-value">$14.20</div>
306 <div class="ac-kpi-delta ac-delta-meta">62% local models · 38% hosted Claude</div>
307 </div>
308 </div>
309 </div>
310
311 {/* ═══════════ SUPPORT ═══════════ */}
312 <div class="ac-section ac-hidden" id="ac-section-support">
313 <div class="ac-page-header">
314 <div>
315 <div class="ac-eyebrow">Site admin · Support</div>
316 <h1 class="ac-h1">Customer support</h1>
317 <p class="ac-subtitle">Every ticket arrives pre-diagnosed: the agent correlates the customer's report with their audit log, gate runs, and deploys before you open it.</p>
318 </div>
319 </div>
320
321 <div class="ac-support-grid">
322 {/* Ticket list */}
323 <div class="ac-ticket-list" id="ac-ticket-list">
324 <button type="button" class="ac-ticket-card ac-ticket-active" data-ticket="0">
325 <div class="ac-ticket-meta">
326 <span class="ac-badge ac-badge-urgent">Urgent</span>
327 <span class="ac-mono-small">SUP-1042</span>
328 <span class="ac-ticket-age">18m</span>
329 </div>
330 <div class="ac-ticket-title">Push rejected — gate timeout on every push since 11:40</div>
331 <div class="ac-ticket-who">northwind-labs · Team plan</div>
332 </button>
333 <button type="button" class="ac-ticket-card" data-ticket="1">
334 <div class="ac-ticket-meta">
335 <span class="ac-badge ac-badge-high">High</span>
336 <span class="ac-mono-small">SUP-1041</span>
337 <span class="ac-ticket-age">1h</span>
338 </div>
339 <div class="ac-ticket-title">EU data-region repo shows empty semantic search results</div>
340 <div class="ac-ticket-who">helvetia-fintech · Enterprise</div>
341 </button>
342 <button type="button" class="ac-ticket-card" data-ticket="2">
343 <div class="ac-ticket-meta">
344 <span class="ac-badge ac-badge-normal">Normal</span>
345 <span class="ac-mono-small">SUP-1039</span>
346 <span class="ac-ticket-age">3h</span>
347 </div>
348 <div class="ac-ticket-title">How do we exempt a bot account from AI review?</div>
349 <div class="ac-ticket-who">acme-tools · Pro</div>
350 </button>
351 <button type="button" class="ac-ticket-card" data-ticket="3">
352 <div class="ac-ticket-meta">
353 <span class="ac-badge ac-badge-normal">Normal</span>
354 <span class="ac-mono-small">SUP-1038</span>
355 <span class="ac-ticket-age">5h</span>
356 </div>
357 <div class="ac-ticket-title">Sandbox preview URL 404s for ~30s after PR open</div>
358 <div class="ac-ticket-who">brightpath-edu · Team</div>
359 </button>
360 </div>
361
362 {/* Ticket detail */}
363 <div class="ac-ticket-detail" id="ac-ticket-detail">
364 {/* Content injected by JS */}
365 </div>
366 </div>
367 </div>
368
369 {/* ═══════════ CUSTOMERS & BILLING ═══════════ */}
370 <div class="ac-section ac-hidden" id="ac-section-billing">
371 <div style="margin-bottom:32px">
372 <div class="ac-eyebrow">Site admin · Customers</div>
373 <h1 class="ac-h1">Customers &amp; billing</h1>
374 <p class="ac-subtitle">First-party platforms are marked <strong style="color:#16181d">Internal — exempt</strong> and are never billed. Quotas still apply so internal usage can't starve customers.</p>
375 </div>
376 <div class="ac-table-card">
377 <div class="ac-table-header ac-cust-grid">
378 <span>Organization</span>
379 <span>Plan</span>
380 <span>Seats</span>
381 <span>AI usage / mo</span>
382 <span>Billing</span>
383 </div>
384 <div class="ac-table-row ac-cust-grid">
385 <span class="ac-org-cell">
386 <span class="ac-org-avatar" style="background:#4353c9">GC</span>
387 <span class="ac-table-name">gluecron (platform)</span>
388 </span>
389 <span class="ac-table-signal">Internal</span>
390 <span class="ac-table-mono">6</span>
391 <span class="ac-table-mono">—</span>
392 <span class="ac-bill-cell ac-bill-exempt"><span class="ac-dot" style="background:#c4c6cf"></span>Exempt</span>
393 </div>
394 <div class="ac-table-row ac-cust-grid">
395 <span class="ac-org-cell">
396 <span class="ac-org-avatar" style="background:#4353c9">GI</span>
397 <span class="ac-table-name">gluecron-infra</span>
398 </span>
399 <span class="ac-table-signal">Internal</span>
400 <span class="ac-table-mono">3</span>
401 <span class="ac-table-mono">—</span>
402 <span class="ac-bill-cell ac-bill-exempt"><span class="ac-dot" style="background:#c4c6cf"></span>Exempt</span>
403 </div>
404 <div class="ac-table-row ac-cust-grid">
405 <span class="ac-org-cell">
406 <span class="ac-org-avatar" style="background:#16181d">HF</span>
407 <span class="ac-table-name">helvetia-fintech</span>
408 </span>
409 <span class="ac-table-signal">Enterprise</span>
410 <span class="ac-table-mono">140</span>
411 <span class="ac-table-mono">$2,410</span>
412 <span class="ac-bill-cell ac-bill-current"><span class="ac-dot ac-dot-green"></span>Current</span>
413 </div>
414 <div class="ac-table-row ac-cust-grid">
415 <span class="ac-org-cell">
416 <span class="ac-org-avatar" style="background:#41707e">NL</span>
417 <span class="ac-table-name">northwind-labs</span>
418 </span>
419 <span class="ac-table-signal">Team</span>
420 <span class="ac-table-mono">24</span>
421 <span class="ac-table-mono">$380</span>
422 <span class="ac-bill-cell ac-bill-current"><span class="ac-dot ac-dot-green"></span>Current</span>
423 </div>
424 <div class="ac-table-row ac-cust-grid">
425 <span class="ac-org-cell">
426 <span class="ac-org-avatar" style="background:#6b7080">AT</span>
427 <span class="ac-table-name">acme-tools</span>
428 </span>
429 <span class="ac-table-signal">Pro</span>
430 <span class="ac-table-mono">8</span>
431 <span class="ac-table-mono">$96</span>
432 <span class="ac-bill-cell ac-bill-current"><span class="ac-dot ac-dot-green"></span>Current</span>
433 </div>
434 <div class="ac-table-row ac-cust-grid ac-table-row-last">
435 <span class="ac-org-cell">
436 <span class="ac-org-avatar" style="background:#b45309">BE</span>
437 <span class="ac-table-name">brightpath-edu</span>
438 </span>
439 <span class="ac-table-signal">Team</span>
440 <span class="ac-table-mono">17</span>
441 <span class="ac-table-mono">$204</span>
442 <span class="ac-bill-cell ac-bill-pastdue"><span class="ac-dot ac-dot-amber"></span>Past due · 3d</span>
443 </div>
444 </div>
445 </div>
446
447 </main>
448 </div>
449
450 <script dangerouslySetInnerHTML={{ __html: js }} />
451 </>
452 );
453};
454
455export default AdminConsole;
456
457const js = `
458(function() {
459 var tickets = [
460 {
461 id: 'SUP-1042', priority: 'Urgent', badgeCls: 'ac-badge-urgent', age: '18m',
462 title: 'Push rejected — gate timeout on every push since 11:40',
463 who: 'northwind-labs · Team plan', plan: 'Team',
464 report: '"All our pushes to api-core started failing about 40 minutes ago with \\'gate: security timed out\\'. Nothing changed on our side. We\\'re blocked on a release."',
465 diagnosis: 'Their pushes queue behind the stalled repair loop on pr-214 (same worker pool). Not customer-caused. Draining the stalled loop restores gate capacity; no customer action needed.',
466 evidence: 'evidence: 6 gate runs > 120s · worker pool at 100% · repair loop pr-214 oscillating since 11:32'
467 },
468 {
469 id: 'SUP-1041', priority: 'High', badgeCls: 'ac-badge-high', age: '1h',
470 title: 'EU data-region repo shows empty semantic search results',
471 who: 'helvetia-fintech · Enterprise', plan: 'Enterprise',
472 report: '"Semantic search returns nothing for our Frankfurt-region repos. Keyword search works fine."',
473 diagnosis: 'The 0083 data-region migration created the EU index shard but the embedding backfill job never ran for pre-existing repos. Re-running the backfill for region=eu fixes it — approximately 12 minutes.',
474 evidence: 'evidence: eu shard row-count 0 · backfill job last ran pre-migration · 2 repos affected'
475 },
476 {
477 id: 'SUP-1039', priority: 'Normal', badgeCls: 'ac-badge-normal', age: '3h',
478 title: 'How do we exempt a bot account from AI review?',
479 who: 'acme-tools · Pro', plan: 'Pro',
480 report: '"Our release bot\\'s version-bump PRs keep getting flagged by AI review. Can we skip review for that account?"',
481 diagnosis: 'Supported: add the bot to the repo\\'s review-exempt list (Settings → AI review → Exemptions). Drafted reply includes the exact steps and a caution about scope.',
482 evidence: 'evidence: 14 bot PRs flagged, all version bumps · exemption feature available on Pro'
483 },
484 {
485 id: 'SUP-1038', priority: 'Normal', badgeCls: 'ac-badge-normal', age: '5h',
486 title: 'Sandbox preview URL 404s for ~30s after PR open',
487 who: 'brightpath-edu · Team', plan: 'Team',
488 report: '"Clicking the preview link right after opening a PR gives a 404. Refreshing after a minute works."',
489 diagnosis: 'Expected cold-boot window while the sandbox provisions. A \\'booting\\' interstitial ships in the current release; drafted reply explains the timeline.',
490 evidence: 'evidence: median boot 24s · 404s only within first 40s of PR open'
491 }
492 ];
493
494 var state = {
495 view: 'overview',
496 ticket: 0,
497 incidentApplied: false,
498 resolved: false,
499 replied: false,
500 deployed: false,
501 diagnosing: false
502 };
503
504 function showSection(name) {
505 var sections = document.querySelectorAll('.ac-section');
506 sections.forEach(function(s) { s.classList.add('ac-hidden'); });
507 var target = document.getElementById('ac-section-' + name);
508 if (target) target.classList.remove('ac-hidden');
509
510 var navItems = document.querySelectorAll('.ac-nav-item[data-section]');
511 navItems.forEach(function(btn) {
512 var active = btn.getAttribute('data-section') === name;
513 btn.setAttribute('data-active', active ? 'true' : 'false');
514 });
515
516 state.view = name;
517 if (name === 'support') renderTicketDetail();
518 }
519
520 function renderTicketDetail() {
521 var t = tickets[state.ticket];
522 var detail = document.getElementById('ac-ticket-detail');
523 if (!detail) return;
524
525 var resolveLabel = state.resolved ? 'Resolved \\u2713' : 'Apply agent fix &amp; resolve';
526 var resolveBg = state.resolved ? '#1e7f5c' : '#16181d';
527 var replyLabel = state.replied ? 'Reply sent \\u2713' : 'Reply to customer';
528
529 detail.innerHTML = '<div class="ac-td-header">' +
530 '<div class="ac-td-meta">' +
531 '<span class="ac-badge ' + t.badgeCls + '">' + t.priority + '</span>' +
532 '<span class="ac-mono-small">' + t.id + '</span>' +
533 '<span class="ac-td-who">' + t.who + ' · ' + t.plan + '</span>' +
534 '</div>' +
535 '<h2 class="ac-td-title">' + t.title + '</h2>' +
536 '</div>' +
537 '<div class="ac-td-body">' +
538 '<div>' +
539 '<div class="ac-mono-label" style="margin-bottom:8px">Customer report</div>' +
540 '<p class="ac-td-report">' + t.report + '</p>' +
541 '</div>' +
542 '<div class="ac-diagnosis-card">' +
543 '<div class="ac-diagnosis-label">Agent pre-diagnosis</div>' +
544 '<p class="ac-diagnosis-text">' + t.diagnosis + '</p>' +
545 '<div class="ac-evidence">' + t.evidence + '</div>' +
546 '</div>' +
547 '<div class="ac-td-actions">' +
548 '<button type="button" class="ac-btn-primary" id="ac-resolve-btn" style="background:' + resolveBg + '">' + resolveLabel + '</button>' +
549 '<a href="/admin/layers" class="ac-btn-secondary-link">View customer audit log</a>' +
550 '<button type="button" class="ac-btn-secondary" id="ac-reply-btn">' + replyLabel + '</button>' +
551 '</div>' +
552 '</div>';
553
554 var resolveBtn = document.getElementById('ac-resolve-btn');
555 if (resolveBtn && !state.resolved) {
556 resolveBtn.addEventListener('click', function() {
557 state.resolved = true;
558 renderTicketDetail();
559 });
560 }
561 var replyBtn = document.getElementById('ac-reply-btn');
562 if (replyBtn && !state.replied) {
563 replyBtn.addEventListener('click', function() {
564 state.replied = true;
565 renderTicketDetail();
566 });
567 }
568 }
569
570 function updateTicketListSelection() {
571 var cards = document.querySelectorAll('#ac-ticket-list .ac-ticket-card');
572 cards.forEach(function(c, i) {
573 if (i === state.ticket) {
574 c.classList.add('ac-ticket-active');
575 } else {
576 c.classList.remove('ac-ticket-active');
577 }
578 });
579 }
580
581 document.addEventListener('DOMContentLoaded', function() {
582 // Nav items
583 var navItems = document.querySelectorAll('.ac-nav-item[data-section]');
584 navItems.forEach(function(btn) {
585 btn.addEventListener('click', function() {
586 var section = this.getAttribute('data-section');
587 if (section) showSection(section);
588 });
589 });
590
591 // Link buttons (data-goto)
592 document.addEventListener('click', function(e) {
593 var target = e.target;
594 while (target && target !== document) {
595 if (target.getAttribute && target.getAttribute('data-goto')) {
596 showSection(target.getAttribute('data-goto'));
597 return;
598 }
599 target = target.parentElement;
600 }
601 });
602
603 // Deploy button
604 var deployBtn = document.getElementById('ac-deploy-btn');
605 if (deployBtn) {
606 deployBtn.addEventListener('click', function() {
607 state.deployed = true;
608 this.textContent = 'Deploy dispatched \\u2713';
609 this.disabled = true;
610 });
611 }
612
613 // Run full diagnosis button (overview)
614 var runDiagBtn = document.getElementById('ac-run-diag-btn');
615 if (runDiagBtn) {
616 runDiagBtn.addEventListener('click', function() {
617 showSection('diagnostics');
618 });
619 }
620
621 // Diagnose button (diagnostics page)
622 var diagBtn = document.getElementById('ac-diagnose-btn');
623 if (diagBtn) {
624 diagBtn.addEventListener('click', function() {
625 if (state.diagnosing) return;
626 state.diagnosing = true;
627 diagBtn.textContent = 'Diagnosing\\u2026';
628 diagBtn.disabled = true;
629 setTimeout(function() {
630 state.diagnosing = false;
631 diagBtn.textContent = 'Run full diagnosis';
632 diagBtn.disabled = false;
633 }, 2000);
634 });
635 }
636
637 // Overview fix buttons
638 var ovFix1 = document.getElementById('ac-ov-fix-1');
639 if (ovFix1) {
640 ovFix1.addEventListener('click', function() {
641 state.incidentApplied = true;
642 this.textContent = '\\u2713 Applied';
643 this.disabled = true;
644 var incidentBtn = document.getElementById('ac-incident-fix-btn');
645 if (incidentBtn) {
646 incidentBtn.textContent = 'Fix applied \\u2014 PR #219 opened';
647 incidentBtn.style.background = '#1e7f5c';
648 incidentBtn.disabled = true;
649 }
650 });
651 }
652 var ovFix2 = document.getElementById('ac-ov-fix-2');
653 if (ovFix2) {
654 ovFix2.addEventListener('click', function() {
655 this.textContent = '\\u2713 Applied';
656 this.disabled = true;
657 });
658 }
659
660 // Incident fix button (diagnostics page)
661 var incidentFixBtn = document.getElementById('ac-incident-fix-btn');
662 if (incidentFixBtn) {
663 incidentFixBtn.addEventListener('click', function() {
664 state.incidentApplied = true;
665 this.textContent = 'Fix applied \\u2014 PR #219 opened';
666 this.style.background = '#1e7f5c';
667 this.disabled = true;
668 var ovFix = document.getElementById('ac-ov-fix-1');
669 if (ovFix) {
670 ovFix.textContent = '\\u2713 Applied';
671 ovFix.disabled = true;
672 }
673 });
674 }
675
676 // Ticket selection
677 var ticketCards = document.querySelectorAll('#ac-ticket-list .ac-ticket-card');
678 ticketCards.forEach(function(card) {
679 card.addEventListener('click', function() {
680 var idx = parseInt(this.getAttribute('data-ticket') || '0', 10);
681 state.ticket = idx;
682 state.resolved = false;
683 state.replied = false;
684 updateTicketListSelection();
685 renderTicketDetail();
686 });
687 });
688
689 // Initial render
690 renderTicketDetail();
691 });
692})();
693`;
694
695const css = `
696@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;600;700&family=Inter+Tight:wght@500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
697
698*, *::before, *::after { box-sizing: border-box; }
699
700body {
701 margin: 0;
702 background: #fcfcfd;
703 -webkit-font-smoothing: antialiased;
704 text-rendering: optimizeLegibility;
705}
706
707@keyframes gcPulse {
708 0%, 100% { opacity: 1; }
709 50% { opacity: 0.35; }
710}
711
712.ac-root {
713 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
714 font-size: 14px;
715 line-height: 1.55;
716 letter-spacing: -0.008em;
717 color: #16181d;
718 background: #fcfcfd;
719 min-height: 100vh;
720 display: flex;
721}
722
723/* ── Sidebar ── */
724.ac-sidebar {
725 width: 224px;
726 flex-shrink: 0;
727 border-right: 1px solid rgba(22,24,29,0.07);
728 display: flex;
729 flex-direction: column;
730 padding: 20px 12px;
731 position: sticky;
732 top: 0;
733 height: 100vh;
734 overflow-y: auto;
735}
736
737.ac-logo {
738 display: inline-flex;
739 align-items: center;
740 gap: 9px;
741 font-family: 'Inter Tight', sans-serif;
742 font-weight: 600;
743 font-size: 15px;
744 letter-spacing: -0.02em;
745 color: #16181d;
746 text-decoration: none;
747 padding: 4px 10px;
748 margin-bottom: 24px;
749}
750
751.ac-logo-mark {
752 width: 16px;
753 height: 16px;
754 border-radius: 5px;
755 background: #4353c9;
756 display: inline-block;
757 flex-shrink: 0;
758}
759
760.ac-logo-admin {
761 font-family: 'JetBrains Mono', monospace;
762 font-size: 10px;
763 letter-spacing: 0.1em;
764 text-transform: uppercase;
765 color: #8a8d99;
766 font-weight: 500;
767 margin-left: 2px;
768}
769
770.ac-nav {
771 display: flex;
772 flex-direction: column;
773 gap: 2px;
774}
775
776.ac-nav-item {
777 display: flex;
778 align-items: center;
779 gap: 10px;
780 padding: 8px 10px;
781 border-radius: 8px;
782 border: 0;
783 background: transparent;
784 color: #6b7080;
785 font-family: inherit;
786 font-size: 13.5px;
787 font-weight: 450;
788 cursor: pointer;
789 text-align: left;
790 width: 100%;
791 text-decoration: none;
792 transition: background 0.1s, color 0.1s;
793}
794
795.ac-nav-item:hover {
796 background: rgba(22,24,29,0.04);
797 color: #16181d;
798}
799
800.ac-nav-item[data-active="true"] {
801 background: rgba(22,24,29,0.05);
802 color: #16181d;
803 font-weight: 600;
804}
805
806.ac-nav-link {
807 display: flex;
808 align-items: center;
809 gap: 10px;
810 color: #6b7080;
811}
812
813.ac-nav-link:hover {
814 color: #16181d;
815}
816
817.ac-nav-icon {
818 width: 16px;
819 text-align: center;
820 font-size: 13px;
821 opacity: 0.8;
822 flex-shrink: 0;
823}
824
825.ac-nav-count {
826 margin-left: auto;
827 font-family: 'JetBrains Mono', monospace;
828 font-size: 11px;
829 color: #8a8d99;
830 font-weight: 500;
831}
832
833.ac-count-amber { color: #b45309; }
834
835.ac-sidebar-footer {
836 margin-top: auto;
837 padding: 14px 10px;
838 border-top: 1px solid rgba(22,24,29,0.07);
839}
840
841.ac-footer-status {
842 display: flex;
843 align-items: center;
844 gap: 8px;
845 font-size: 12.5px;
846 color: #6b7080;
847 margin-bottom: 6px;
848}
849
850.ac-footer-meta {
851 font-family: 'JetBrains Mono', monospace;
852 font-size: 11px;
853 color: #8a8d99;
854}
855
856/* ── Main ── */
857.ac-main {
858 flex: 1;
859 min-width: 0;
860 padding: 40px 44px 72px;
861 max-width: 1180px;
862}
863
864.ac-section { display: block; }
865.ac-hidden { display: none !important; }
866
867/* ── Page header ── */
868.ac-page-header {
869 display: flex;
870 align-items: flex-end;
871 justify-content: space-between;
872 gap: 20px;
873 flex-wrap: wrap;
874 margin-bottom: 32px;
875}
876
877.ac-eyebrow {
878 font-family: 'JetBrains Mono', monospace;
879 font-size: 11px;
880 letter-spacing: 0.12em;
881 text-transform: uppercase;
882 color: #8a8d99;
883 margin-bottom: 10px;
884}
885
886.ac-h1 {
887 font-family: 'Inter Tight', sans-serif;
888 font-size: 26px;
889 font-weight: 600;
890 letter-spacing: -0.025em;
891 line-height: 1.15;
892 margin: 0 0 6px;
893 color: #111318;
894}
895
896.ac-subtitle {
897 font-size: 13.5px;
898 color: #6b7080;
899 margin: 0;
900 max-width: 60ch;
901}
902
903.ac-h2 {
904 font-family: 'Inter Tight', sans-serif;
905 font-size: 15px;
906 font-weight: 600;
907 margin: 0;
908 color: #16181d;
909}
910
911.ac-header-actions {
912 display: flex;
913 gap: 10px;
914}
915
916/* ── Buttons ── */
917.ac-btn-primary {
918 padding: 8px 16px;
919 border-radius: 8px;
920 font-size: 13px;
921 font-weight: 500;
922 border: 0;
923 background: #16181d;
924 color: #fff;
925 cursor: pointer;
926 font-family: inherit;
927 white-space: nowrap;
928 transition: opacity 0.15s;
929}
930
931.ac-btn-primary:hover { opacity: 0.88; }
932.ac-btn-primary:disabled { background: #9a9da8; cursor: default; }
933
934.ac-btn-secondary {
935 padding: 8px 16px;
936 border-radius: 8px;
937 font-size: 13px;
938 font-weight: 500;
939 border: 1px solid rgba(22,24,29,0.12);
940 background: #ffffff;
941 color: #16181d;
942 cursor: pointer;
943 font-family: inherit;
944 white-space: nowrap;
945 transition: border-color 0.15s;
946}
947
948.ac-btn-secondary:hover { border-color: rgba(22,24,29,0.24); }
949.ac-btn-secondary:disabled { opacity: 0.6; cursor: default; }
950
951.ac-btn-secondary-link {
952 padding: 8px 16px;
953 border-radius: 8px;
954 font-size: 13px;
955 font-weight: 500;
956 border: 1px solid rgba(22,24,29,0.12);
957 background: #ffffff;
958 color: #16181d;
959 text-decoration: none;
960 display: inline-block;
961 white-space: nowrap;
962 transition: border-color 0.15s;
963}
964
965.ac-btn-secondary-link:hover { border-color: rgba(22,24,29,0.24); }
966
967.ac-btn-ghost {
968 padding: 6px 12px;
969 border-radius: 7px;
970 font-size: 12px;
971 font-weight: 500;
972 border: 1px solid rgba(22,24,29,0.12);
973 background: #ffffff;
974 color: #16181d;
975 cursor: pointer;
976 font-family: inherit;
977 flex-shrink: 0;
978 transition: border-color 0.15s;
979}
980
981.ac-btn-ghost:hover { border-color: rgba(22,24,29,0.24); }
982.ac-btn-ghost:disabled { opacity: 0.6; cursor: default; }
983
984.ac-link-btn {
985 border: 0;
986 background: none;
987 font-size: 12.5px;
988 color: #6b7080;
989 cursor: pointer;
990 font-family: inherit;
991 padding: 0;
992 transition: color 0.15s;
993}
994
995.ac-link-btn:hover { color: #16181d; }
996
997/* ── Status dots ── */
998.ac-dot {
999 width: 7px;
1000 height: 7px;
1001 border-radius: 50%;
1002 display: inline-block;
1003 flex-shrink: 0;
1004}
1005
1006.ac-dot-green { background: #1e7f5c; }
1007.ac-dot-amber { background: #b45309; }
1008.ac-dot-red { background: #b42318; }
1009.ac-dot-gray { background: #c4c6cf; }
1010.ac-pulse { animation: gcPulse 1.4s ease-in-out infinite; }
1011
1012/* ── KPI grid ── */
1013.ac-kpi-grid {
1014 display: grid;
1015 grid-template-columns: repeat(4, 1fr);
1016 gap: 1px;
1017 background: rgba(22,24,29,0.07);
1018 border: 1px solid rgba(22,24,29,0.07);
1019 border-radius: 12px;
1020 overflow: hidden;
1021 margin-bottom: 36px;
1022}
1023
1024.ac-kpi-cell {
1025 background: #ffffff;
1026 padding: 20px 22px;
1027}
1028
1029.ac-kpi-label {
1030 font-size: 12px;
1031 color: #8a8d99;
1032 margin-bottom: 8px;
1033}
1034
1035.ac-kpi-value {
1036 font-family: 'Inter Tight', sans-serif;
1037 font-size: 26px;
1038 font-weight: 600;
1039 letter-spacing: -0.02em;
1040 color: #16181d;
1041 font-variant-numeric: tabular-nums;
1042}
1043
1044.ac-kpi-delta {
1045 font-size: 12px;
1046 margin-top: 4px;
1047}
1048
1049.ac-delta-green { color: #1e7f5c; }
1050.ac-delta-amber { color: #b45309; }
1051.ac-delta-meta { color: #8a8d99; }
1052
1053/* ── Two column layout ── */
1054.ac-two-col {
1055 display: grid;
1056 grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
1057 gap: 40px;
1058 align-items: start;
1059}
1060
1061.ac-section-header {
1062 display: flex;
1063 align-items: baseline;
1064 justify-content: space-between;
1065 margin-bottom: 16px;
1066}
1067
1068/* ── Card ── */
1069.ac-card {
1070 border: 1px solid rgba(22,24,29,0.07);
1071 border-radius: 12px;
1072 background: #ffffff;
1073 overflow: hidden;
1074}
1075
1076/* ── Diagnostics rows ── */
1077.ac-diag-row {
1078 display: flex;
1079 align-items: flex-start;
1080 gap: 12px;
1081 padding: 14px 20px;
1082 border-bottom: 1px solid rgba(22,24,29,0.05);
1083}
1084
1085.ac-diag-row-last { border-bottom: 0; }
1086
1087.ac-diag-content {
1088 min-width: 0;
1089 flex: 1;
1090}
1091
1092.ac-diag-title-row {
1093 display: flex;
1094 align-items: baseline;
1095 gap: 10px;
1096 flex-wrap: wrap;
1097}
1098
1099.ac-diag-name {
1100 font-size: 13.5px;
1101 font-weight: 600;
1102 color: #16181d;
1103}
1104
1105.ac-diag-detail {
1106 font-size: 12.5px;
1107 color: #6b7080;
1108 margin: 2px 0 0;
1109}
1110
1111/* ── Ticket cards ── */
1112.ac-ticket-list-preview {
1113 display: flex;
1114 flex-direction: column;
1115 gap: 10px;
1116}
1117
1118.ac-ticket-card {
1119 text-align: left;
1120 border: 1px solid rgba(22,24,29,0.07);
1121 border-radius: 12px;
1122 background: #ffffff;
1123 padding: 14px 18px;
1124 cursor: pointer;
1125 font-family: inherit;
1126 width: 100%;
1127 transition: border-color 0.15s;
1128}
1129
1130.ac-ticket-card:hover { border-color: rgba(22,24,29,0.18); }
1131.ac-ticket-active { border-color: rgba(22,24,29,0.30) !important; }
1132
1133.ac-ticket-meta {
1134 display: flex;
1135 align-items: center;
1136 gap: 8px;
1137 margin-bottom: 4px;
1138}
1139
1140.ac-ticket-age {
1141 margin-left: auto;
1142 font-size: 11.5px;
1143 color: #8a8d99;
1144}
1145
1146.ac-ticket-title {
1147 font-size: 13.5px;
1148 font-weight: 500;
1149 color: #16181d;
1150}
1151
1152.ac-ticket-who {
1153 font-size: 12px;
1154 color: #6b7080;
1155 margin-top: 2px;
1156}
1157
1158/* ── Badges ── */
1159.ac-badge {
1160 font-size: 11px;
1161 font-weight: 600;
1162 padding: 1px 8px;
1163 border-radius: 9999px;
1164}
1165
1166.ac-badge-urgent { background: rgba(180,35,24,0.08); color: #b42318; }
1167.ac-badge-high { background: rgba(180,83,9,0.10); color: #b45309; }
1168.ac-badge-normal { background: rgba(22,24,29,0.06); color: #6b7080; }
1169
1170/* ── Mono helpers ── */
1171.ac-mono-small {
1172 font-family: 'JetBrains Mono', monospace;
1173 font-size: 11px;
1174 color: #8a8d99;
1175}
1176
1177.ac-mono-label {
1178 font-family: 'JetBrains Mono', monospace;
1179 font-size: 11px;
1180 letter-spacing: 0.1em;
1181 text-transform: uppercase;
1182 color: #8a8d99;
1183 font-weight: 500;
1184}
1185
1186/* ── Incident card ── */
1187.ac-incident-card {
1188 border: 1px solid rgba(180,83,9,0.25);
1189 border-radius: 12px;
1190 background: #ffffff;
1191 margin-bottom: 28px;
1192 overflow: hidden;
1193}
1194
1195.ac-incident-header {
1196 padding: 18px 22px;
1197 border-bottom: 1px solid rgba(22,24,29,0.06);
1198}
1199
1200.ac-incident-title-row {
1201 display: flex;
1202 align-items: center;
1203 gap: 10px;
1204 margin-bottom: 6px;
1205 flex-wrap: wrap;
1206}
1207
1208.ac-incident-title {
1209 font-size: 14px;
1210 font-weight: 600;
1211}
1212
1213.ac-incident-desc {
1214 font-size: 13px;
1215 color: #6b7080;
1216 margin: 0;
1217 max-width: 70ch;
1218}
1219
1220.ac-incident-body {
1221 padding: 16px 22px;
1222 display: flex;
1223 flex-direction: column;
1224 gap: 12px;
1225}
1226
1227.ac-diff-block {
1228 padding: 14px 16px;
1229 background: #fcfcfd;
1230 border: 1px solid rgba(22,24,29,0.07);
1231 border-radius: 10px;
1232 font-family: 'JetBrains Mono', monospace;
1233 font-size: 12px;
1234 line-height: 1.7;
1235 color: #16181d;
1236 overflow-x: auto;
1237}
1238
1239.ac-diff-comment { color: #8a8d99; white-space: pre; }
1240.ac-diff-remove { color: #b42318; white-space: pre; }
1241.ac-diff-add { color: #1e7f5c; white-space: pre; }
1242
1243.ac-incident-actions {
1244 display: flex;
1245 gap: 10px;
1246 align-items: center;
1247 flex-wrap: wrap;
1248}
1249
1250.ac-incident-note {
1251 font-size: 12px;
1252 color: #8a8d99;
1253}
1254
1255/* ── Table card ── */
1256.ac-table-card {
1257 border: 1px solid rgba(22,24,29,0.07);
1258 border-radius: 12px;
1259 background: #ffffff;
1260 overflow: hidden;
1261}
1262
1263.ac-table-header {
1264 padding: 10px 20px;
1265 border-bottom: 1px solid rgba(22,24,29,0.07);
1266 font-family: 'JetBrains Mono', monospace;
1267 font-size: 10.5px;
1268 letter-spacing: 0.1em;
1269 text-transform: uppercase;
1270 color: #8a8d99;
1271 display: grid;
1272 gap: 16px;
1273}
1274
1275.ac-table-row {
1276 padding: 13px 20px;
1277 border-bottom: 1px solid rgba(22,24,29,0.05);
1278 align-items: center;
1279 display: grid;
1280 gap: 16px;
1281}
1282
1283.ac-table-row-last { border-bottom: 0; }
1284
1285.ac-subsys-grid {
1286 grid-template-columns: minmax(160px, 1.2fr) 1fr 110px 130px;
1287}
1288
1289.ac-cust-grid {
1290 grid-template-columns: minmax(140px, 1.6fr) minmax(80px, 1fr) minmax(56px, 0.6fr) minmax(90px, 1fr) minmax(110px, 1.1fr);
1291}
1292
1293.ac-table-name {
1294 font-size: 13.5px;
1295 font-weight: 500;
1296 color: #16181d;
1297}
1298
1299.ac-table-signal {
1300 font-size: 12.5px;
1301 color: #6b7080;
1302}
1303
1304.ac-table-p99 {
1305 font-family: 'JetBrains Mono', monospace;
1306 font-size: 12px;
1307 color: #16181d;
1308 font-variant-numeric: tabular-nums;
1309}
1310
1311.ac-table-mono {
1312 font-family: 'JetBrains Mono', monospace;
1313 font-size: 12px;
1314 color: #16181d;
1315 font-variant-numeric: tabular-nums;
1316}
1317
1318.ac-status-cell {
1319 display: inline-flex;
1320 align-items: center;
1321 gap: 7px;
1322 font-size: 12.5px;
1323}
1324
1325.ac-status-green { color: #1e7f5c; }
1326.ac-status-amber { color: #b45309; }
1327
1328/* ── Flywheel grid ── */
1329.ac-flywheel-grid {
1330 display: grid;
1331 grid-template-columns: repeat(3, 1fr);
1332 gap: 1px;
1333 background: rgba(22,24,29,0.07);
1334 border: 1px solid rgba(22,24,29,0.07);
1335 border-radius: 12px;
1336 overflow: hidden;
1337}
1338
1339.ac-flywheel-cell {
1340 background: #ffffff;
1341 padding: 20px 22px;
1342}
1343
1344.ac-flywheel-value {
1345 font-family: 'Inter Tight', sans-serif;
1346 font-size: 24px;
1347 font-weight: 600;
1348 font-variant-numeric: tabular-nums;
1349 color: #16181d;
1350}
1351
1352/* ── Support grid ── */
1353.ac-support-grid {
1354 display: grid;
1355 grid-template-columns: minmax(280px, 1fr) minmax(0, 1.6fr);
1356 gap: 32px;
1357 align-items: start;
1358}
1359
1360.ac-ticket-list {
1361 display: flex;
1362 flex-direction: column;
1363 gap: 10px;
1364}
1365
1366/* ── Ticket detail ── */
1367.ac-ticket-detail {
1368 border: 1px solid rgba(22,24,29,0.07);
1369 border-radius: 12px;
1370 background: #ffffff;
1371 overflow: hidden;
1372 min-height: 400px;
1373}
1374
1375.ac-td-header {
1376 padding: 20px 24px;
1377 border-bottom: 1px solid rgba(22,24,29,0.06);
1378}
1379
1380.ac-td-meta {
1381 display: flex;
1382 align-items: center;
1383 gap: 10px;
1384 margin-bottom: 6px;
1385 flex-wrap: wrap;
1386}
1387
1388.ac-td-who {
1389 font-size: 12px;
1390 color: #8a8d99;
1391}
1392
1393.ac-td-title {
1394 font-family: 'Inter Tight', sans-serif;
1395 font-size: 18px;
1396 font-weight: 600;
1397 letter-spacing: -0.015em;
1398 margin: 0;
1399 color: #16181d;
1400}
1401
1402.ac-td-body {
1403 padding: 18px 24px;
1404 display: flex;
1405 flex-direction: column;
1406 gap: 16px;
1407}
1408
1409.ac-td-report {
1410 font-size: 13.5px;
1411 color: #16181d;
1412 margin: 0;
1413 line-height: 1.6;
1414}
1415
1416.ac-diagnosis-card {
1417 border: 1px solid rgba(67,83,201,0.18);
1418 background: rgba(67,83,201,0.03);
1419 border-radius: 10px;
1420 padding: 14px 16px;
1421}
1422
1423.ac-diagnosis-label {
1424 font-family: 'JetBrains Mono', monospace;
1425 font-size: 11px;
1426 letter-spacing: 0.1em;
1427 text-transform: uppercase;
1428 color: #4353c9;
1429 font-weight: 500;
1430 margin-bottom: 8px;
1431}
1432
1433.ac-diagnosis-text {
1434 font-size: 13px;
1435 color: #16181d;
1436 margin: 0 0 10px;
1437 line-height: 1.6;
1438}
1439
1440.ac-evidence {
1441 font-family: 'JetBrains Mono', monospace;
1442 font-size: 11.5px;
1443 color: #6b7080;
1444}
1445
1446.ac-td-actions {
1447 display: flex;
1448 gap: 10px;
1449 flex-wrap: wrap;
1450}
1451
1452/* ── Customers / billing ── */
1453.ac-org-cell {
1454 display: flex;
1455 align-items: center;
1456 gap: 10px;
1457 min-width: 0;
1458}
1459
1460.ac-org-avatar {
1461 width: 24px;
1462 height: 24px;
1463 border-radius: 7px;
1464 color: #fff;
1465 font-size: 10px;
1466 font-weight: 600;
1467 display: inline-flex;
1468 align-items: center;
1469 justify-content: center;
1470 flex-shrink: 0;
1471}
1472
1473.ac-bill-cell {
1474 display: inline-flex;
1475 align-items: center;
1476 gap: 7px;
1477 font-size: 12px;
1478 font-weight: 500;
1479}
1480
1481.ac-bill-exempt { color: #6b7080; }
1482.ac-bill-current { color: #1e7f5c; }
1483.ac-bill-pastdue { color: #b45309; }
1484`;