Swap stylesheet to /dist/app.css. Sidebar markup ported to wd-sidebar / wd-nav-item / wd-sidebar-* primitives (active state = leading accent dot). Logout button promoted to wd-btn--ghost--sm--icon. Hero (portrait, wordmark, tagline) and the 10 illustrated cards keep their bespoke design — they're a brand moment. Hardcoded oklch values in the inline style replaced with var(--accent-bright), var(--signal-bad), var(--bg-base), var(--accent-border), var(--overlay), etc. wherever the brand palette already provides them. All JS ids preserved (assetCount, projectCount, recorderCount, containerCount, nodeCount, jobCount, ingestCount, captureStatus, tokenBurn, userAvatar, userName, userRole, logoutBtn, systemBuild). loadStats() poll cycle unchanged.
563 lines
29 KiB
HTML
563 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
<title>Home — Z-AMPP</title>
|
|
<link rel="stylesheet" href="/dist/app.css">
|
|
<style>
|
|
/* Page-only layout. Sidebar + all chrome is from /dist/app.css primitives.
|
|
Hero / cards / SVG art are bespoke to the home landing page. */
|
|
body { margin: 0; }
|
|
.wd-shell { display: flex; min-height: 100vh; }
|
|
|
|
.home-main {
|
|
flex: 1;
|
|
overflow: auto;
|
|
position: relative;
|
|
background:
|
|
radial-gradient(ellipse 70% 50% at 50% 0%, oklch(35% 0.16 266 / 0.55), transparent 65%),
|
|
radial-gradient(ellipse 80% 60% at 30% 90%, oklch(40% 0.20 266 / 0.45), transparent 70%),
|
|
radial-gradient(ellipse 60% 50% at 80% 80%, oklch(45% 0.18 240 / 0.30), transparent 65%),
|
|
linear-gradient(135deg, oklch(20% 0.05 260), var(--bg-base) 100%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.home-stage {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: safe center;
|
|
padding: 56px 32px 32px;
|
|
gap: 36px;
|
|
}
|
|
|
|
.home-brandmark {
|
|
display: flex; flex-direction: column; align-items: center; gap: 14px;
|
|
}
|
|
.home-portrait {
|
|
width: 120px; height: 120px;
|
|
border-radius: 50%;
|
|
overflow: hidden; position: relative;
|
|
background:
|
|
radial-gradient(ellipse at 40% 30%, oklch(35% 0.18 266 / 0.4), transparent 65%),
|
|
linear-gradient(135deg, var(--bg-surface), var(--bg-deep));
|
|
border: 2px solid var(--accent-border);
|
|
box-shadow:
|
|
0 20px 50px -10px oklch(45% 0.20 266 / 0.4),
|
|
inset 0 1px 0 oklch(100% 0 0 / 0.08);
|
|
}
|
|
.home-portrait img {
|
|
position: absolute; inset: 0;
|
|
width: 100%; height: 100%;
|
|
object-fit: cover;
|
|
object-position: 50% 30%;
|
|
}
|
|
.home-portrait-dot {
|
|
position: absolute; bottom: 4px; right: 4px;
|
|
width: 18px; height: 18px;
|
|
background: var(--signal-bad);
|
|
border: 3px solid var(--bg-panel);
|
|
border-radius: 50%;
|
|
box-shadow: 0 0 12px var(--signal-bad);
|
|
animation: live-pulse 1.6s ease-in-out infinite;
|
|
}
|
|
@keyframes live-pulse {
|
|
0%, 100% { opacity: 0.85; transform: scale(1); }
|
|
50% { opacity: 1; transform: scale(1.1); }
|
|
}
|
|
.home-wordmark-svg {
|
|
width: min(380px, 70vw);
|
|
height: auto;
|
|
filter: drop-shadow(0 16px 30px oklch(35% 0.18 266 / 0.45));
|
|
margin-bottom: -10px;
|
|
}
|
|
|
|
.home-tagline {
|
|
display: flex; align-items: center; gap: 12px;
|
|
font: 500 17px/1 var(--font);
|
|
color: oklch(75% 0.10 240);
|
|
letter-spacing: 0.005em;
|
|
}
|
|
.home-tagline::before {
|
|
content: ''; width: 3px; height: 18px;
|
|
background: var(--accent-bright);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.home-cards {
|
|
display: flex;
|
|
gap: 18px;
|
|
width: 100%;
|
|
max-width: 1680px;
|
|
padding: 0 24px;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
.home-card {
|
|
flex: 1 1 0;
|
|
min-width: 200px;
|
|
max-width: 220px;
|
|
background: oklch(12% 0.020 250 / 0.85);
|
|
border: 1px solid var(--border-faint);
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
text-decoration: none;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
flex-direction: column;
|
|
cursor: pointer;
|
|
backdrop-filter: blur(6px);
|
|
transition: transform var(--dur-slide) var(--ease-out-quart),
|
|
border-color var(--dur-slide) var(--ease-out-quart),
|
|
box-shadow var(--dur-slide) var(--ease-out-quart);
|
|
}
|
|
.home-card:hover {
|
|
transform: translateY(-4px);
|
|
border-color: var(--accent-border);
|
|
box-shadow: 0 24px 50px -16px oklch(45% 0.20 266 / 0.4);
|
|
}
|
|
.home-card-title {
|
|
padding: 12px 16px 10px;
|
|
font: 500 15px/1.2 var(--font);
|
|
letter-spacing: 0.005em;
|
|
color: var(--text-primary);
|
|
}
|
|
.home-card-preview {
|
|
position: relative;
|
|
aspect-ratio: 16/10;
|
|
background: linear-gradient(135deg, var(--bg-surface), var(--bg-base));
|
|
overflow: hidden;
|
|
border-top: 1px solid var(--border-faint);
|
|
border-bottom: 1px solid var(--border-faint);
|
|
}
|
|
.home-card-preview svg.preview-art {
|
|
position: absolute; inset: 0;
|
|
width: 100%; height: 100%;
|
|
}
|
|
.home-card-stats {
|
|
position: absolute; bottom: 8px; left: 10px;
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
background: var(--overlay);
|
|
backdrop-filter: blur(6px);
|
|
padding: 3px 8px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border-faint);
|
|
font: 400 10px/1 var(--font-mono);
|
|
letter-spacing: 0.04em;
|
|
color: var(--text-secondary);
|
|
}
|
|
.home-card-stats b { color: var(--text-primary); font-weight: 600; }
|
|
.home-card-stats-dot {
|
|
width: 6px; height: 6px; border-radius: 50%;
|
|
background: var(--signal-good); box-shadow: 0 0 6px var(--signal-good);
|
|
}
|
|
.home-card-desc {
|
|
padding: 12px 16px 16px;
|
|
font: 400 11.5px/1.5 var(--font);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.home-footer {
|
|
padding: 24px;
|
|
display: flex; align-items: center; gap: 10px;
|
|
justify-content: center;
|
|
color: var(--text-tertiary);
|
|
font: 400 11px/1 var(--font);
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
.home-footer-mark {
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
font-weight: 700;
|
|
color: var(--text-secondary);
|
|
}
|
|
.home-footer-mark-dot {
|
|
width: 6px; height: 6px; border-radius: 50%;
|
|
background: var(--accent-bright);
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.home-stage { padding: 32px 16px; gap: 24px; }
|
|
.home-portrait { width: 96px; height: 96px; }
|
|
.home-cards { gap: 12px; padding: 0 12px; }
|
|
.home-card { min-width: 150px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="wd-shell">
|
|
<nav class="wd-sidebar" aria-label="Main navigation">
|
|
<div class="wd-sidebar-header">
|
|
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" style="width:18px;height:18px;">
|
|
<span class="wd-sidebar-brand">Z-AMPP</span>
|
|
</div>
|
|
<div class="wd-sidebar-nav">
|
|
<a href="home.html" class="wd-nav-item is-active">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 7l6-5 6 5v7a1 1 0 0 1-1 1h-3v-5H6v5H3a1 1 0 0 1-1-1z"/></svg>
|
|
Home
|
|
</a>
|
|
<a href="index.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg>
|
|
Library
|
|
</a>
|
|
<a href="projects.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 4a1 1 0 0 1 1-1h4l2 2h5a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/></svg>
|
|
Projects
|
|
</a>
|
|
<a href="upload.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 11V3M5 6l3-3 3 3"/><path d="M2 13h12"/></svg>
|
|
Ingest
|
|
</a>
|
|
<a href="recorders.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="4" width="10" height="8" rx="1"/><path d="M11 7l4-2v6l-4-2"/></svg>
|
|
Recorders
|
|
</a>
|
|
<a href="capture.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3"/><circle cx="8" cy="8" r="6.5"/></svg>
|
|
Capture
|
|
</a>
|
|
<a href="jobs.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h8M2 12h5"/></svg>
|
|
Jobs
|
|
</a>
|
|
<a href="editor.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 13l1.5-3.5L11 2l3 3-7.5 7.5L3 14zM10 3l3 3"/></svg>
|
|
Editor
|
|
</a>
|
|
<div class="wd-sidebar-section">Admin</div>
|
|
<a href="users.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="5" r="2.5"/><path d="M1 13c0-2.8 2.2-5 5-5s5 2.2 5 5"/><circle cx="12" cy="5" r="2"/><path d="M15 12c0-1.9-1.3-3.5-3-4"/></svg>
|
|
Users
|
|
</a>
|
|
<a href="tokens.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="10" r="3.5"/><path d="M8.7 7.3L13 3M11.5 3.5l1.5 1.5M13.5 2.5l1 1"/></svg>
|
|
Tokens
|
|
</a>
|
|
<a href="containers.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="5" width="14" height="4" rx="1"/><rect x="1" y="10" width="14" height="4" rx="1"/><path d="M4 7h1M4 12h1"/></svg>
|
|
Containers
|
|
</a>
|
|
<a href="cluster.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2"/><circle cx="2" cy="3" r="1.5"/><circle cx="14" cy="3" r="1.5"/><circle cx="2" cy="13" r="1.5"/><circle cx="14" cy="13" r="1.5"/><path d="M3.1 4.1L6.5 6.5M12.9 4.1L9.5 6.5M3.1 11.9L6.5 9.5M12.9 11.9L9.5 9.5"/></svg>
|
|
Cluster
|
|
</a>
|
|
<a href="settings.html" class="wd-nav-item">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M2.9 2.9l1.1 1.1M12 12l1.1 1.1M2.9 13.1L4 12M12 4l1.1-1.1"/></svg>
|
|
Settings
|
|
</a>
|
|
</div>
|
|
<div class="wd-sidebar-footer">
|
|
<div class="wd-sidebar-user">
|
|
<div class="wd-sidebar-user-avatar" id="userAvatar">?</div>
|
|
<div class="wd-sidebar-user-info">
|
|
<div class="wd-sidebar-user-name" id="userName">—</div>
|
|
<div class="wd-sidebar-user-role" id="userRole"></div>
|
|
</div>
|
|
<button class="wd-btn wd-btn--ghost wd-btn--sm wd-btn--icon wd-sidebar-user-logout" id="logoutBtn" title="Sign out">
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12"><path d="M10 8H3M6 5l-3 3 3 3"/><path d="M7 3h5a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H7"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="home-main">
|
|
<div class="home-stage">
|
|
<div class="home-brandmark">
|
|
<div class="home-portrait">
|
|
<img src="img/ampp-safe.png?v=hardhat3" alt="Zac in hardhat">
|
|
<span class="home-portrait-dot" title="On duty"></span>
|
|
</div>
|
|
<svg class="home-wordmark-svg" viewBox="0 0 540 132" xmlns="http://www.w3.org/2000/svg" aria-label="Z-AMPP">
|
|
<defs>
|
|
<linearGradient id="zwm" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0" stop-color="oklch(82% 0.14 220)"/>
|
|
<stop offset="0.55" stop-color="oklch(58% 0.18 245)"/>
|
|
<stop offset="1" stop-color="oklch(32% 0.16 265)"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<text x="0" y="108" font-family="Inter, system-ui, sans-serif" font-weight="900" font-size="132" letter-spacing="-8" fill="url(#zwm)">Z-AMPP</text>
|
|
</svg>
|
|
</div>
|
|
|
|
<div class="home-tagline">Please select an option below to get started</div>
|
|
|
|
<div class="home-cards">
|
|
|
|
<a href="index.html" class="home-card">
|
|
<div class="home-card-title">Library</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<g fill="oklch(25% 0.05 260)" stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5">
|
|
<rect x="12" y="14" width="40" height="28" rx="2"/>
|
|
<rect x="58" y="14" width="40" height="28" rx="2"/>
|
|
<rect x="104" y="14" width="40" height="28" rx="2"/>
|
|
<rect x="150" y="14" width="40" height="28" rx="2"/>
|
|
<rect x="12" y="48" width="40" height="28" rx="2"/>
|
|
<rect x="58" y="48" width="40" height="28" rx="2"/>
|
|
<rect x="104" y="48" width="40" height="28" rx="2"/>
|
|
<rect x="150" y="48" width="40" height="28" rx="2"/>
|
|
<rect x="12" y="82" width="40" height="28" rx="2"/>
|
|
<rect x="58" y="82" width="40" height="28" rx="2"/>
|
|
<rect x="104" y="82" width="40" height="28" rx="2"/>
|
|
<rect x="150" y="82" width="40" height="28" rx="2"/>
|
|
</g>
|
|
<g fill="oklch(45% 0.20 266 / 0.8)"><circle cx="32" cy="28" r="3"/><circle cx="124" cy="62" r="3"/><circle cx="170" cy="96" r="3"/></g>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="assetCount">--</b> assets</span>
|
|
</div>
|
|
<div class="home-card-desc">Browse, organize, and preview every asset across all projects.</div>
|
|
</a>
|
|
|
|
<a href="projects.html" class="home-card">
|
|
<div class="home-card-title">Projects</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<g fill="oklch(20% 0.04 260)" stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5">
|
|
<path d="M12 24 L12 100 L188 100 L188 36 L92 36 L80 24 Z" />
|
|
<path d="M28 56 L184 56" stroke-dasharray="2,3"/>
|
|
<path d="M28 76 L184 76" stroke-dasharray="2,3"/>
|
|
</g>
|
|
<g fill="oklch(45% 0.20 266 / 0.6)">
|
|
<rect x="32" y="46" width="6" height="6" rx="1"/>
|
|
<rect x="32" y="66" width="6" height="6" rx="1"/>
|
|
<rect x="32" y="86" width="6" height="6" rx="1"/>
|
|
</g>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="projectCount">--</b> projects</span>
|
|
</div>
|
|
<div class="home-card-desc">Create projects, organize bins, and manage who owns what footage.</div>
|
|
</a>
|
|
|
|
<a href="upload.html" class="home-card">
|
|
<div class="home-card-title">Ingest</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<g stroke="oklch(45% 0.20 266 / 0.6)" stroke-width="2" fill="none">
|
|
<path d="M100 80 V 30 M85 45 L 100 30 L 115 45" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<line x1="30" y1="100" x2="170" y2="100" stroke-dasharray="3,3" stroke-width="1"/>
|
|
</g>
|
|
<rect x="40" y="92" width="120" height="6" rx="3" fill="oklch(25% 0.05 260)"/>
|
|
<rect x="40" y="92" width="68" height="6" rx="3" fill="oklch(55% 0.20 266 / 0.9)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="ingestCount">--</b> processing</span>
|
|
</div>
|
|
<div class="home-card-desc">Upload finished files. MOV, MP4, MXF, ProRes, drop them, we proxy them.</div>
|
|
</a>
|
|
|
|
<a href="recorders.html" class="home-card">
|
|
<div class="home-card-title">Recorders</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<rect x="20" y="22" width="120" height="80" rx="4" fill="oklch(8% 0.01 250)" stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5"/>
|
|
<g opacity="0.5"><path d="M30 50 q15 -12 30 0 t30 0 t30 0 t30 0" stroke="oklch(55% 0.20 266)" stroke-width="1" fill="none"/><path d="M30 70 q15 -8 30 0 t30 0 t30 0 t30 0" stroke="oklch(55% 0.20 266)" stroke-width="1" fill="none"/></g>
|
|
<path d="M140 62 L182 42 L182 82 Z" fill="oklch(20% 0.04 260)" stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5"/>
|
|
<circle cx="160" cy="62" r="6" fill="oklch(62% 0.22 25)"><animate attributeName="opacity" values="1;0.5;1" dur="1.6s" repeatCount="indefinite"/></circle>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot" style="background:var(--signal-bad);box-shadow:0 0 6px var(--signal-bad)"></span><b id="recorderCount">--</b> live</span>
|
|
</div>
|
|
<div class="home-card-desc">Pull SRT, RTMP, and SDI feeds straight to ProRes with a live HLS preview.</div>
|
|
</a>
|
|
|
|
<a href="capture.html" class="home-card">
|
|
<div class="home-card-title">Capture</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<circle cx="100" cy="62" r="34" fill="none" stroke="oklch(45% 0.10 266 / 0.3)" stroke-width="0.5"/>
|
|
<circle cx="100" cy="62" r="22" fill="none" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.5"/>
|
|
<circle cx="100" cy="62" r="12" fill="oklch(20% 0.04 260)" stroke="oklch(55% 0.20 266 / 0.7)" stroke-width="1"/>
|
|
<circle cx="100" cy="62" r="4" fill="oklch(70% 0.18 266)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="captureStatus">Idle</b></span>
|
|
</div>
|
|
<div class="home-card-desc">DeckLink SDI capture with manual scene control and per-device routing.</div>
|
|
</a>
|
|
|
|
<a href="editor.html" class="home-card">
|
|
<div class="home-card-title">Editor</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<rect x="14" y="14" width="172" height="44" rx="3" fill="oklch(8% 0.01 250)" stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5"/>
|
|
<g stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5">
|
|
<line x1="14" y1="74" x2="186" y2="74"/>
|
|
<line x1="14" y1="92" x2="186" y2="92"/>
|
|
<line x1="14" y1="110" x2="186" y2="110"/>
|
|
</g>
|
|
<rect x="22" y="68" width="60" height="12" rx="2" fill="oklch(55% 0.20 266 / 0.8)"/>
|
|
<rect x="90" y="68" width="38" height="12" rx="2" fill="oklch(55% 0.20 266 / 0.5)"/>
|
|
<rect x="40" y="86" width="90" height="12" rx="2" fill="oklch(70% 0.18 266 / 0.6)"/>
|
|
<rect x="30" y="104" width="80" height="12" rx="2" fill="oklch(55% 0.20 266 / 0.7)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span>Web editor ↗</span>
|
|
</div>
|
|
<div class="home-card-desc">Pull a clip from the library into the in-browser editor. Trim, cut, export.</div>
|
|
</a>
|
|
|
|
<a href="jobs.html" class="home-card">
|
|
<div class="home-card-title">Jobs</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<g stroke="oklch(45% 0.10 266 / 0.4)" stroke-width="0.5">
|
|
<line x1="14" y1="28" x2="186" y2="28"/>
|
|
<line x1="14" y1="46" x2="186" y2="46"/>
|
|
<line x1="14" y1="64" x2="186" y2="64"/>
|
|
<line x1="14" y1="82" x2="186" y2="82"/>
|
|
<line x1="14" y1="100" x2="186" y2="100"/>
|
|
</g>
|
|
<rect x="22" y="20" width="38" height="6" rx="3" fill="oklch(45% 0.16 145 / 0.8)"/>
|
|
<rect x="22" y="38" width="80" height="6" rx="3" fill="oklch(45% 0.16 145 / 0.6)"/>
|
|
<rect x="22" y="56" width="120" height="6" rx="3" fill="oklch(70% 0.18 80 / 0.8)"/>
|
|
<rect x="22" y="74" width="60" height="6" rx="3" fill="oklch(45% 0.16 145 / 0.7)"/>
|
|
<rect x="22" y="92" width="100" height="6" rx="3" fill="oklch(45% 0.16 145 / 0.5)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="jobCount">--</b> active</span>
|
|
</div>
|
|
<div class="home-card-desc">Track proxy generation, thumbnails, and AMPP folder sync as they run.</div>
|
|
</a>
|
|
|
|
<a href="containers.html" class="home-card">
|
|
<div class="home-card-title">Containers</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<g fill="oklch(20% 0.04 260)" stroke="oklch(45% 0.10 266 / 0.45)" stroke-width="0.6">
|
|
<rect x="20" y="18" width="160" height="22" rx="3"/>
|
|
<rect x="20" y="46" width="160" height="22" rx="3"/>
|
|
<rect x="20" y="74" width="160" height="22" rx="3"/>
|
|
<rect x="20" y="102" width="160" height="16" rx="3"/>
|
|
</g>
|
|
<circle cx="34" cy="29" r="3.5" fill="oklch(62% 0.22 145)"/>
|
|
<circle cx="34" cy="57" r="3.5" fill="oklch(62% 0.22 145)"/>
|
|
<circle cx="34" cy="85" r="3.5" fill="oklch(62% 0.22 145)"/>
|
|
<circle cx="34" cy="110" r="3.5" fill="oklch(62% 0.22 25)"/>
|
|
<rect x="46" y="25" width="60" height="5" rx="2" fill="oklch(50% 0.12 266 / 0.7)"/>
|
|
<rect x="46" y="53" width="80" height="5" rx="2" fill="oklch(50% 0.12 266 / 0.7)"/>
|
|
<rect x="46" y="81" width="50" height="5" rx="2" fill="oklch(50% 0.12 266 / 0.7)"/>
|
|
<rect x="46" y="107" width="70" height="4" rx="2" fill="oklch(40% 0.10 266 / 0.5)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="containerCount">--</b> running</span>
|
|
</div>
|
|
<div class="home-card-desc">Manage Docker Compose services, start, stop, and restart containers.</div>
|
|
</a>
|
|
|
|
<a href="cluster.html" class="home-card">
|
|
<div class="home-card-title">Cluster</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<circle cx="100" cy="62" r="10" fill="oklch(20% 0.04 260)" stroke="oklch(55% 0.20 266 / 0.7)" stroke-width="1"/>
|
|
<circle cx="100" cy="62" r="4" fill="oklch(70% 0.18 266)"/>
|
|
<line x1="100" y1="62" x2="36" y2="22" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
|
|
<line x1="100" y1="62" x2="164" y2="22" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
|
|
<line x1="100" y1="62" x2="24" y2="80" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
|
|
<line x1="100" y1="62" x2="176" y2="80" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
|
|
<line x1="100" y1="62" x2="100" y2="108" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
|
|
<circle cx="36" cy="22" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
|
|
<circle cx="164" cy="22" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
|
|
<circle cx="24" cy="80" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
|
|
<circle cx="176" cy="80" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
|
|
<circle cx="100" cy="108" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
|
|
<circle cx="36" cy="22" r="2.5" fill="oklch(62% 0.22 145)"/>
|
|
<circle cx="164" cy="22" r="2.5" fill="oklch(62% 0.22 145)"/>
|
|
<circle cx="24" cy="80" r="2.5" fill="oklch(62% 0.22 145)"/>
|
|
<circle cx="176" cy="80" r="2.5" fill="oklch(55% 0.18 80)"/>
|
|
<circle cx="100" cy="108" r="2.5" fill="oklch(45% 0.10 266 / 0.4)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="nodeCount">--</b> nodes</span>
|
|
</div>
|
|
<div class="home-card-desc">Multi-server cluster registry. Monitor, federate, and scale Z-AMPP nodes.</div>
|
|
</a>
|
|
|
|
<a href="tokens.html" class="home-card" title="Just kidding">
|
|
<div class="home-card-title">Tokens</div>
|
|
<div class="home-card-preview">
|
|
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
|
|
<g stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.5"><line x1="14" y1="100" x2="186" y2="100"/><line x1="14" y1="80" x2="186" y2="80" stroke-dasharray="2,3"/><line x1="14" y1="60" x2="186" y2="60" stroke-dasharray="2,3"/></g>
|
|
<polyline points="14,90 38,72 62,80 86,55 110,62 134,40 158,48 186,28" fill="none" stroke="oklch(70% 0.18 200)" stroke-width="2"/>
|
|
<polyline points="14,95 38,86 62,90 86,76 110,80 134,68 158,72 186,58" fill="none" stroke="oklch(62% 0.15 145)" stroke-width="2"/>
|
|
<circle cx="186" cy="28" r="3" fill="oklch(62% 0.22 25)"/>
|
|
</svg>
|
|
<span class="home-card-stats"><span class="home-card-stats-dot" style="background:var(--signal-bad);box-shadow:0 0 6px var(--signal-bad)"></span><b id="tokenBurn">—</b> burning</span>
|
|
</div>
|
|
<div class="home-card-desc">Token-metered pricing parody. Click for a giggle. (You actually pay $0.)</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="home-footer">
|
|
<span class="home-footer-mark"><span class="home-footer-mark-dot"></span>Wild Dragon</span>
|
|
<span>·</span>
|
|
<span id="systemBuild">Z-AMPP Operator Console</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="js/api.js?v=6"></script>
|
|
<script src="js/topbar-strip.js?v=1"></script>
|
|
<script>
|
|
async function loadStats() {
|
|
const setText = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
|
try {
|
|
const [aRes, pRes, rRes, jRes, cRes, nRes] = await Promise.allSettled([
|
|
fetch('/api/v1/assets?limit=1', { credentials: 'include' }),
|
|
fetch('/api/v1/projects', { credentials: 'include' }),
|
|
fetch('/api/v1/recorders', { credentials: 'include' }),
|
|
fetch('/api/v1/jobs?status=active', { credentials: 'include' }),
|
|
fetch('/api/v1/system/containers', { credentials: 'include' }),
|
|
fetch('/api/v1/cluster', { credentials: 'include' }),
|
|
]);
|
|
|
|
if (aRes.status === 'fulfilled' && aRes.value.ok) {
|
|
const j = await aRes.value.json();
|
|
setText('assetCount', j.total ?? (j.assets?.length ?? '--'));
|
|
}
|
|
if (pRes.status === 'fulfilled' && pRes.value.ok) {
|
|
const j = await pRes.value.json();
|
|
const arr = Array.isArray(j) ? j : (j.projects ?? []);
|
|
setText('projectCount', arr.length);
|
|
}
|
|
if (rRes.status === 'fulfilled' && rRes.value.ok) {
|
|
const j = await rRes.value.json();
|
|
const arr = Array.isArray(j) ? j : [];
|
|
const recording = arr.filter(r => r.status === 'recording').length;
|
|
setText('recorderCount', recording);
|
|
}
|
|
if (jRes.status === 'fulfilled' && jRes.value.ok) {
|
|
const j = await jRes.value.json();
|
|
const arr = Array.isArray(j) ? j : (j.jobs ?? []);
|
|
setText('jobCount', arr.length);
|
|
setText('ingestCount', arr.filter(x => (x.type || '').toLowerCase().includes('proxy')).length || arr.length);
|
|
}
|
|
if (cRes.status === 'fulfilled' && cRes.value.ok) {
|
|
const j = await cRes.value.json();
|
|
const arr = Array.isArray(j) ? j : (j.containers ?? []);
|
|
const running = arr.filter(c => c.state === 'running').length;
|
|
setText('containerCount', running);
|
|
}
|
|
if (nRes.status === 'fulfilled' && nRes.value.ok) {
|
|
const j = await nRes.value.json();
|
|
const arr = Array.isArray(j) ? j : (j.nodes ?? []);
|
|
const online = arr.filter(n => n.online).length;
|
|
setText('nodeCount', arr.length > 0 ? online + '/' + arr.length : '--');
|
|
}
|
|
const tb = document.getElementById('tokenBurn'); if (tb) { tb.textContent = (14000 + Math.round(Math.random() * 8000)).toLocaleString(); }
|
|
} catch (_) { /* leave dashes */ }
|
|
}
|
|
loadStats();
|
|
setInterval(loadStats, 15000);
|
|
</script>
|
|
<script src="js/auth-guard.js"></script>
|
|
</body>
|
|
</html>
|