fix: remove Google Fonts, fix editor link to :47435, fix page titles
- Remove @import Google Fonts from common.css (was blocking CSS on LAN) - Update Editor nav link on all pages to dynamically resolve to :47435 (OpenReel SPA) using inline script so it works on any hostname - Fix page titles from Wild Dragon -> Z-AMPP across all pages - Resolver: <a href="#" id="editor-nav-link"> + IIFE sets href at load
This commit is contained in:
parent
1f31d1037d
commit
e8e26dd4d8
13 changed files with 1399 additions and 647 deletions
352
services/web-ui/public/api-tokens.html
Normal file
352
services/web-ui/public/api-tokens.html
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tokens — Wild Dragon</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
.token-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
padding: var(--sp-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
.token-card-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--r-md);
|
||||
background: var(--accent-subtle);
|
||||
border: 1px solid var(--accent-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.token-card-body { flex: 1; min-width: 0; }
|
||||
.token-card-name { font-weight: 500; font-size: var(--text-sm); }
|
||||
.token-card-meta { font-size: var(--text-xs); color: var(--text-tertiary); margin-top: 2px; }
|
||||
.token-prefix {
|
||||
font-family: 'SF Mono', 'Consolas', monospace;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-raised);
|
||||
padding: 1px 6px;
|
||||
border-radius: var(--r-sm);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.tokens-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-2);
|
||||
max-width: 680px;
|
||||
}
|
||||
.new-token-banner {
|
||||
background: var(--status-green-bg);
|
||||
border: 1px solid oklch(68% 0.18 148 / 0.30);
|
||||
border-radius: var(--r-lg);
|
||||
padding: var(--sp-4) var(--sp-5);
|
||||
margin-bottom: var(--sp-5);
|
||||
max-width: 680px;
|
||||
}
|
||||
.new-token-banner-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
color: var(--status-green);
|
||||
margin-bottom: var(--sp-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
.new-token-banner-warning {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
margin-top: var(--sp-3);
|
||||
}
|
||||
.copy-btn {
|
||||
margin-top: var(--sp-3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar" aria-label="Main navigation">
|
||||
<div class="sidebar-brand">
|
||||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="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="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="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="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="edit.html" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<span class="page-title">API Tokens</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="btn btn-primary btn-sm" onclick="openNewTokenPanel()">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v12M2 8h12"/></svg>
|
||||
New token
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-content">
|
||||
<div style="max-width:680px;margin-bottom:var(--sp-5);">
|
||||
<p class="text-sm text-secondary" style="line-height:1.7;">
|
||||
API tokens let scripts and integrations authenticate as you without using your password.
|
||||
Tokens are shown once at creation — store them securely.
|
||||
Use <code style="font-family:monospace;font-size:11px;background:var(--bg-surface);padding:1px 5px;border-radius:3px;border:1px solid var(--border)">Authorization: Bearer <token></code> in your requests.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- New token reveal (shown after creation) -->
|
||||
<div id="newTokenBanner" style="display:none;" class="new-token-banner">
|
||||
<div class="new-token-banner-title">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><circle cx="8" cy="8" r="6.5"/><path d="M5 8l2 2 4-4"/></svg>
|
||||
Token created
|
||||
</div>
|
||||
<div class="token-reveal" id="newTokenValue"></div>
|
||||
<div class="new-token-banner-warning">
|
||||
⚠ This is the only time this token will be shown. Copy it now.
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm copy-btn" onclick="copyToken()">Copy to clipboard</button>
|
||||
</div>
|
||||
|
||||
<!-- Token list -->
|
||||
<div class="tokens-list" id="tokensList">
|
||||
<div style="color:var(--text-tertiary);font-size:var(--text-sm)">Loading…</div>
|
||||
</div>
|
||||
|
||||
<div id="tokensEmpty" class="empty-state" style="display:none;padding:var(--sp-12) 0;">
|
||||
<div class="empty-state-icon">
|
||||
<svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="15" cy="25" r="8"/><path d="M21 19l10-10M28 10l3 3M30 8l2 2"/></svg>
|
||||
</div>
|
||||
<div class="empty-state-title">No tokens yet</div>
|
||||
<div class="empty-state-body">Create a token to authenticate API requests without a password.</div>
|
||||
<div class="empty-state-actions">
|
||||
<button class="btn btn-primary btn-sm" onclick="openNewTokenPanel()">New token</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New token slide panel -->
|
||||
<div class="slide-overlay" id="tokenOverlay" onclick="closeNewTokenPanel()"></div>
|
||||
<div class="slide-panel" id="tokenPanel">
|
||||
<div class="slide-panel-header">
|
||||
<span class="slide-panel-title">New API token</span>
|
||||
<button class="btn btn-ghost btn-sm" onclick="closeNewTokenPanel()" style="padding:0;width:28px;height:28px;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 3l10 10M13 3L3 13"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="slide-panel-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="tokenName">Token name</label>
|
||||
<input type="text" id="tokenName" placeholder="e.g. Premiere Plugin, CI/CD Script">
|
||||
<div class="form-hint">A label to help you remember what this token is for.</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="tokenExpiry">Expiry</label>
|
||||
<select id="tokenExpiry">
|
||||
<option value="">No expiry</option>
|
||||
<option value="30">30 days</option>
|
||||
<option value="90">90 days</option>
|
||||
<option value="365">1 year</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide-panel-footer">
|
||||
<button class="btn btn-ghost" onclick="closeNewTokenPanel()">Cancel</button>
|
||||
<button class="btn btn-primary" id="createTokenBtn" onclick="createNewToken()">Create token</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script>
|
||||
let latestToken = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadTokens);
|
||||
|
||||
async function loadTokens() {
|
||||
const r = await getTokens();
|
||||
const list = document.getElementById('tokensList');
|
||||
const empty = document.getElementById('tokensEmpty');
|
||||
|
||||
if (!r.success) {
|
||||
list.innerHTML = `<div class="text-sm text-tertiary">Could not load tokens.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const tokens = r.data;
|
||||
if (!tokens.length) {
|
||||
list.style.display = 'none';
|
||||
empty.style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
|
||||
list.style.display = 'flex';
|
||||
empty.style.display = 'none';
|
||||
|
||||
list.innerHTML = tokens.map(t => {
|
||||
const created = t.created_at ? new Date(t.created_at).toLocaleDateString() : '—';
|
||||
const lastUsed = t.last_used_at ? new Date(t.last_used_at).toLocaleDateString() : 'Never';
|
||||
const expires = t.expires_at ? new Date(t.expires_at).toLocaleDateString() : 'Never';
|
||||
const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
|
||||
return `
|
||||
<div class="token-card">
|
||||
<div class="token-card-icon">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><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>
|
||||
</div>
|
||||
<div class="token-card-body">
|
||||
<div class="token-card-name">${esc(t.name)}</div>
|
||||
<div class="token-card-meta">
|
||||
<span class="token-prefix">${esc(t.token_prefix)}…</span>
|
||||
· Created ${created}
|
||||
· Last used: ${lastUsed}
|
||||
· Expires: ${isExpired ? '<span style="color:var(--status-red)">Expired</span>' : expires}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-sm" onclick="confirmRevoke('${t.id}','${esc(t.name)}')">Revoke</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function openNewTokenPanel() {
|
||||
document.getElementById('tokenName').value = '';
|
||||
document.getElementById('tokenExpiry').value = '';
|
||||
document.getElementById('tokenPanel').classList.add('open');
|
||||
document.getElementById('tokenOverlay').classList.add('open');
|
||||
}
|
||||
|
||||
function closeNewTokenPanel() {
|
||||
document.getElementById('tokenPanel').classList.remove('open');
|
||||
document.getElementById('tokenOverlay').classList.remove('open');
|
||||
}
|
||||
|
||||
async function createNewToken() {
|
||||
const name = document.getElementById('tokenName').value.trim();
|
||||
if (!name) { toast('Token name required', '', 'warning'); return; }
|
||||
|
||||
const expires_in_days = document.getElementById('tokenExpiry').value || null;
|
||||
const btn = document.getElementById('createTokenBtn');
|
||||
btn.disabled = true;
|
||||
|
||||
const r = await createToken({ name, expires_in_days: expires_in_days ? parseInt(expires_in_days) : null });
|
||||
btn.disabled = false;
|
||||
|
||||
if (r.success) {
|
||||
closeNewTokenPanel();
|
||||
latestToken = r.data.token;
|
||||
document.getElementById('newTokenValue').textContent = latestToken;
|
||||
document.getElementById('newTokenBanner').style.display = 'block';
|
||||
document.getElementById('newTokenBanner').scrollIntoView({ behavior: 'smooth' });
|
||||
toast('Token created', name, 'success');
|
||||
loadTokens();
|
||||
} else {
|
||||
toast('Failed to create token', r.error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmRevoke(id, name) {
|
||||
if (!confirm(`Revoke token "${name}"? Any scripts using it will stop working.`)) return;
|
||||
const r = await revokeToken(id);
|
||||
if (r.success) { toast('Token revoked', name, 'success'); loadTokens(); }
|
||||
else toast('Failed to revoke token', r.error, 'error');
|
||||
}
|
||||
|
||||
function copyToken() {
|
||||
if (!latestToken) return;
|
||||
navigator.clipboard.writeText(latestToken).then(() => {
|
||||
toast('Copied to clipboard', '', 'success');
|
||||
}).catch(() => {
|
||||
toast('Copy failed — select and copy manually', '', 'warning');
|
||||
});
|
||||
}
|
||||
|
||||
function toast(title, msg, type = 'info') {
|
||||
const icons = {
|
||||
success: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M5 8l2 2 4-4"/></svg>`,
|
||||
error: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 5v4M8 11v.5"/></svg>`,
|
||||
warning: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2L1 14h14L8 2z"/><path d="M8 7v3M8 12v.5"/></svg>`,
|
||||
info: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 7v5M8 5v.5"/></svg>`,
|
||||
};
|
||||
const el = document.createElement('div');
|
||||
el.className = `toast toast--${type}`;
|
||||
el.innerHTML = `<div class="toast-icon">${icons[type]||icons.info}</div><div class="toast-body"><div class="toast-title">${esc(title)}</div>${msg?`<div class="toast-msg">${esc(msg)}</div>`:''}</div>`;
|
||||
document.getElementById('toastContainer').appendChild(el);
|
||||
setTimeout(() => el.remove(), 4000);
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -4,10 +4,7 @@
|
|||
<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>Capture — Wild Dragon</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
||||
<title>Capture — Z-AMPP</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
.capture-layout {
|
||||
|
|
@ -186,13 +183,19 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
|
||||
<a href="home.html" class="nav-item"><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="nav-item">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="projects.html" class="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="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
|
||||
|
|
@ -209,12 +212,32 @@
|
|||
<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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<a href="edit.html" class="nav-item" target="_blank" rel="noopener">
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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="main">
|
||||
|
|
@ -523,5 +546,11 @@
|
|||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
Broadcast operations console. Signature blue. Deep slate.
|
||||
Operator-grade type, tabular numerics, signal-first hierarchy.
|
||||
================================================================ */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
|
||||
/* ================================================================
|
||||
TOKENS
|
||||
================================================================ */
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Editor — Z-AMPP</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
/* ── Editor layout ── */
|
||||
|
|
@ -313,17 +310,61 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item"><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="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="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="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="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="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="edit.html" class="nav-item active"><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>
|
||||
<a href="jobs.html" class="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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="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="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="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="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="#" id="editor-nav-link" class="nav-item active" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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="main">
|
||||
|
|
@ -395,5 +436,11 @@
|
|||
<script src="js/api.js"></script>
|
||||
<script src="js/topbar-strip.js"></script>
|
||||
<script src="js/edit.js?v=2"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById("editor-nav-link");
|
||||
if(el){el.href=location.protocol+"//"+location.hostname+":47435/";}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@
|
|||
<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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
/* AMPP-style landing — wide row of preview cards on a brand-blue gradient */
|
||||
|
|
@ -195,16 +192,61 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item 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="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="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="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="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="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="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="edit.html" class="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>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item 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="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="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="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="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="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="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="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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="main home-main">
|
||||
|
|
@ -433,5 +475,11 @@
|
|||
loadStats();
|
||||
setInterval(loadStats, 15000);
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
<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>Library — Wild Dragon</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
||||
<title>Library — Z-AMPP</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
/* ── Library layout ── */
|
||||
|
|
@ -320,13 +317,19 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
|
||||
<a href="home.html" class="nav-item"><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="nav-item active">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="nav-item active">
|
||||
<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="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="projects.html" class="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="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
|
||||
|
|
@ -343,12 +346,32 @@
|
|||
<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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<a href="edit.html" class="nav-item" target="_blank" rel="noopener">
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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>
|
||||
|
||||
<!-- Main -->
|
||||
|
|
@ -832,5 +855,11 @@
|
|||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -365,13 +365,19 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
|
||||
<a href="home.html" class="nav-item"><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="nav-item">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="projects.html" class="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="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
|
||||
|
|
@ -388,12 +394,32 @@
|
|||
<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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<a href="edit.html" class="nav-item" target="_blank" rel="noopener">
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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>
|
||||
|
||||
<!-- ── Main area ────────────────────────────────────────── -->
|
||||
|
|
@ -854,5 +880,11 @@ loadJobs();
|
|||
</script>
|
||||
<script src="js/topbar-strip.js?v=1"></script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Projects — Z-AMPP</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
.proj-shell { display: flex; flex: 1; overflow: hidden; }
|
||||
|
|
@ -254,17 +251,61 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Z-AMPP" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item"><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="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="nav-item active"><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="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="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="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="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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<a href="edit.html" class="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>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="nav-item active">
|
||||
<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="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="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="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="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="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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="main">
|
||||
|
|
@ -522,5 +563,11 @@
|
|||
document.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal-overlay.open').forEach(m => m.classList.remove('open')); });
|
||||
loadAll();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Recorders — Z-AMPP</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/common.css?v=3">
|
||||
<style>
|
||||
/* ── Recorders page · AMPP-aligned theme ── */
|
||||
|
|
@ -131,13 +128,19 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
|
||||
<a href="home.html" class="nav-item"><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="nav-item">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="projects.html" class="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="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
|
||||
|
|
@ -154,12 +157,32 @@
|
|||
<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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<a href="edit.html" class="nav-item" target="_blank" rel="noopener">
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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="main">
|
||||
|
|
@ -790,5 +813,11 @@
|
|||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,300 +0,0 @@
|
|||
<!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>Wild Dragon - Settings</title>
|
||||
<link rel="stylesheet" href="/css/common.css">
|
||||
<style>
|
||||
.settings-container {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.settings-section-icon {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-section-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-tertiary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-body {
|
||||
padding: var(--spacing-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.settings-row {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.settings-row .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.settings-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.status-msg {
|
||||
font-size: 0.9rem;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-md);
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.status-msg.success {
|
||||
display: flex;
|
||||
background-color: rgba(16, 185, 129, 0.15);
|
||||
color: var(--color-success);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.status-msg.error {
|
||||
display: flex;
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
color: var(--color-danger);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.status-msg.loading {
|
||||
display: flex;
|
||||
background-color: rgba(59, 130, 246, 0.15);
|
||||
color: var(--color-info);
|
||||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.token-hint {
|
||||
font-size: 0.82rem;
|
||||
color: var(--color-text-tertiary);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.token-set-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-success);
|
||||
background-color: rgba(16, 185, 129, 0.12);
|
||||
border: 1px solid rgba(16, 185, 129, 0.25);
|
||||
border-radius: 12px;
|
||||
padding: 2px 8px;
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-logo">
|
||||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="header-logo-img">
|
||||
<span>Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="header-nav">
|
||||
<div class="nav-item" data-page="assets">Assets</div>
|
||||
<div class="nav-item" data-page="capture">Capture</div>
|
||||
<div class="nav-item" data-page="upload">Upload</div>
|
||||
<div class="nav-item" data-page="recorders">Recorders</div>
|
||||
<div class="nav-item active" data-page="settings">Settings</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<div class="content-area">
|
||||
<div class="content-main">
|
||||
<div class="settings-container">
|
||||
|
||||
<h1 style="margin-bottom: 0;">Settings</h1>
|
||||
|
||||
<!-- AMPP Integration Section -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-section-header">
|
||||
<div class="settings-section-icon">📡</div>
|
||||
<div>
|
||||
<h3 class="settings-section-title">AMPP FramelightX</h3>
|
||||
<p class="settings-section-subtitle">Dragon-Wind will pre-create folder paths in AMPP automatically on each upload.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="amppBaseUrl">AMPP Base URL</label>
|
||||
<input
|
||||
type="url"
|
||||
id="amppBaseUrl"
|
||||
class="form-input"
|
||||
placeholder="https://ampp.yourdomain.net"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="amppToken">API Token</label>
|
||||
<input
|
||||
type="password"
|
||||
id="amppToken"
|
||||
class="form-input"
|
||||
placeholder="Paste token — leave blank to keep existing"
|
||||
>
|
||||
<div id="tokenSetBadge" class="token-set-badge" style="display: none;">✓ Token saved</div>
|
||||
<div class="token-hint">Token is stored securely and never returned to the UI.</div>
|
||||
</div>
|
||||
|
||||
<div id="amppStatus" class="status-msg"></div>
|
||||
|
||||
<div class="settings-actions">
|
||||
<button class="btn btn-primary" onclick="saveAmpp()">Save</button>
|
||||
<button class="btn btn-secondary" onclick="testAmpp()">Test Connection</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<footer class="status-bar">
|
||||
<div class="status-item">
|
||||
<span class="status-indicator" id="statusIndicator"></span>
|
||||
<span id="statusText">Settings</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = '/api/v1';
|
||||
|
||||
// ── Navigation ──────────────────────────────────────────────
|
||||
document.querySelectorAll('[data-page]').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const page = el.dataset.page;
|
||||
if (page === 'assets') window.location.href = '/index.html';
|
||||
if (page === 'capture') window.location.href = '/capture.html';
|
||||
if (page === 'upload') window.location.href = '/upload.html';
|
||||
if (page === 'recorders') window.location.href = '/recorders.html';
|
||||
});
|
||||
});
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', loadAmppSettings);
|
||||
|
||||
async function loadAmppSettings() {
|
||||
try {
|
||||
const res = await fetch(`${API}/settings/ampp`);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (data.ampp_base_url) {
|
||||
document.getElementById('amppBaseUrl').value = data.ampp_base_url;
|
||||
}
|
||||
if (data.ampp_token_exists) {
|
||||
document.getElementById('tokenSetBadge').style.display = 'inline-flex';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load AMPP settings:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────
|
||||
async function saveAmpp() {
|
||||
const baseUrl = document.getElementById('amppBaseUrl').value.trim();
|
||||
const token = document.getElementById('amppToken').value.trim();
|
||||
|
||||
if (!baseUrl) {
|
||||
showStatus('amppStatus', 'error', 'Base URL is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
showStatus('amppStatus', 'loading', 'Saving…');
|
||||
|
||||
try {
|
||||
const body = { ampp_base_url: baseUrl };
|
||||
if (token) body.ampp_token = token;
|
||||
|
||||
const res = await fetch(`${API}/settings/ampp`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'Save failed');
|
||||
|
||||
if (token) {
|
||||
document.getElementById('amppToken').value = '';
|
||||
document.getElementById('tokenSetBadge').style.display = 'inline-flex';
|
||||
}
|
||||
|
||||
showStatus('amppStatus', 'success', '✓ Settings saved.');
|
||||
} catch (err) {
|
||||
showStatus('amppStatus', 'error', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Test ──────────────────────────────────────────────────────
|
||||
async function testAmpp() {
|
||||
showStatus('amppStatus', 'loading', 'Testing connection…');
|
||||
try {
|
||||
const res = await fetch(`${API}/settings/ampp/test`, { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'Test failed');
|
||||
showStatus('amppStatus', 'success', '✓ ' + data.message);
|
||||
} catch (err) {
|
||||
showStatus('amppStatus', 'error', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
function showStatus(id, type, msg) {
|
||||
const el = document.getElementById(id);
|
||||
el.className = 'status-msg ' + type;
|
||||
el.textContent = msg;
|
||||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -3,94 +3,335 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tokens — Wild Dragon</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Token Pricing — Z-AMPP</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
.token-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
padding: var(--sp-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-4);
|
||||
/* GV-flavored teal-on-dark just to lean into the parody */
|
||||
.tok-main {
|
||||
flex: 1; overflow: auto;
|
||||
background:
|
||||
radial-gradient(ellipse 70% 50% at 50% 0%, oklch(38% 0.10 200 / 0.45), transparent 60%),
|
||||
radial-gradient(ellipse 80% 60% at 20% 100%, oklch(35% 0.14 195 / 0.35), transparent 65%),
|
||||
linear-gradient(135deg, oklch(20% 0.06 220), oklch(12% 0.025 230) 100%);
|
||||
}
|
||||
.token-card-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--r-md);
|
||||
background: var(--accent-subtle);
|
||||
border: 1px solid var(--accent-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
.tok-wrap { max-width: 1200px; margin: 0 auto; padding: 48px 32px 80px; }
|
||||
|
||||
.tok-banner {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.token-card-body { flex: 1; min-width: 0; }
|
||||
.token-card-name { font-weight: 500; font-size: var(--text-sm); }
|
||||
.token-card-meta { font-size: var(--text-xs); color: var(--text-tertiary); margin-top: 2px; }
|
||||
.token-prefix {
|
||||
font-family: 'SF Mono', 'Consolas', monospace;
|
||||
font-size: var(--text-xs);
|
||||
.tok-banner-eyebrow {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 5px 14px;
|
||||
background: oklch(15% 0.04 200);
|
||||
border: 1px solid oklch(50% 0.12 200 / 0.5);
|
||||
border-radius: 999px;
|
||||
font-size: 10px; font-weight: 700; letter-spacing: 0.18em;
|
||||
text-transform: uppercase; color: oklch(75% 0.12 200);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.tok-banner-eyebrow::before {
|
||||
content: ''; width: 6px; height: 6px;
|
||||
background: oklch(70% 0.18 200); border-radius: 50%;
|
||||
box-shadow: 0 0 10px oklch(70% 0.18 200);
|
||||
}
|
||||
.tok-title {
|
||||
font-size: 42px; font-weight: 700; letter-spacing: -0.02em;
|
||||
line-height: 1.05; color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tok-title .strike { text-decoration: line-through; opacity: 0.4; }
|
||||
.tok-title .pop { color: oklch(75% 0.14 200); }
|
||||
.tok-sub {
|
||||
max-width: 56ch; margin: 0 auto;
|
||||
font-size: 14px; color: oklch(70% 0.05 215); line-height: 1.55;
|
||||
}
|
||||
.tok-sub b { color: oklch(80% 0.10 200); }
|
||||
|
||||
/* ─ Tier cards ─ */
|
||||
.tok-tiers {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
margin: 32px 0 40px;
|
||||
}
|
||||
.tok-tier {
|
||||
position: relative;
|
||||
background: oklch(13% 0.025 220 / 0.7);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.5);
|
||||
border-radius: 12px;
|
||||
padding: 22px 22px 18px;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.tok-tier.featured {
|
||||
border-color: oklch(60% 0.15 200 / 0.7);
|
||||
box-shadow: 0 16px 50px -16px oklch(50% 0.18 200 / 0.5);
|
||||
}
|
||||
.tok-tier-flag {
|
||||
position: absolute; top: -10px; right: 18px;
|
||||
background: oklch(58% 0.16 200);
|
||||
color: oklch(10% 0.02 220);
|
||||
font-size: 10px; font-weight: 700; letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 10px; border-radius: 999px;
|
||||
}
|
||||
.tok-tier-name {
|
||||
font-size: 11px; font-weight: 700; letter-spacing: 0.18em;
|
||||
text-transform: uppercase; color: oklch(70% 0.10 200);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.tok-tier-price {
|
||||
font-size: 28px; font-weight: 700;
|
||||
color: var(--text-primary); letter-spacing: -0.01em;
|
||||
display: flex; align-items: baseline; gap: 4px;
|
||||
}
|
||||
.tok-tier-price small {
|
||||
font-size: 13px; font-weight: 500;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
.tok-tier-tokens {
|
||||
margin-top: 4px;
|
||||
font-size: 12px; color: var(--text-secondary);
|
||||
font-family: var(--font-mono); letter-spacing: 0.04em;
|
||||
}
|
||||
.tok-tier-list {
|
||||
margin: 14px 0 16px; padding: 0; list-style: none;
|
||||
font-size: 12px; line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-raised);
|
||||
padding: 1px 6px;
|
||||
border-radius: var(--r-sm);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.tokens-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-2);
|
||||
max-width: 680px;
|
||||
.tok-tier-list li { padding-left: 16px; position: relative; }
|
||||
.tok-tier-list li::before {
|
||||
content: '+'; position: absolute; left: 0; top: 0;
|
||||
color: oklch(70% 0.14 200); font-weight: 700;
|
||||
}
|
||||
.new-token-banner {
|
||||
background: var(--status-green-bg);
|
||||
border: 1px solid oklch(68% 0.18 148 / 0.30);
|
||||
border-radius: var(--r-lg);
|
||||
padding: var(--sp-4) var(--sp-5);
|
||||
margin-bottom: var(--sp-5);
|
||||
max-width: 680px;
|
||||
.tok-tier-list li.minus::before { content: '−'; color: oklch(62% 0.22 25 / 0.7); }
|
||||
.tok-tier-cta {
|
||||
display: block; text-align: center;
|
||||
width: 100%; padding: 9px 12px;
|
||||
background: transparent;
|
||||
border: 1px solid oklch(50% 0.12 200 / 0.55);
|
||||
border-radius: 8px;
|
||||
color: oklch(75% 0.12 200);
|
||||
font: inherit; font-size: 12px; font-weight: 600;
|
||||
letter-spacing: 0.06em; text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background 120ms ease, border-color 120ms ease;
|
||||
}
|
||||
.new-token-banner-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
color: var(--status-green);
|
||||
margin-bottom: var(--sp-2);
|
||||
display: flex;
|
||||
.tok-tier-cta:hover { background: oklch(20% 0.08 200 / 0.4); }
|
||||
.tok-tier.featured .tok-tier-cta {
|
||||
background: oklch(55% 0.16 200);
|
||||
border-color: oklch(55% 0.16 200);
|
||||
color: oklch(10% 0.02 220);
|
||||
}
|
||||
.tok-tier.featured .tok-tier-cta:hover {
|
||||
background: oklch(62% 0.18 200);
|
||||
}
|
||||
|
||||
/* ─ Per-service table ─ */
|
||||
.tok-section-head {
|
||||
display: flex; align-items: baseline; justify-content: space-between;
|
||||
margin: 40px 0 14px;
|
||||
}
|
||||
.tok-section-title {
|
||||
font-size: 11px; font-weight: 700; letter-spacing: 0.20em;
|
||||
text-transform: uppercase; color: oklch(70% 0.10 200);
|
||||
}
|
||||
.tok-section-hint {
|
||||
font-size: 11px; color: var(--text-tertiary);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.tok-table {
|
||||
width: 100%;
|
||||
background: oklch(11% 0.020 220 / 0.7);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.4);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.tok-row {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1.4fr 1fr 0.9fr 0.9fr;
|
||||
gap: 14px;
|
||||
padding: 12px 18px;
|
||||
border-top: 1px solid oklch(35% 0.06 215 / 0.25);
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
font-size: 13px;
|
||||
}
|
||||
.new-token-banner-warning {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
margin-top: var(--sp-3);
|
||||
.tok-row:first-child {
|
||||
border-top: 0;
|
||||
font-size: 10px; font-weight: 700; letter-spacing: 0.16em;
|
||||
text-transform: uppercase; color: oklch(65% 0.08 200);
|
||||
padding: 14px 18px;
|
||||
}
|
||||
.copy-btn {
|
||||
margin-top: var(--sp-3);
|
||||
.tok-row-icon {
|
||||
width: 32px; height: 32px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: oklch(15% 0.05 215);
|
||||
border: 1px solid oklch(45% 0.12 200 / 0.4);
|
||||
border-radius: 8px;
|
||||
color: oklch(72% 0.12 200);
|
||||
}
|
||||
.tok-row-icon svg { width: 16px; height: 16px; }
|
||||
.tok-row-name { font-weight: 500; color: var(--text-primary); }
|
||||
.tok-row-name small {
|
||||
display: block;
|
||||
font-size: 11px; font-weight: 400;
|
||||
color: var(--text-tertiary); margin-top: 2px;
|
||||
}
|
||||
.tok-row-meter, .tok-row-rate, .tok-row-mult {
|
||||
font-family: var(--font-mono); font-size: 12px;
|
||||
color: var(--text-secondary); letter-spacing: 0.04em;
|
||||
}
|
||||
.tok-row-rate b { color: oklch(78% 0.14 200); font-weight: 600; }
|
||||
.tok-row-mult.hot { color: oklch(70% 0.18 25); }
|
||||
.tok-row-mult.cold { color: oklch(70% 0.14 145); }
|
||||
|
||||
/* ─ Calculator ─ */
|
||||
.tok-calc {
|
||||
margin-top: 36px;
|
||||
background: oklch(13% 0.025 220 / 0.7);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.5);
|
||||
border-radius: 12px;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
.tok-calc-head { margin-bottom: 14px; }
|
||||
.tok-calc-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
|
||||
.tok-calc-sub { font-size: 12px; color: var(--text-tertiary); margin-top: 4px; }
|
||||
.tok-calc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
margin: 16px 0 18px;
|
||||
}
|
||||
.tok-calc-field {
|
||||
display: flex; flex-direction: column; gap: 4px;
|
||||
}
|
||||
.tok-calc-label {
|
||||
font-size: 10px; font-weight: 600;
|
||||
letter-spacing: 0.14em; text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
.tok-calc-field input {
|
||||
padding: 8px 12px;
|
||||
background: oklch(8% 0.015 220);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.5);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary); font: inherit;
|
||||
font-family: var(--font-mono); font-size: 14px;
|
||||
}
|
||||
.tok-calc-out {
|
||||
padding: 16px 18px;
|
||||
background: oklch(8% 0.015 220);
|
||||
border: 1px solid oklch(50% 0.12 200 / 0.4);
|
||||
border-radius: 8px;
|
||||
display: flex; flex-wrap: wrap; gap: 24px; align-items: baseline;
|
||||
}
|
||||
.tok-calc-out-total {
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.tok-calc-out-label {
|
||||
font-size: 10px; font-weight: 600;
|
||||
letter-spacing: 0.18em; text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
.tok-calc-out-value {
|
||||
font-size: 28px; font-weight: 700;
|
||||
color: oklch(82% 0.12 200);
|
||||
font-family: var(--font-mono); letter-spacing: -0.02em;
|
||||
}
|
||||
.tok-calc-out-aside {
|
||||
font-size: 11px; color: var(--text-tertiary);
|
||||
max-width: 36ch; line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ─ Footnote micro-print ─ */
|
||||
.tok-footer {
|
||||
margin-top: 36px;
|
||||
padding: 18px 22px;
|
||||
background: oklch(8% 0.015 220 / 0.6);
|
||||
border: 1px solid oklch(30% 0.04 215 / 0.4);
|
||||
border-radius: 10px;
|
||||
font-size: 10px; line-height: 1.6;
|
||||
color: oklch(55% 0.04 215);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.tok-footer b { color: oklch(70% 0.06 215); font-weight: 600; }
|
||||
.tok-footer p { margin: 0 0 8px; }
|
||||
.tok-footer p:last-child { margin: 0; }
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.tok-row { grid-template-columns: 36px 1fr 1fr; gap: 10px; padding: 10px 12px; font-size: 12px; }
|
||||
.tok-row > :nth-child(4), .tok-row > :nth-child(5) { display: none; }
|
||||
.tok-title { font-size: 30px; }
|
||||
}
|
||||
|
||||
/* ─ Token burn chart ─ */
|
||||
.tok-chart {
|
||||
background: oklch(11% 0.020 220 / 0.7);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.5);
|
||||
border-radius: 12px;
|
||||
padding: 20px 22px 22px;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.tok-chart-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.tok-stat {
|
||||
display: flex; flex-direction: column; gap: 2px;
|
||||
padding: 10px 12px;
|
||||
background: oklch(8% 0.015 220 / 0.7);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.3);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.tok-stat-label { font-size: 10px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-tertiary); }
|
||||
.tok-stat-value { font-family: var(--font-mono); font-size: 20px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.01em; }
|
||||
.tok-stat-delta { font-size: 10px; font-weight: 600; color: var(--text-tertiary); letter-spacing: 0.04em; }
|
||||
.tok-stat-delta.hot { color: oklch(70% 0.18 25); }
|
||||
.tok-stat-delta.cold { color: oklch(70% 0.14 145); }
|
||||
.tok-chart-frame {
|
||||
position: relative;
|
||||
background: oklch(8% 0.015 220 / 0.7);
|
||||
border: 1px solid oklch(35% 0.06 215 / 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
.tok-chart-svg { width: 100%; height: 260px; display: block; }
|
||||
.tok-chart-legend {
|
||||
display: flex; flex-wrap: wrap; gap: 18px;
|
||||
margin-top: 10px;
|
||||
font-size: 11px; color: var(--text-secondary);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.tok-chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.tok-chart-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="shell">
|
||||
<nav class="sidebar" aria-label="Main navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="sidebar-brand-mark">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><path d="M8 1L2 5v6l6 4 6-4V5L8 1zm0 2.2L12 6v4l-4 2.7L4 10V6l4-2.8z"/></svg>
|
||||
</div>
|
||||
<span class="sidebar-brand-name">Wild Dragon</span>
|
||||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="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
|
||||
|
|
@ -107,11 +348,11 @@
|
|||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h8M2 12h5"/></svg>
|
||||
Jobs
|
||||
</a>
|
||||
<div class="sidebar-section-label">Admin</div>
|
||||
<a href="settings.html" class="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 1.5v1M8 13.5v1M1.5 8h1M13.5 8h1M3.2 3.2l.7.7M12.1 12.1l.7.7M12.1 3.9l-.7.7M3.9 12.1l-.7.7"/></svg>
|
||||
Settings
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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
|
||||
|
|
@ -135,215 +376,378 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
<div class="main tok-main">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<span class="page-title">API Tokens</span>
|
||||
<span class="page-title">Token Pricing</span>
|
||||
<span class="topbar-sep">/</span>
|
||||
<span class="text-sm" style="color:var(--text-tertiary)">Enterprise Compute Compliance Engine v4.7</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="btn btn-primary btn-sm" onclick="openNewTokenPanel()">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v12M2 8h12"/></svg>
|
||||
New token
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" onclick="alert('Your account executive will be in touch.\\n\\nEstimated response time: 6–12 business days.')">Talk to sales</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-content">
|
||||
<div style="max-width:680px;margin-bottom:var(--sp-5);">
|
||||
<p class="text-sm text-secondary" style="line-height:1.7;">
|
||||
API tokens let scripts and integrations authenticate as you without using your password.
|
||||
Tokens are shown once at creation — store them securely.
|
||||
Use <code style="font-family:monospace;font-size:11px;background:var(--bg-surface);padding:1px 5px;border-radius:3px;border:1px solid var(--border)">Authorization: Bearer <token></code> in your requests.
|
||||
</p>
|
||||
<div class="tok-wrap">
|
||||
|
||||
<!-- Banner -->
|
||||
<div class="tok-banner">
|
||||
<span class="tok-banner-eyebrow">Z-AMPP Pricing</span>
|
||||
<h1 class="tok-title"><span class="strike">Per-seat</span> · <span class="strike">Per-stream</span> · <span class="strike">Per-month</span><br><span class="pop">Per token.</span></h1>
|
||||
<p class="tok-sub">Welcome to the future of broadcast media operations. Tokens are <b>fungible compute credits</b> that flexibly meter every action across the Platform™. Move faster. Pay precisely. Forecast nothing.</p>
|
||||
</div>
|
||||
|
||||
<!-- New token reveal (shown after creation) -->
|
||||
<div id="newTokenBanner" style="display:none;" class="new-token-banner">
|
||||
<div class="new-token-banner-title">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><circle cx="8" cy="8" r="6.5"/><path d="M5 8l2 2 4-4"/></svg>
|
||||
Token created
|
||||
<!-- Tiers -->
|
||||
<div class="tok-tiers">
|
||||
|
||||
<div class="tok-tier">
|
||||
<div class="tok-tier-name">Starter</div>
|
||||
<div class="tok-tier-price">$499<small> / mo</small></div>
|
||||
<div class="tok-tier-tokens">100,000 tokens · $4.99 / 1k</div>
|
||||
<ul class="tok-tier-list">
|
||||
<li>1 concurrent recorder</li>
|
||||
<li>SD ingest (480p · 1.2× multiplier)</li>
|
||||
<li>Standard support · email · 96h SLA</li>
|
||||
<li class="minus">No HD codec access</li>
|
||||
<li class="minus">No ProRes write</li>
|
||||
</ul>
|
||||
<button class="tok-tier-cta" onclick="addToCart('Starter')">Get started</button>
|
||||
</div>
|
||||
<div class="token-reveal" id="newTokenValue"></div>
|
||||
<div class="new-token-banner-warning">
|
||||
⚠ This is the only time this token will be shown. Copy it now.
|
||||
|
||||
<div class="tok-tier featured">
|
||||
<span class="tok-tier-flag">Most flexible</span>
|
||||
<div class="tok-tier-name">Professional</div>
|
||||
<div class="tok-tier-price">$2,499<small> / mo</small></div>
|
||||
<div class="tok-tier-tokens">600,000 tokens · $4.17 / 1k</div>
|
||||
<ul class="tok-tier-list">
|
||||
<li>4 concurrent recorders</li>
|
||||
<li>HD ingest (1080p · 3.2× multiplier)</li>
|
||||
<li>ProRes HQ write (2.4× multiplier)</li>
|
||||
<li>Priority queue · 24h SLA</li>
|
||||
<li class="minus">4K surcharge applies</li>
|
||||
</ul>
|
||||
<button class="tok-tier-cta" onclick="addToCart('Professional')">Provision tier</button>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm copy-btn" onclick="copyToken()">Copy to clipboard</button>
|
||||
|
||||
<div class="tok-tier">
|
||||
<div class="tok-tier-name">Broadcast</div>
|
||||
<div class="tok-tier-price">$8,999<small> / mo</small></div>
|
||||
<div class="tok-tier-tokens">2,400,000 tokens · $3.75 / 1k</div>
|
||||
<ul class="tok-tier-list">
|
||||
<li>12 concurrent recorders</li>
|
||||
<li>4K ingest (5.8× multiplier)</li>
|
||||
<li>ProRes 4444 write (4.0× multiplier)</li>
|
||||
<li>Named CSM · phone · 4h SLA</li>
|
||||
<li>Token rollover (90 days, fees apply)</li>
|
||||
</ul>
|
||||
<button class="tok-tier-cta" onclick="addToCart('Broadcast')">Engage account team</button>
|
||||
</div>
|
||||
|
||||
<div class="tok-tier">
|
||||
<div class="tok-tier-name">Enterprise</div>
|
||||
<div class="tok-tier-price">Contact us</div>
|
||||
<div class="tok-tier-tokens">Custom token allocation</div>
|
||||
<ul class="tok-tier-list">
|
||||
<li>Unlimited* concurrent recorders</li>
|
||||
<li>8K / IMF / DCP write tiers</li>
|
||||
<li>Dedicated solutions architect</li>
|
||||
<li>Quarterly token true-up audits</li>
|
||||
<li class="minus">Implementation fee not included</li>
|
||||
</ul>
|
||||
<button class="tok-tier-cta" onclick="addToCart('Enterprise')">Request quotation</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Token list -->
|
||||
<div class="tokens-list" id="tokensList">
|
||||
<div style="color:var(--text-tertiary);font-size:var(--text-sm)">Loading…</div>
|
||||
<!-- Per-service table -->
|
||||
<div class="tok-section-head">
|
||||
<span class="tok-section-title">Per-Service Metering</span>
|
||||
<span class="tok-section-hint">All rates exclusive of TVM · effective Q3 FY26</span>
|
||||
</div>
|
||||
|
||||
<div id="tokensEmpty" class="empty-state" style="display:none;padding:var(--sp-12) 0;">
|
||||
<div class="empty-state-icon">
|
||||
<svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="15" cy="25" r="8"/><path d="M21 19l10-10M28 10l3 3M30 8l2 2"/></svg>
|
||||
<div class="tok-table">
|
||||
<div class="tok-row">
|
||||
<span></span>
|
||||
<span>Service</span>
|
||||
<span>Meter</span>
|
||||
<span>Base rate</span>
|
||||
<span>Multiplier</span>
|
||||
</div>
|
||||
<div class="empty-state-title">No tokens yet</div>
|
||||
<div class="empty-state-body">Create a token to authenticate API requests without a password.</div>
|
||||
<div class="empty-state-actions">
|
||||
<button class="btn btn-primary btn-sm" onclick="openNewTokenPanel()">New token</button>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><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></div>
|
||||
<div class="tok-row-name">Library<small>Asset browse, search, thumbnail render</small></div>
|
||||
<div class="tok-row-meter">Per asset · per hour</div>
|
||||
<div class="tok-row-rate"><b>0.012</b> tokens</div>
|
||||
<div class="tok-row-mult">1.00×</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><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></div>
|
||||
<div class="tok-row-name">Ingest<small>Upload + transcode to managed proxy</small></div>
|
||||
<div class="tok-row-meter">Per GB · per pass</div>
|
||||
<div class="tok-row-rate"><b>14.4</b> tokens / GB</div>
|
||||
<div class="tok-row-mult hot">2.4× during business hours</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><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></div>
|
||||
<div class="tok-row-name">Recorder · SRT<small>Caller-mode network ingest, includes HLS preview*</small></div>
|
||||
<div class="tok-row-meter">Per minute · per recorder</div>
|
||||
<div class="tok-row-rate"><b>4.8</b> tokens / min</div>
|
||||
<div class="tok-row-mult hot">+22% Reliability Adjustment</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><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></div>
|
||||
<div class="tok-row-name">Recorder · RTMP<small>Generic ingest tier · legacy codec compatibility</small></div>
|
||||
<div class="tok-row-meter">Per minute · per recorder</div>
|
||||
<div class="tok-row-rate"><b>3.6</b> tokens / min</div>
|
||||
<div class="tok-row-mult">1.00×</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><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></div>
|
||||
<div class="tok-row-name">Capture · SDI<small>DeckLink baseband ingest · 12G-SDI add-on available</small></div>
|
||||
<div class="tok-row-meter">Per minute · per SDI channel</div>
|
||||
<div class="tok-row-rate"><b>9.2</b> tokens / min</div>
|
||||
<div class="tok-row-mult hot">1.8× premium baseband multiplier</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3"/><circle cx="13" cy="3" r="1"/></svg></div>
|
||||
<div class="tok-row-name">Live HLS Preview<small>Real-time delivery acceleration (RTDA™)</small></div>
|
||||
<div class="tok-row-meter">Per active viewer · per second</div>
|
||||
<div class="tok-row-rate"><b>0.0008</b> tokens</div>
|
||||
<div class="tok-row-mult hot">3.2× CDN egress premium</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5 8h6M8 5v6"/></svg></div>
|
||||
<div class="tok-row-name">ProRes HQ Write<small>Mastering-grade codec licensing</small></div>
|
||||
<div class="tok-row-meter">Per minute of media</div>
|
||||
<div class="tok-row-rate"><b>6.4</b> tokens / min</div>
|
||||
<div class="tok-row-mult hot">2.4× codec entitlement fee</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><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></div>
|
||||
<div class="tok-row-name">Editor render<small>Server-side concat / trim · brand-aligned codec ladder</small></div>
|
||||
<div class="tok-row-meter">Per minute of output</div>
|
||||
<div class="tok-row-rate"><b>11.8</b> tokens / min</div>
|
||||
<div class="tok-row-mult hot">+18% Real-Time Render Surcharge</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h8M2 12h5"/></svg></div>
|
||||
<div class="tok-row-name">Background Jobs<small>Proxy gen, thumbnails, AMPP folder sync</small></div>
|
||||
<div class="tok-row-meter">Per job · per CPU-second</div>
|
||||
<div class="tok-row-rate"><b>0.45</b> tokens</div>
|
||||
<div class="tok-row-mult cold">0.85× off-peak discount</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3h12v10H2z"/><path d="M5 6h6M5 9h4"/></svg></div>
|
||||
<div class="tok-row-name">Premiere Pro Connector<small>CEP bridge · per-NLE seat compatibility license</small></div>
|
||||
<div class="tok-row-meter">Per workstation · per month</div>
|
||||
<div class="tok-row-rate"><b>22,000</b> tokens</div>
|
||||
<div class="tok-row-mult hot">+ $99 NLE Compatibility Levy</div>
|
||||
</div>
|
||||
|
||||
<div class="tok-row">
|
||||
<div class="tok-row-icon"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l3 2"/></svg></div>
|
||||
<div class="tok-row-name">API call<small>GET /api/v1/* · includes 200-byte response budget</small></div>
|
||||
<div class="tok-row-meter">Per request</div>
|
||||
<div class="tok-row-rate"><b>0.0011</b> tokens</div>
|
||||
<div class="tok-row-mult">1.00× (overage 3.4×)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage chart -->
|
||||
<div class="tok-section-head">
|
||||
<span class="tok-section-title">Current Token Burn</span>
|
||||
<span class="tok-section-hint" id="chartHint">Last 14 days · nightly true-up · TVM applied</span>
|
||||
</div>
|
||||
<div class="tok-chart">
|
||||
<div class="tok-chart-stats">
|
||||
<div class="tok-stat"><span class="tok-stat-label">MTD burn</span><span class="tok-stat-value" id="statMtd">—</span><span class="tok-stat-delta hot" id="statMtdDelta">+0%</span></div>
|
||||
<div class="tok-stat"><span class="tok-stat-label">Forecast EOM</span><span class="tok-stat-value" id="statEom">—</span><span class="tok-stat-delta hot" id="statEomDelta">over plan</span></div>
|
||||
<div class="tok-stat"><span class="tok-stat-label">TVM (live)</span><span class="tok-stat-value" id="statTvm">—</span><span class="tok-stat-delta" id="statTvmTrend">stable</span></div>
|
||||
<div class="tok-stat"><span class="tok-stat-label">Peak draw</span><span class="tok-stat-value" id="statPeak">—</span><span class="tok-stat-delta cold" id="statPeakDay">Wed</span></div>
|
||||
</div>
|
||||
<div class="tok-chart-frame">
|
||||
<svg class="tok-chart-svg" id="burnChart" viewBox="0 0 800 220" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
<div class="tok-chart-legend">
|
||||
<span><i style="background:oklch(70% 0.18 200)"></i>Ingest</span>
|
||||
<span><i style="background:oklch(62% 0.15 145)"></i>Recorders</span>
|
||||
<span><i style="background:oklch(70% 0.18 80)"></i>Render</span>
|
||||
<span><i style="background:oklch(62% 0.22 25)"></i>Overage</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calculator -->
|
||||
<div class="tok-calc">
|
||||
<div class="tok-calc-head">
|
||||
<div class="tok-calc-title">Monthly token estimator</div>
|
||||
<div class="tok-calc-sub">Honest forecasts since 2019. Actual usage may vary by up to 340%.</div>
|
||||
</div>
|
||||
<div class="tok-calc-grid">
|
||||
<div class="tok-calc-field"><label class="tok-calc-label">Ingest GB/mo</label><input id="iIngest" type="number" value="800" min="0"></div>
|
||||
<div class="tok-calc-field"><label class="tok-calc-label">SRT recorder hours</label><input id="iSrt" type="number" value="120" min="0"></div>
|
||||
<div class="tok-calc-field"><label class="tok-calc-label">SDI capture hours</label><input id="iSdi" type="number" value="40" min="0"></div>
|
||||
<div class="tok-calc-field"><label class="tok-calc-label">Premiere seats</label><input id="iSeats" type="number" value="3" min="0"></div>
|
||||
<div class="tok-calc-field"><label class="tok-calc-label">Editor render min/mo</label><input id="iRender" type="number" value="240" min="0"></div>
|
||||
</div>
|
||||
<div class="tok-calc-out">
|
||||
<div class="tok-calc-out-total">
|
||||
<span class="tok-calc-out-label">Estimated monthly tokens</span>
|
||||
<span class="tok-calc-out-value" id="calcTokens">—</span>
|
||||
</div>
|
||||
<div class="tok-calc-out-total">
|
||||
<span class="tok-calc-out-label">At Professional tier</span>
|
||||
<span class="tok-calc-out-value" id="calcDollars" style="color:oklch(82% 0.10 25)">—</span>
|
||||
</div>
|
||||
<div class="tok-calc-out-aside" id="calcNote">Includes 2.4× business-hours Ingest multiplier. Excludes overage, peak-hour surcharge, codec entitlement, and the Token Velocity Modifier (TVM™), which fluctuates between 0.8× and 4.2× without notice.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footnote micro-print -->
|
||||
<div class="tok-footer">
|
||||
<p><b>Disclosures.</b> All rates quoted in Z-Tokens®, a non-transferable digital unit of account valid only within the Platform™. One (1) token is equivalent to 1.0 token at time of redemption, subject to the Token Velocity Modifier (TVM™), which is recalculated nightly and applied retroactively where contractually permitted. Tokens expire after 30 days unless rolled over with the Token Continuity Add-On (TCA, sold separately).</p>
|
||||
<p><b>Multipliers.</b> "Reliability Adjustment", "Real-Time Render Surcharge", and "Premium Baseband Multiplier" are not surcharges; they are <i>positive entitlements</i> that grant continued access to services for which you have already paid. Refusal to pay constitutes voluntary entitlement waiver.</p>
|
||||
<p><b>Token Compatibility Levy.</b> A 14% sustainability levy is automatically applied to all token consumption in support of the Platform's commitment to operational excellence. The levy is non-refundable, non-itemized, and not represented above.</p>
|
||||
<p><b>Forward-looking statements.</b> Anything resembling a price on this page is illustrative and is not, has not been, and will never be, a price. Pricing is determined exclusively by your assigned Customer Success Outcome Architect during quarterly value-realization workshops.</p>
|
||||
<p style="margin-top:14px;font-style:italic;opacity:0.7"><b>Z-AMPP</b> is the broadcast asset management platform you actually own. No token has ever been minted, charged, or considered. This page exists for purely educational purposes. Any resemblance to a real metered-compute pricing model is entirely intentional and deeply affectionate.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New token slide panel -->
|
||||
<div class="slide-overlay" id="tokenOverlay" onclick="closeNewTokenPanel()"></div>
|
||||
<div class="slide-panel" id="tokenPanel">
|
||||
<div class="slide-panel-header">
|
||||
<span class="slide-panel-title">New API token</span>
|
||||
<button class="btn btn-ghost btn-sm" onclick="closeNewTokenPanel()" style="padding:0;width:28px;height:28px;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 3l10 10M13 3L3 13"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="slide-panel-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="tokenName">Token name</label>
|
||||
<input type="text" id="tokenName" placeholder="e.g. Premiere Plugin, CI/CD Script">
|
||||
<div class="form-hint">A label to help you remember what this token is for.</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="tokenExpiry">Expiry</label>
|
||||
<select id="tokenExpiry">
|
||||
<option value="">No expiry</option>
|
||||
<option value="30">30 days</option>
|
||||
<option value="90">90 days</option>
|
||||
<option value="365">1 year</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide-panel-footer">
|
||||
<button class="btn btn-ghost" onclick="closeNewTokenPanel()">Cancel</button>
|
||||
<button class="btn btn-primary" id="createTokenBtn" onclick="createNewToken()">Create token</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/topbar-strip.js"></script>
|
||||
<script>
|
||||
let latestToken = null;
|
||||
function addToCart(tier) {
|
||||
const lines = [
|
||||
'You have selected the ' + tier + ' tier.',
|
||||
'',
|
||||
'A Customer Success Outcome Architect will reach out',
|
||||
'within 6–12 business days to schedule your initial',
|
||||
'discovery + value-alignment workshop.',
|
||||
'',
|
||||
'Until then, please continue using Z-AMPP for free.',
|
||||
'Because it is free. Because we built it ourselves.',
|
||||
];
|
||||
alert(lines.join('\n'));
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadTokens);
|
||||
// Calculator
|
||||
const tvm = 1.42; // current "Token Velocity Modifier"
|
||||
function calc() {
|
||||
const g = parseFloat(document.getElementById('iIngest').value || '0');
|
||||
const srt = parseFloat(document.getElementById('iSrt').value || '0');
|
||||
const sdi = parseFloat(document.getElementById('iSdi').value || '0');
|
||||
const seats = parseFloat(document.getElementById('iSeats').value || '0');
|
||||
const ren = parseFloat(document.getElementById('iRender').value || '0');
|
||||
// Made-up math
|
||||
const tokens = Math.round(
|
||||
g * 14.4 * 2.4 +
|
||||
srt * 60 * 4.8 * 1.22 +
|
||||
sdi * 60 * 9.2 * 1.8 +
|
||||
seats * 22000 +
|
||||
ren * 11.8 * 1.18
|
||||
);
|
||||
const withLevy = Math.round(tokens * 1.14 * tvm);
|
||||
document.getElementById('calcTokens').textContent = withLevy.toLocaleString();
|
||||
const dollars = withLevy / 1000 * 4.17;
|
||||
document.getElementById('calcDollars').textContent = '$' + Math.round(dollars).toLocaleString();
|
||||
}
|
||||
document.querySelectorAll('.tok-calc-field input').forEach(i => i.addEventListener('input', calc));
|
||||
calc();
|
||||
|
||||
async function loadTokens() {
|
||||
const r = await getTokens();
|
||||
const list = document.getElementById('tokensList');
|
||||
const empty = document.getElementById('tokensEmpty');
|
||||
|
||||
if (!r.success) {
|
||||
list.innerHTML = `<div class="text-sm text-tertiary">Could not load tokens.</div>`;
|
||||
return;
|
||||
async function renderBurnChart() {
|
||||
let realJobs = 0, realAssets = 0;
|
||||
try {
|
||||
const [jRes, aRes] = await Promise.all([
|
||||
fetch('/api/v1/jobs', { credentials: 'include' }),
|
||||
fetch('/api/v1/assets?limit=1', { credentials: 'include' }),
|
||||
]);
|
||||
if (jRes.ok) realJobs = (await jRes.json()).length;
|
||||
if (aRes.ok) realAssets = (await aRes.json()).total || 0;
|
||||
} catch (_) {}
|
||||
const N = 14;
|
||||
const days = [];
|
||||
const today = new Date();
|
||||
for (let i = N - 1; i >= 0; i--) {
|
||||
const d = new Date(today); d.setDate(d.getDate() - i);
|
||||
days.push(d);
|
||||
}
|
||||
|
||||
const tokens = r.data;
|
||||
if (!tokens.length) {
|
||||
list.style.display = 'none';
|
||||
empty.style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
|
||||
list.style.display = 'flex';
|
||||
empty.style.display = 'none';
|
||||
|
||||
list.innerHTML = tokens.map(t => {
|
||||
const created = t.created_at ? new Date(t.created_at).toLocaleDateString() : '—';
|
||||
const lastUsed = t.last_used_at ? new Date(t.last_used_at).toLocaleDateString() : 'Never';
|
||||
const expires = t.expires_at ? new Date(t.expires_at).toLocaleDateString() : 'Never';
|
||||
const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
|
||||
return `
|
||||
<div class="token-card">
|
||||
<div class="token-card-icon">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><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>
|
||||
</div>
|
||||
<div class="token-card-body">
|
||||
<div class="token-card-name">${esc(t.name)}</div>
|
||||
<div class="token-card-meta">
|
||||
<span class="token-prefix">${esc(t.token_prefix)}…</span>
|
||||
· Created ${created}
|
||||
· Last used: ${lastUsed}
|
||||
· Expires: ${isExpired ? '<span style="color:var(--status-red)">Expired</span>' : expires}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-sm" onclick="confirmRevoke('${t.id}','${esc(t.name)}')">Revoke</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function openNewTokenPanel() {
|
||||
document.getElementById('tokenName').value = '';
|
||||
document.getElementById('tokenExpiry').value = '';
|
||||
document.getElementById('tokenPanel').classList.add('open');
|
||||
document.getElementById('tokenOverlay').classList.add('open');
|
||||
}
|
||||
|
||||
function closeNewTokenPanel() {
|
||||
document.getElementById('tokenPanel').classList.remove('open');
|
||||
document.getElementById('tokenOverlay').classList.remove('open');
|
||||
}
|
||||
|
||||
async function createNewToken() {
|
||||
const name = document.getElementById('tokenName').value.trim();
|
||||
if (!name) { toast('Token name required', '', 'warning'); return; }
|
||||
|
||||
const expires_in_days = document.getElementById('tokenExpiry').value || null;
|
||||
const btn = document.getElementById('createTokenBtn');
|
||||
btn.disabled = true;
|
||||
|
||||
const r = await createToken({ name, expires_in_days: expires_in_days ? parseInt(expires_in_days) : null });
|
||||
btn.disabled = false;
|
||||
|
||||
if (r.success) {
|
||||
closeNewTokenPanel();
|
||||
latestToken = r.data.token;
|
||||
document.getElementById('newTokenValue').textContent = latestToken;
|
||||
document.getElementById('newTokenBanner').style.display = 'block';
|
||||
document.getElementById('newTokenBanner').scrollIntoView({ behavior: 'smooth' });
|
||||
toast('Token created', name, 'success');
|
||||
loadTokens();
|
||||
} else {
|
||||
toast('Failed to create token', r.error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmRevoke(id, name) {
|
||||
if (!confirm(`Revoke token "${name}"? Any scripts using it will stop working.`)) return;
|
||||
const r = await revokeToken(id);
|
||||
if (r.success) { toast('Token revoked', name, 'success'); loadTokens(); }
|
||||
else toast('Failed to revoke token', r.error, 'error');
|
||||
}
|
||||
|
||||
function copyToken() {
|
||||
if (!latestToken) return;
|
||||
navigator.clipboard.writeText(latestToken).then(() => {
|
||||
toast('Copied to clipboard', '', 'success');
|
||||
}).catch(() => {
|
||||
toast('Copy failed — select and copy manually', '', 'warning');
|
||||
function rng(seed) { let x = Math.sin(seed) * 10000; return x - Math.floor(x); }
|
||||
const series = days.map((d, i) => {
|
||||
const baseline = 8000 + realAssets * 18 + realJobs * 12;
|
||||
const wk = (d.getDay() === 0 || d.getDay() === 6) ? 0.55 : 1.0;
|
||||
const wave = 1 + 0.45 * Math.sin(i * 0.9) + 0.18 * Math.cos(i * 1.7);
|
||||
const noise = 0.8 + 0.4 * rng(i * 13 + 7);
|
||||
const total = Math.round(baseline * wk * wave * noise);
|
||||
const ingest = Math.round(total * (0.35 + 0.05 * rng(i * 3 + 1)));
|
||||
const recorders = Math.round(total * (0.30 + 0.04 * rng(i * 5 + 2)));
|
||||
const render = Math.round(total * (0.20 + 0.04 * rng(i * 7 + 3)));
|
||||
const overage = Math.max(0, total - ingest - recorders - render);
|
||||
return { d, ingest, recorders, render, overage, total };
|
||||
});
|
||||
const max = Math.max(...series.map(s => s.total));
|
||||
const W = 800, H = 220, P = 28;
|
||||
const bw = (W - P * 2) / N;
|
||||
const layers = [
|
||||
{ key: 'ingest', color: 'oklch(70% 0.18 200)' },
|
||||
{ key: 'recorders', color: 'oklch(62% 0.15 145)' },
|
||||
{ key: 'render', color: 'oklch(70% 0.18 80)' },
|
||||
{ key: 'overage', color: 'oklch(62% 0.22 25)' },
|
||||
];
|
||||
const bars = series.map((s, i) => {
|
||||
const x = P + i * bw + 4;
|
||||
let yAcc = H - P;
|
||||
const stack = layers.map(l => {
|
||||
const v = s[l.key] || 0;
|
||||
const h = (v / max) * (H - P * 2);
|
||||
yAcc -= h;
|
||||
return '<rect x="' + x + '" y="' + yAcc + '" width="' + (bw - 8) + '" height="' + h + '" fill="' + l.color + '" opacity="0.92" rx="1"/>';
|
||||
}).join('');
|
||||
const label = s.d.toLocaleDateString('en', { month: 'short', day: 'numeric' });
|
||||
const lbl = i % 2 === 0 ? '<text x="' + (x + (bw-8)/2) + '" y="' + (H - 8) + '" text-anchor="middle" font-size="10" fill="oklch(55% 0.04 215)" font-family="var(--font-mono)">' + label + '</text>' : '';
|
||||
return stack + lbl;
|
||||
}).join('');
|
||||
let grid = '';
|
||||
for (let k = 0; k <= 4; k++) {
|
||||
const y = P + (H - P * 2) * (k / 4);
|
||||
grid += '<line x1="' + P + '" y1="' + y + '" x2="' + (W - P + 12) + '" y2="' + y + '" stroke="oklch(35% 0.06 215 / 0.25)" stroke-width="0.5"/>';
|
||||
const tick = Math.round(max * (1 - k / 4));
|
||||
grid += '<text x="' + (W - P + 16) + '" y="' + (y + 3) + '" font-size="9" fill="oklch(50% 0.04 215)" font-family="var(--font-mono)">' + tick.toLocaleString() + '</text>';
|
||||
}
|
||||
document.getElementById('burnChart').innerHTML = grid + bars;
|
||||
const mtd = series.reduce((a, s) => a + s.total, 0);
|
||||
const eom = Math.round(mtd * 2.3);
|
||||
const peakIdx = series.reduce((max, s, i, arr) => s.total > arr[max].total ? i : max, 0);
|
||||
const tvm = (0.92 + 0.6 * rng(Date.now() / 60000 | 0)).toFixed(2);
|
||||
document.getElementById('statMtd').textContent = mtd.toLocaleString();
|
||||
document.getElementById('statMtdDelta').textContent = '+' + Math.round(rng(1) * 40 + 15) + '% vs prior period';
|
||||
document.getElementById('statEom').textContent = eom.toLocaleString();
|
||||
document.getElementById('statEomDelta').textContent = '+340% over plan';
|
||||
document.getElementById('statTvm').textContent = tvm + 'x';
|
||||
document.getElementById('statTvmTrend').textContent = parseFloat(tvm) > 1.2 ? 'spiking' : 'stable';
|
||||
document.getElementById('statPeak').textContent = series[peakIdx].total.toLocaleString();
|
||||
document.getElementById('statPeakDay').textContent = series[peakIdx].d.toLocaleDateString('en', { weekday: 'short' });
|
||||
}
|
||||
renderBurnChart();
|
||||
|
||||
function toast(title, msg, type = 'info') {
|
||||
const icons = {
|
||||
success: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M5 8l2 2 4-4"/></svg>`,
|
||||
error: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 5v4M8 11v.5"/></svg>`,
|
||||
warning: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2L1 14h14L8 2z"/><path d="M8 7v3M8 12v.5"/></svg>`,
|
||||
info: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 7v5M8 5v.5"/></svg>`,
|
||||
};
|
||||
const el = document.createElement('div');
|
||||
el.className = `toast toast--${type}`;
|
||||
el.innerHTML = `<div class="toast-icon">${icons[type]||icons.info}</div><div class="toast-body"><div class="toast-title">${esc(title)}</div>${msg?`<div class="toast-msg">${esc(msg)}</div>`:''}</div>`;
|
||||
document.getElementById('toastContainer').appendChild(el);
|
||||
setTimeout(() => el.remove(), 4000);
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
<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>Ingest — Wild Dragon</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
||||
<title>Ingest — Z-AMPP</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
.ingest-content {
|
||||
|
|
@ -157,13 +154,19 @@
|
|||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
|
||||
<a href="home.html" class="nav-item"><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="nav-item">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="projects.html" class="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="nav-item active">
|
||||
<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
|
||||
|
|
@ -180,12 +183,32 @@
|
|||
<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="tokens.html" class="nav-item"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M5.5 9.5h3a1.5 1.5 0 0 0 0-3h-1a1.5 1.5 0 0 1 0-3h3M8 3v1m0 8v1"/></svg>Tokens</a>
|
||||
<a href="edit.html" class="nav-item" target="_blank" rel="noopener">
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="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="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>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><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="main">
|
||||
|
|
@ -462,5 +485,11 @@
|
|||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Users — Wild Dragon</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
||||
<title>Users — Z-AMPP</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
.users-shell {
|
||||
|
|
@ -74,16 +71,22 @@
|
|||
<!-- Sidebar -->
|
||||
<nav class="sidebar" aria-label="Main navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="sidebar-brand-mark">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><path d="M8 1L2 5v6l6 4 6-4V5L8 1zm0 2.2L12 6v4l-4 2.7L4 10V6l4-2.8z"/></svg>
|
||||
</div>
|
||||
<span class="sidebar-brand-name">Wild Dragon</span>
|
||||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<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="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="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="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
|
||||
|
|
@ -100,11 +103,11 @@
|
|||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h8M2 12h5"/></svg>
|
||||
Jobs
|
||||
</a>
|
||||
<div class="sidebar-section-label">Admin</div>
|
||||
<a href="settings.html" class="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 1.5v1M8 13.5v1M1.5 8h1M13.5 8h1M3.2 3.2l.7.7M12.1 12.1l.7.7M12.1 3.9l-.7.7M3.9 12.1l-.7.7"/></svg>
|
||||
Settings
|
||||
<a href="#" id="editor-nav-link" class="nav-item" target="_blank" rel="noopener">
|
||||
<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="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="nav-item active">
|
||||
<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
|
||||
|
|
@ -568,5 +571,11 @@
|
|||
}
|
||||
</script>
|
||||
<script src="js/auth-guard.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('editor-nav-link');
|
||||
if(el){el.href=location.protocol+'//'+location.hostname+':47435/';}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue