// Shared sketchy primitives + helpers used across all screens
const { useState, useEffect, useMemo, useRef } = React;
// Variation card frame
function Variation({ num, title, desc, children, w }) {
return (
{num}
{title}
— {desc}
khs-tracker.app/{title.toLowerCase().replace(/\s+/g,'-')}
{children}
);
}
// Sketchy nav rail (sidebar)
function Sidebar({ active = 'home', compact = false }) {
const items = [
['home','Dashboard','◐'],
['part','Participants','◧'],
['auth','Authorizations','▤'],
['log','Service log','◔'],
['staff','Staff','◑'],
['rep','Reports','◬'],
['set','Settings','✦'],
];
return (
{!compact &&
menu
}
{items.map(([k,label,glyph]) => (
{glyph}
{!compact && {label}}
))}
);
}
// Top bar inside a sketch
function SketchTopBar({ title, search = true, right }) {
return (
{title}
{search &&
⌕ search participants…
}
{right || <>
⎘ export
+ new
>}
);
}
// Status dot
function StatusDot({ s='ok', label }) {
return
{label}
;
}
// Mini horizontal usage bar with auth/used/remain split
function UsageBar({ used = 60, remain = 40, warn = false, height = 12 }) {
const total = used + remain || 100;
const pUsed = (used/total)*100;
const fill = warn ? 'var(--r-orange)' : 'var(--sage)';
return (
);
}
// SVG sketchy gauge (semicircle)
function Gauge({ pct = 70, status = 'ok', label }) {
const r = 44;
const cx = 60, cy = 56;
const angle = Math.PI * (1 - pct/100);
const x = cx + r * Math.cos(angle);
const y = cy - r * Math.sin(angle);
const color = `var(--r-${status})`;
const dash = pct/100 * Math.PI * r;
const arcLen = Math.PI * r;
return (
);
}
// 12-month grid
function MonthGrid({ data, current = 4 /* index of current month, 0-11 */ }) {
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
return (
{months.map((m,i)=>{
const v = data[i] ?? null;
const past = i < current;
const cur = i === current;
const bg = v == null ? 'var(--paper-2)' :
v > 90 ? 'var(--r-orange)' :
v > 60 ? 'var(--sage)' :
v > 30 ? 'var(--sage-tint)' : 'var(--paper-2)';
return (
);
})}
);
}
// Vertical sparkline bars
function Spark({ data, height = 32, color = 'sage' }) {
const max = Math.max(...data, 1);
return (
{data.map((v,i)=>(
.85?'#f0c0b3':v/max>.6?'var(--sage)':'var(--sage-tint)',
borderRadius:'2px 2px 0 0'
}}/>
))}
);
}
// Circle initials avatar
function Avatar({ name = 'AP', tone = 'sage' }) {
const bg = tone==='sage'?'var(--sage-tint)': tone==='warn'?'#f4d8b6':'#fff';
return (
{name}
);
}
// Sketchy text strokes (for placeholder copy)
function Strokes({ lines = 3, widths = ['100%','85%','60%'] }) {
return (
{Array.from({length:lines}).map((_,i)=>(
))}
);
}
// Annotation note
function Note({ children, style }) {
return {children}
;
}
// Section header inside a sketch
function SkH({ children, kicker, right }) {
return (
{kicker && {kicker}}
{children}
{right && {right}}
);
}
Object.assign(window, {
Variation, Sidebar, SketchTopBar, StatusDot, UsageBar, Gauge,
MonthGrid, Spark, Avatar, Strokes, Note, SkH
});