Files
boc/aamos-ai-site/docs/vims/index.html
T
Bernt 58ca4e68db feat(boc): Complete Business Operations Center v1.0
- Go backend API with full CRUD for all modules (CRM, Sales, Finance, HR, Legal, Marketing, Support, Purchase, Inventory, Projects, Automation, Analytics)
- Rust analytics service with parallel report generation
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables, full migrations
- Redis cache, sessions, pub/sub
- Kafka event streaming with Zookeeper
- WebSocket hub for real-time updates
- Automation engine with cron jobs, workflows, event triggers
- JWT authentication, multi-tenant from start
- Docker Compose with all services
- Nginx reverse proxy with rate limiting
- Integration tests passing
- Feature gap analysis against Fortnox/Odoo/Visma

Refs: BOC-001
2026-07-12 12:41:35 +00:00

349 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VIMS API — AAMOS API Docs</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--navy: #0A2540; --blue: #635BFF; --blue-faint: #F0EFFF;
--text: #1a2332; --text-muted: #5a6880; --border: #e2e8f0;
--bg: #ffffff; --bg-sidebar: #f8fafc; --bg-code: #f1f5f9;
--sidebar-w: 260px; --header-h: 60px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', sans-serif; color: var(--text); background: var(--bg); font-size: 15px; line-height: 1.6; }
a { color: var(--blue); text-decoration: none; } a:hover { text-decoration: underline; }
.header { position: fixed; top: 0; left: 0; right: 0; height: var(--header-h); background: var(--navy); display: flex; align-items: center; padding: 0 24px; z-index: 100; gap: 16px; }
.header-logo { display: flex; align-items: center; gap: 10px; color: #fff; font-weight: 700; font-size: 18px; text-decoration: none; }
.header-logo-mark { width: 32px; height: 32px; background: var(--blue); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 800; color: #fff; }
.header-badge { background: rgba(99,91,255,0.25); color: #a5a0ff; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 20px; letter-spacing: 0.05em; }
.header-nav { margin-left: auto; display: flex; gap: 24px; align-items: center; }
.header-nav a { color: rgba(255,255,255,0.75); font-size: 14px; font-weight: 500; }
.header-nav a:hover { color: #fff; text-decoration: none; }
.btn-primary { background: var(--blue); color: #fff; padding: 8px 18px; border-radius: 8px; font-size: 13px; font-weight: 600; }
.hamburger { display: none; background: none; border: none; color: #fff; cursor: pointer; padding: 4px; }
.layout { display: flex; margin-top: var(--header-h); min-height: calc(100vh - var(--header-h)); }
.sidebar { width: var(--sidebar-w); background: var(--bg-sidebar); border-right: 1px solid var(--border); position: fixed; top: var(--header-h); bottom: 0; overflow-y: auto; padding: 24px 0; transition: transform 0.25s ease; }
.sidebar-section { margin-bottom: 8px; }
.sidebar-label { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; color: var(--text-muted); padding: 8px 20px 4px; text-transform: uppercase; }
.sidebar-link { display: flex; align-items: center; gap: 8px; padding: 7px 20px; color: var(--text-muted); font-size: 13.5px; font-weight: 500; transition: all 0.15s; border-left: 3px solid transparent; }
.sidebar-link:hover { color: var(--navy); background: var(--blue-faint); text-decoration: none; }
.sidebar-link.active { color: var(--blue); background: var(--blue-faint); border-left-color: var(--blue); }
.sidebar-link .icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
.main { margin-left: var(--sidebar-w); flex: 1; padding: 48px 56px; max-width: calc(900px + var(--sidebar-w)); }
.page-header { margin-bottom: 40px; }
.breadcrumb { font-size: 13px; color: var(--text-muted); margin-bottom: 12px; }
.breadcrumb a { color: var(--text-muted); } .breadcrumb a:hover { color: var(--blue); }
h1 { font-size: 32px; font-weight: 800; color: var(--navy); margin-bottom: 12px; }
.page-desc { font-size: 16px; color: var(--text-muted); }
h2 { font-size: 20px; font-weight: 700; color: var(--navy); margin: 40px 0 14px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
h3 { font-size: 15px; font-weight: 600; color: var(--text-muted); margin: 16px 0 8px; text-transform: uppercase; letter-spacing: 0.06em; font-size: 12px; }
p { color: var(--text-muted); margin-bottom: 12px; }
.divider { border: none; border-top: 1px solid var(--border); margin: 40px 0; }
/* Endpoint card */
.endpoint-card { border: 1px solid var(--border); border-radius: 12px; margin: 24px 0; overflow: hidden; }
.endpoint-header { display: flex; align-items: center; gap: 12px; padding: 18px 24px; background: var(--bg-sidebar); border-bottom: 1px solid var(--border); }
.method-badge { font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 6px; }
.method-post { background: #dcfce7; color: #15803d; }
.method-get { background: #dbeafe; color: #1d4ed8; }
.method-delete { background: #fff5f5; color: #dc2626; }
.method-put { background: #fff7ed; color: #d97706; }
.endpoint-path { font-family: 'JetBrains Mono', monospace; font-size: 14px; color: var(--navy); font-weight: 500; }
.endpoint-body { padding: 24px; }
.endpoint-desc { color: var(--text-muted); font-size: 14px; margin-bottom: 20px; }
/* Params table */
.params-table { width: 100%; border-collapse: collapse; font-size: 13.5px; margin: 8px 0 20px; }
.params-table th { text-align: left; padding: 8px 12px; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; color: var(--text-muted); border-bottom: 2px solid var(--border); text-transform: uppercase; }
.params-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.params-table tr:last-child td { border-bottom: none; }
.param-name { font-family: 'JetBrains Mono', monospace; color: var(--navy); font-size: 13px; }
.param-type { font-family: 'JetBrains Mono', monospace; color: var(--blue); font-size: 12px; }
.param-req { display: inline-block; background: #fef3c7; color: #92400e; font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; text-transform: uppercase; }
.param-opt { display: inline-block; background: var(--bg-code); color: var(--text-muted); font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; text-transform: uppercase; }
.param-desc { color: var(--text-muted); }
/* Code */
.code-wrap { position: relative; margin: 12px 0; }
.code-lang { position: absolute; top: 10px; right: 44px; font-size: 11px; font-weight: 600; color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 0.05em; font-family: 'Inter', sans-serif; }
.copy-btn { position: absolute; top: 8px; right: 10px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15); color: rgba(255,255,255,0.7); border-radius: 6px; padding: 4px 8px; font-size: 11px; cursor: pointer; font-family: 'Inter', sans-serif; font-weight: 500; transition: all 0.15s; }
.copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
.copy-btn.copied { color: #4ade80; border-color: #4ade80; }
pre { background: #0f1923; color: #e2e8f0; border-radius: 10px; padding: 18px 20px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.7; }
code { font-family: 'JetBrains Mono', monospace; }
.inline-code { background: var(--bg-code); color: #c2410c; padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'JetBrains Mono', monospace; }
.tok-kw { color: #c792ea; } .tok-str { color: #c3e88d; } .tok-num { color: #f78c6c; } .tok-cmt { color: #546e7a; }
.tok-url { color: #80cbc4; } .tok-key { color: #89ddff; } .tok-bool { color: #ff5572; }
.tok-method { color: #ffcb6b; font-weight: 600; } .tok-path { color: #80cbc4; }
.info-box { background: var(--blue-faint); border: 1px solid #d4d0ff; border-radius: 10px; padding: 16px 20px; display: flex; gap: 12px; margin: 20px 0; }
.info-box-icon { font-size: 18px; flex-shrink: 0; }
.info-box-body { font-size: 13.5px; color: var(--text); }
.info-box-body strong { color: var(--navy); }
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); z-index: 50; }
.sidebar.open { transform: translateX(0); }
.main { margin-left: 0; padding: 24px 20px; }
.hamburger { display: flex; }
.header-nav { display: none; }
.overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 40; }
.overlay.visible { display: block; }
.endpoint-header { flex-wrap: wrap; }
}
</style>
</head>
<body>
<header class="header">
<button class="hamburger" id="hamburger" aria-label="Menu">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect y="4" width="22" height="2" rx="1" fill="white"/><rect y="10" width="22" height="2" rx="1" fill="white"/><rect y="16" width="22" height="2" rx="1" fill="white"/></svg>
</button>
<a href="/docs/" class="header-logo"><div class="header-logo-mark">A</div>AAMOS</a>
<span class="header-badge">API DOCS</span>
<nav class="header-nav">
<a href="https://aamos.ai">Home</a>
<a href="/docs/">Docs</a>
<a href="https://aamos.ai/signup/" class="btn-primary">Get API Key</a>
</nav>
</header>
<div class="overlay" id="overlay"></div>
<div class="layout">
<nav class="sidebar" id="sidebar">
<div class="sidebar-section">
<div class="sidebar-label">Getting Started</div>
<a href="/docs/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h12v2H2zm0 4h12v2H2zm0 4h8v2H2z"/></svg>Overview</a>
<a href="/docs/authentication/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a4 4 0 110 8A4 4 0 018 1zm0 10c-4 0-7 2-7 3v1h14v-1c0-1-3-3-7-3z"/></svg>Authentication</a>
</div>
<div class="sidebar-section">
<div class="sidebar-label">APIs</div>
<a href="/docs/vims/" class="sidebar-link active"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M1 3h14v2H1zm2 4h10v2H3zm2 4h6v2H5z"/></svg>VIMS</a>
<a href="/docs/reality-alerts/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1L1 14h14L8 1zm0 3l5 9H3l5-9zm-1 3v3h2V7H7zm0 4v2h2v-2H7z"/></svg>Reality Alerts</a>
<a href="/docs/kyz/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a4 4 0 110 8A4 4 0 018 1zm0 10c-4 0-7 2-7 3v1h14v-1c0-1-3-3-7-3z"/></svg>KYZ</a>
<a href="/docs/modules/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h5v5H2zm7 0h5v5H9zm-7 7h5v5H2zm7 0h5v5H9z"/></svg>AAMOS Modules</a>
</div>
<div class="sidebar-section">
<div class="sidebar-label">On this page</div>
<a href="#analyze" class="sidebar-link" style="font-size:12.5px">Analyze Image</a>
<a href="#baseline" class="sidebar-link" style="font-size:12.5px">Set Baseline</a>
<a href="#list-objects" class="sidebar-link" style="font-size:12.5px">List Objects</a>
<a href="#get-analysis" class="sidebar-link" style="font-size:12.5px">Get Analysis</a>
</div>
</nav>
<main class="main">
<div class="page-header">
<div class="breadcrumb"><a href="/docs/">Docs</a> / VIMS API</div>
<h1>VIMS API Reference</h1>
<p class="page-desc">Visual Integrity Monitoring System — detect changes, anomalies, and deviations in monitored objects using computer vision.</p>
</div>
<div class="info-box">
<span class="info-box-icon">📍</span>
<div class="info-box-body"><strong>Base path:</strong> All VIMS endpoints are under <code class="inline-code">https://api.aamos.ai/v1/vims/</code></div>
</div>
<h2 id="analyze">POST /v1/vims/analyze</h2>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/v1/vims/analyze</span>
</div>
<div class="endpoint-body">
<p class="endpoint-desc">Analyze an image for changes against the registered baseline for a given object. Returns a change score, risk level, and detected anomalies. The baseline is automatically updated if drift is within acceptable thresholds.</p>
<h3>Request — multipart/form-data</h3>
<table class="params-table">
<thead><tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
<tbody>
<tr><td class="param-name">image</td><td class="param-type">file</td><td><span class="param-req">Required</span></td><td class="param-desc">The image to analyze. Accepted formats: JPEG, PNG, WebP. Max 10 MB.</td></tr>
<tr><td class="param-name">object_id</td><td class="param-type">string</td><td><span class="param-req">Required</span></td><td class="param-desc">Your unique identifier for the monitored object. Must have an existing baseline.</td></tr>
<tr><td class="param-name">lat</td><td class="param-type">float</td><td><span class="param-opt">Optional</span></td><td class="param-desc">GPS latitude of the image capture location.</td></tr>
<tr><td class="param-name">lng</td><td class="param-type">float</td><td><span class="param-opt">Optional</span></td><td class="param-desc">GPS longitude of the image capture location.</td></tr>
</tbody>
</table>
<h3>Example Request</h3>
<div class="code-wrap">
<span class="code-lang">bash</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre><span class="tok-kw">curl</span> -X POST <span class="tok-str">https://api.aamos.ai/v1/vims/analyze</span> \
-H <span class="tok-str">"Authorization: Bearer ak_live_YOUR_KEY"</span> \
-F <span class="tok-str">"image=@/path/to/photo.jpg"</span> \
-F <span class="tok-str">"object_id=site-front-gate"</span> \
-F <span class="tok-str">"lat=59.3293"</span> \
-F <span class="tok-str">"lng=18.0686"</span></pre>
</div>
<h3>Response — 200 OK</h3>
<div class="code-wrap">
<span class="code-lang">json</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>{
<span class="tok-key">"analysis_id"</span>: <span class="tok-str">"an_7f3a9b2c1d"</span>,
<span class="tok-key">"object_id"</span>: <span class="tok-str">"site-front-gate"</span>,
<span class="tok-key">"change_detected"</span>: <span class="tok-bool">true</span>,
<span class="tok-key">"change_score"</span>: <span class="tok-num">0.74</span>,
<span class="tok-key">"risk_level"</span>: <span class="tok-str">"high"</span>,
<span class="tok-key">"detections"</span>: [
{
<span class="tok-key">"type"</span>: <span class="tok-str">"structural_change"</span>,
<span class="tok-key">"confidence"</span>: <span class="tok-num">0.91</span>,
<span class="tok-key">"bbox"</span>: [<span class="tok-num">120</span>, <span class="tok-num">45</span>, <span class="tok-num">380</span>, <span class="tok-num">290</span>],
<span class="tok-key">"label"</span>: <span class="tok-str">"Gate damage detected"</span>
}
],
<span class="tok-key">"baseline_updated"</span>: <span class="tok-bool">false</span>,
<span class="tok-key">"analyzed_at"</span>: <span class="tok-str">"2025-01-15T10:30:00Z"</span>
}</pre>
</div>
<h3>Risk Levels</h3>
<p>The <code class="inline-code">risk_level</code> field reflects the severity of detected change:</p>
<table class="params-table">
<thead><tr><th>Value</th><th>change_score range</th><th>Meaning</th></tr></thead>
<tbody>
<tr><td class="param-name">none</td><td class="param-type">0.0 0.1</td><td class="param-desc">No meaningful change detected.</td></tr>
<tr><td class="param-name">low</td><td class="param-type">0.1 0.3</td><td class="param-desc">Minor variation, within expected range.</td></tr>
<tr><td class="param-name">medium</td><td class="param-type">0.3 0.6</td><td class="param-desc">Notable change, review recommended.</td></tr>
<tr><td class="param-name">high</td><td class="param-type">0.6 1.0</td><td class="param-desc">Significant change, immediate review required.</td></tr>
</tbody>
</table>
</div>
</div>
<h2 id="baseline">POST /v1/vims/baseline</h2>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/v1/vims/baseline</span>
</div>
<div class="endpoint-body">
<p class="endpoint-desc">Register a new baseline image for an object. The baseline is the reference state used for all subsequent analyze calls. You can update baselines as needed.</p>
<h3>Request — multipart/form-data</h3>
<table class="params-table">
<thead><tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
<tbody>
<tr><td class="param-name">object_id</td><td class="param-type">string</td><td><span class="param-req">Required</span></td><td class="param-desc">Your unique identifier for this object. Will be created if it doesn't exist.</td></tr>
<tr><td class="param-name">image</td><td class="param-type">file</td><td><span class="param-req">Required</span></td><td class="param-desc">The baseline image. JPEG, PNG, or WebP. Max 10 MB.</td></tr>
</tbody>
</table>
<h3>Example Request</h3>
<div class="code-wrap">
<span class="code-lang">bash</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre><span class="tok-kw">curl</span> -X POST <span class="tok-str">https://api.aamos.ai/v1/vims/baseline</span> \
-H <span class="tok-str">"Authorization: Bearer ak_live_YOUR_KEY"</span> \
-F <span class="tok-str">"object_id=site-front-gate"</span> \
-F <span class="tok-str">"image=@/path/to/baseline.jpg"</span></pre>
</div>
<h3>Response — 201 Created</h3>
<div class="code-wrap">
<span class="code-lang">json</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>{
<span class="tok-key">"baseline_id"</span>: <span class="tok-str">"bl_3d8e1f7a9b"</span>,
<span class="tok-key">"object_id"</span>: <span class="tok-str">"site-front-gate"</span>,
<span class="tok-key">"created_at"</span>: <span class="tok-str">"2025-01-15T09:00:00Z"</span>
}</pre>
</div>
</div>
</div>
<h2 id="list-objects">GET /v1/vims/objects</h2>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/v1/vims/objects</span>
</div>
<div class="endpoint-body">
<p class="endpoint-desc">List all monitored objects registered under your API key, with their baseline count and last analysis timestamp.</p>
<h3>Example Request</h3>
<div class="code-wrap">
<span class="code-lang">bash</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre><span class="tok-kw">curl</span> <span class="tok-str">https://api.aamos.ai/v1/vims/objects</span> \
-H <span class="tok-str">"Authorization: Bearer ak_live_YOUR_KEY"</span></pre>
</div>
<h3>Response — 200 OK</h3>
<div class="code-wrap">
<span class="code-lang">json</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>{
<span class="tok-key">"objects"</span>: [
{
<span class="tok-key">"object_id"</span>: <span class="tok-str">"site-front-gate"</span>,
<span class="tok-key">"baseline_count"</span>: <span class="tok-num">3</span>,
<span class="tok-key">"last_analysis"</span>: <span class="tok-str">"2025-01-15T10:30:00Z"</span>
},
{
<span class="tok-key">"object_id"</span>: <span class="tok-str">"server-rack-a1"</span>,
<span class="tok-key">"baseline_count"</span>: <span class="tok-num">1</span>,
<span class="tok-key">"last_analysis"</span>: <span class="tok-str">"2025-01-14T14:15:00Z"</span>
}
]
}</pre>
</div>
</div>
</div>
<h2 id="get-analysis">GET /v1/vims/analysis/:analysis_id</h2>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/v1/vims/analysis/:analysis_id</span>
</div>
<div class="endpoint-body">
<p class="endpoint-desc">Retrieve the full details of a previously completed analysis by its ID.</p>
<h3>Path Parameters</h3>
<table class="params-table">
<thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td class="param-name">analysis_id</td><td class="param-type">string</td><td class="param-desc">The analysis ID returned by <code class="inline-code">POST /v1/vims/analyze</code>.</td></tr>
</tbody>
</table>
<h3>Example Request</h3>
<div class="code-wrap">
<span class="code-lang">bash</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre><span class="tok-kw">curl</span> <span class="tok-str">https://api.aamos.ai/v1/vims/analysis/an_7f3a9b2c1d</span> \
-H <span class="tok-str">"Authorization: Bearer ak_live_YOUR_KEY"</span></pre>
</div>
<h3>Response — 200 OK</h3>
<div class="code-wrap">
<span class="code-lang">json</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>{
<span class="tok-key">"analysis_id"</span>: <span class="tok-str">"an_7f3a9b2c1d"</span>,
<span class="tok-key">"object_id"</span>: <span class="tok-str">"site-front-gate"</span>,
<span class="tok-key">"change_detected"</span>: <span class="tok-bool">true</span>,
<span class="tok-key">"change_score"</span>: <span class="tok-num">0.74</span>,
<span class="tok-key">"risk_level"</span>: <span class="tok-str">"high"</span>,
<span class="tok-key">"detections"</span>: [
{
<span class="tok-key">"type"</span>: <span class="tok-str">"structural_change"</span>,
<span class="tok-key">"confidence"</span>: <span class="tok-num">0.91</span>,
<span class="tok-key">"bbox"</span>: [<span class="tok-num">120</span>, <span class="tok-num">45</span>, <span class="tok-num">380</span>, <span class="tok-num">290</span>],
<span class="tok-key">"label"</span>: <span class="tok-str">"Gate damage detected"</span>
}
],
<span class="tok-key">"baseline_updated"</span>: <span class="tok-bool">false</span>,
<span class="tok-key">"baseline_id"</span>: <span class="tok-str">"bl_3d8e1f7a9b"</span>,
<span class="tok-key">"image_url"</span>: <span class="tok-str">"https://storage.aamos.ai/analyses/an_7f3a9b2c1d.jpg"</span>,
<span class="tok-key">"analyzed_at"</span>: <span class="tok-str">"2025-01-15T10:30:00Z"</span>
}</pre>
</div>
</div>
</div>
</main>
</div>
<script>
function copyCode(btn) {
const pre = btn.closest('.code-wrap').querySelector('pre');
navigator.clipboard.writeText(pre.innerText || pre.textContent).then(() => {
btn.textContent = 'Copied!'; btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
});
}
const hamburger = document.getElementById('hamburger');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
hamburger.addEventListener('click', () => { sidebar.classList.toggle('open'); overlay.classList.toggle('visible'); });
overlay.addEventListener('click', () => { sidebar.classList.remove('open'); overlay.classList.remove('visible'); });
</script>
</body>
</html>