feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops
- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help - Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete - Sigil scramble post-forge uniquification and Dispense Reveal ceremony - Full system check, desktop push, BITS/host-binary persistence, Path Tracer - Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav - README documents alerts, sigil scramble, and pack-usb workflow - USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
This commit is contained in:
@@ -125,6 +125,7 @@ export default function CursorFire() {
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="cursor-fire-fx"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
|
||||
@@ -364,6 +364,24 @@
|
||||
color: var(--neon-green);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.system-status-bar {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.system-status-bar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.compare-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -73,12 +73,12 @@ interface ActivityPulseProps {
|
||||
items: { id: string; label: string; ok: boolean; time?: string }[];
|
||||
}
|
||||
|
||||
export function ActivityPulse({ items }: ActivityPulseProps) {
|
||||
export function ActivityPulse({ items, sample = false }: ActivityPulseProps & { sample?: boolean }) {
|
||||
if (items.length === 0) {
|
||||
return <p className="activity-empty font-tech">Awaiting fleet activity…</p>;
|
||||
}
|
||||
return (
|
||||
<div className="activity-pulse">
|
||||
<div className={`activity-pulse${sample ? ' sample-activity' : ''}`}>
|
||||
{items.slice(0, 12).map((item) => (
|
||||
<div key={item.id} className={`activity-blip ${item.ok ? 'ok' : 'bad'}`} title={item.time || item.label}>
|
||||
<span className="activity-blip-core" />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { SacredMotif, type MotifName } from './motifs';
|
||||
|
||||
const ACCENT_MOTIFS: Record<string, [MotifName, MotifName]> = {
|
||||
cyan: ['hex', 'seed'],
|
||||
magenta: ['vesica', 'yantra'],
|
||||
amber: ['spiral', 'key'],
|
||||
green: ['seed', 'hex'],
|
||||
purple: ['metatron', 'torus'],
|
||||
brass: ['flower', 'key'],
|
||||
gold: ['yantra', 'spiral'],
|
||||
};
|
||||
|
||||
/** Corner watermarks inside neon cards */
|
||||
export default function SacredCardWatermark({ accent = 'brass' }: { accent?: string }) {
|
||||
const [tl, br] = ACCENT_MOTIFS[accent] ?? ACCENT_MOTIFS.brass;
|
||||
return (
|
||||
<>
|
||||
<div className="neon-card-sacred neon-card-sacred--tl" aria-hidden>
|
||||
<SacredMotif name={tl} opacity={0.5} />
|
||||
</div>
|
||||
<div className="neon-card-sacred neon-card-sacred--tr" aria-hidden>
|
||||
<SacredMotif name="seed" opacity={0.35} />
|
||||
</div>
|
||||
<div className="neon-card-sacred neon-card-sacred--br" aria-hidden>
|
||||
<SacredMotif name={br} opacity={0.45} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Layer-specific overrides (shared tokens in sacred-geometry.css) */
|
||||
.sacred-layer__corner--mid-l {
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.sacred-layer__corner--mid-r {
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.sacred-layer__corner--mid-l svg,
|
||||
.sacred-layer__corner--mid-r svg {
|
||||
animation: sacred-geo-rotate 220s linear infinite;
|
||||
}
|
||||
|
||||
.sacred-layer__corner--mid-r svg {
|
||||
animation-direction: reverse;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { SacredMotif } from './motifs';
|
||||
import './SacredGeometryLayer.css';
|
||||
|
||||
/** Viewport-wide sacred geometry — corners, keys, wisdom rail */
|
||||
export default function SacredGeometryLayer() {
|
||||
return (
|
||||
<div className="sacred-layer" aria-hidden>
|
||||
<div className="sacred-layer__corner sacred-layer__corner--tl">
|
||||
<SacredMotif name="metatron" opacity={0.55} />
|
||||
</div>
|
||||
<div className="sacred-layer__corner sacred-layer__corner--tr">
|
||||
<SacredMotif name="yantra" opacity={0.5} />
|
||||
</div>
|
||||
<div className="sacred-layer__corner sacred-layer__corner--bl">
|
||||
<SacredMotif name="spiral" opacity={0.5} />
|
||||
</div>
|
||||
<div className="sacred-layer__corner sacred-layer__corner--br">
|
||||
<SacredMotif name="key" opacity={0.55} />
|
||||
</div>
|
||||
<div className="sacred-layer__corner sacred-layer__corner--mid-l">
|
||||
<SacredMotif name="vesica" opacity={0.45} />
|
||||
</div>
|
||||
<div className="sacred-layer__corner sacred-layer__corner--mid-r">
|
||||
<SacredMotif name="torus" opacity={0.45} />
|
||||
</div>
|
||||
|
||||
<div className="sacred-layer__keys">
|
||||
<span className="sacred-key sacred-key--1" title="Shell index">
|
||||
◈
|
||||
</span>
|
||||
<span className="sacred-key sacred-key--2" title="Ley ledger">
|
||||
✦
|
||||
</span>
|
||||
<span className="sacred-key sacred-key--3" title="Root chord">
|
||||
◎
|
||||
</span>
|
||||
<span className="sacred-key sacred-key--4" title="Archive gate">
|
||||
ψ
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="sacred-layer__wisdom-rail font-tech" title="Whispers from the deck">
|
||||
<span>Ω</span>
|
||||
<span>∇</span>
|
||||
<span>∞</span>
|
||||
<span>⌬</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { SacredMotif } from './motifs';
|
||||
|
||||
/** Optional sacred divider + motif beside page titles */
|
||||
export default function SacredPageHeader({
|
||||
children,
|
||||
className = '',
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<header className={`page-header page-header--sacred ${className}`.trim()}>
|
||||
{children}
|
||||
<div className="page-header-sacred-motif" aria-hidden>
|
||||
<SacredMotif name="hex" opacity={0.55} />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
292
server/web/src/components/Visual/sacredGeometry/motifs.tsx
Normal file
292
server/web/src/components/Visual/sacredGeometry/motifs.tsx
Normal file
@@ -0,0 +1,292 @@
|
||||
/** Reusable sacred-geometry SVG motifs — decorative only (aria-hidden at call sites). */
|
||||
|
||||
type MotifProps = {
|
||||
className?: string;
|
||||
stroke?: string;
|
||||
opacity?: number;
|
||||
};
|
||||
|
||||
const DEFAULT_STROKE = '#c9a227';
|
||||
|
||||
export function FlowerOfLifeWatermark({ className = '', stroke = DEFAULT_STROKE, opacity = 0.55 }: MotifProps) {
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
const R = 32;
|
||||
const petalAngles = [0, 60, 120, 180, 240, 300];
|
||||
const petals = petalAngles.map((deg) => {
|
||||
const rad = (deg * Math.PI) / 180;
|
||||
return { x: cx + R * Math.cos(rad), y: cy + R * Math.sin(rad) };
|
||||
});
|
||||
const starR = R * 1.15;
|
||||
const starPts = Array.from({ length: 5 }, (_, i) => {
|
||||
const rad = ((i * 72 - 90) * Math.PI) / 180;
|
||||
return { x: cx + starR * Math.cos(rad), y: cy + starR * Math.sin(rad) };
|
||||
});
|
||||
const starPath = starPts.map((p, i) => (i === 0 ? `M${p.x},${p.y}` : `L${p.x},${p.y}`)).join(' ') + ' Z';
|
||||
const triR = R * 0.7;
|
||||
const triUp = [0, 120, 240]
|
||||
.map((d) => {
|
||||
const rad = ((d - 90) * Math.PI) / 180;
|
||||
return `${cx + triR * Math.cos(rad)},${cy + triR * Math.sin(rad)}`;
|
||||
})
|
||||
.join(' ');
|
||||
const triDown = [60, 180, 300]
|
||||
.map((d) => {
|
||||
const rad = ((d - 90) * Math.PI) / 180;
|
||||
return `${cx + triR * Math.cos(rad)},${cy + triR * Math.sin(rad)}`;
|
||||
})
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity}>
|
||||
<circle cx={cx} cy={cy} r={R * 1.35} fill="none" stroke={stroke} strokeWidth="0.18" strokeDasharray="1.2 1.8" />
|
||||
<circle cx={cx} cy={cy} r={R} fill="none" stroke={stroke} strokeWidth="0.22" />
|
||||
<circle cx={cx} cy={cy} r={R * 0.5} fill="none" stroke={stroke} strokeWidth="0.18" strokeDasharray="0.6 1.2" />
|
||||
{petals.map((p, i) => (
|
||||
<circle key={i} cx={p.x} cy={p.y} r={R} fill="none" stroke={stroke} strokeWidth="0.16" opacity="0.7" />
|
||||
))}
|
||||
<path d={starPath} fill="none" stroke="#ff8c00" strokeWidth="0.2" strokeLinejoin="round" opacity="0.6" />
|
||||
<polygon points={triUp} fill="none" stroke={stroke} strokeWidth="0.2" opacity="0.8" />
|
||||
<polygon points={triDown} fill="none" stroke={stroke} strokeWidth="0.2" opacity="0.8" />
|
||||
<circle cx={cx} cy={cy} r="0.6" fill={stroke} opacity="0.9" />
|
||||
{starPts.map((p, i) => (
|
||||
<line key={i} x1={cx} y1={cy} x2={p.x} y2={p.y} stroke={stroke} strokeWidth="0.1" opacity="0.35" />
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SeedOfLife({ className = '', stroke = DEFAULT_STROKE, opacity = 0.5 }: MotifProps) {
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
const r = 14;
|
||||
const centers = [{ x: cx, y: cy }];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (i * 60 * Math.PI) / 180;
|
||||
centers.push({ x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) });
|
||||
}
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity}>
|
||||
<circle cx={cx} cy={cy} r={r * 2.2} fill="none" stroke={stroke} strokeWidth="0.2" strokeDasharray="0.8 1.4" />
|
||||
{centers.map((c, i) => (
|
||||
<circle key={i} cx={c.x} cy={c.y} r={r} fill="none" stroke={stroke} strokeWidth="0.18" />
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MetatronCube({ className = '', stroke = DEFAULT_STROKE, opacity = 0.45 }: MotifProps) {
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
const r = 28;
|
||||
const pts: { x: number; y: number }[] = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = ((i * 60 - 90) * Math.PI) / 180;
|
||||
pts.push({ x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) });
|
||||
}
|
||||
const inner = r * 0.55;
|
||||
const innerPts: { x: number; y: number }[] = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = ((i * 60 - 90) * Math.PI) / 180;
|
||||
innerPts.push({ x: cx + inner * Math.cos(a), y: cy + inner * Math.sin(a) });
|
||||
}
|
||||
const lines: [number, number][] = [];
|
||||
for (let i = 0; i < pts.length; i++) {
|
||||
for (let j = i + 1; j < pts.length; j++) lines.push([i, j]);
|
||||
}
|
||||
for (let i = 0; i < innerPts.length; i++) {
|
||||
for (let j = i + 1; j < innerPts.length; j++) lines.push([i + 6, j + 6]);
|
||||
}
|
||||
pts.forEach((_, i) => lines.push([i, i + 6]));
|
||||
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} stroke={stroke} strokeWidth="0.14" fill="none">
|
||||
<circle cx={cx} cy={cy} r={r * 1.05} strokeWidth="0.16" />
|
||||
<circle cx={cx} cy={cy} r={inner} strokeDasharray="0.5 1" />
|
||||
{lines.map(([a, b], i) => {
|
||||
const p1 = a < 6 ? pts[a] : innerPts[a - 6];
|
||||
const p2 = b < 6 ? pts[b] : innerPts[b - 6];
|
||||
return <line key={i} x1={p1.x} y1={p1.y} x2={p2.x} y2={p2.y} opacity="0.5" />;
|
||||
})}
|
||||
{[...pts, ...innerPts].map((p, i) => (
|
||||
<circle key={`d${i}`} cx={p.x} cy={p.y} r="0.5" fill={stroke} stroke="none" />
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function VesicaPiscis({ className = '', stroke = DEFAULT_STROKE, opacity = 0.5 }: MotifProps) {
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
const r = 22;
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} fill="none" stroke={stroke}>
|
||||
<circle cx={cx - r * 0.5} cy={cy} r={r} strokeWidth="0.2" />
|
||||
<circle cx={cx + r * 0.5} cy={cy} r={r} strokeWidth="0.2" />
|
||||
<path
|
||||
d={`M ${cx} ${cy - r * 0.87} A ${r * 0.5} ${r * 0.87} 0 0 1 ${cx} ${cy + r * 0.87} A ${r * 0.5} ${r * 0.87} 0 0 1 ${cx} ${cy - r * 0.87}`}
|
||||
stroke="#00f5ff"
|
||||
strokeWidth="0.16"
|
||||
opacity="0.6"
|
||||
/>
|
||||
<line x1={cx} y1={cy - r} x2={cx} y2={cy + r} strokeWidth="0.12" opacity="0.35" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function HexLattice({ className = '', stroke = DEFAULT_STROKE, opacity = 0.35 }: MotifProps) {
|
||||
const hex = (cx: number, cy: number, s: number) => {
|
||||
const pts = Array.from({ length: 6 }, (_, i) => {
|
||||
const a = ((60 * i - 30) * Math.PI) / 180;
|
||||
return `${cx + s * Math.cos(a)},${cy + s * Math.sin(a)}`;
|
||||
}).join(' ');
|
||||
return <polygon key={`${cx}-${cy}`} points={pts} />;
|
||||
};
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} fill="none" stroke={stroke} strokeWidth="0.12">
|
||||
{hex(50, 50, 18)}
|
||||
{hex(50, 32, 10)}
|
||||
{hex(50, 68, 10)}
|
||||
{hex(34, 41, 10)}
|
||||
{hex(66, 41, 10)}
|
||||
{hex(34, 59, 10)}
|
||||
{hex(66, 59, 10)}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function GoldenSpiral({ className = '', stroke = DEFAULT_STROKE, opacity = 0.4 }: MotifProps) {
|
||||
const cx = 18;
|
||||
const cy = 82;
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} fill="none" stroke={stroke} strokeWidth="0.18">
|
||||
<rect x="18" y="18" width="64" height="64" strokeDasharray="1 2" opacity="0.35" />
|
||||
<path
|
||||
d="M 18 82 A 64 64 0 0 1 82 18 A 40 40 0 0 1 58 42 A 24 24 0 0 1 42 58 A 14 14 0 0 1 50 50"
|
||||
stroke="#ffb020"
|
||||
/>
|
||||
<circle cx={cx} cy={cy} r="1" fill={stroke} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function TorusRings({ className = '', stroke = DEFAULT_STROKE, opacity = 0.42 }: MotifProps) {
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} fill="none" stroke={stroke}>
|
||||
{[34, 26, 18, 10].map((r, i) => (
|
||||
<ellipse
|
||||
key={r}
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
rx={r}
|
||||
ry={r * (0.55 + i * 0.08)}
|
||||
strokeWidth="0.16"
|
||||
transform={`rotate(${i * 12} ${cx} ${cy})`}
|
||||
/>
|
||||
))}
|
||||
<circle cx={cx} cy={cy} r="2" fill={stroke} opacity="0.8" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/** Whimsical “key to knowledge” — geometric bow + shaft */
|
||||
export function KnowledgeKey({ className = '', stroke = DEFAULT_STROKE, opacity = 0.55 }: MotifProps) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} fill="none" stroke={stroke} strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="38" cy="38" r="16" strokeWidth="0.35" />
|
||||
<circle cx="38" cy="38" r="8" stroke="#00f5ff" strokeWidth="0.25" />
|
||||
<polygon
|
||||
points="38,22 46,30 38,38 30,30"
|
||||
strokeWidth="0.2"
|
||||
fill="rgba(201,162,39,0.08)"
|
||||
/>
|
||||
<line x1="50" y1="50" x2="88" y2="88" strokeWidth="0.35" />
|
||||
<rect x="72" y="72" width="8" height="8" strokeWidth="0.22" transform="rotate(45 76 76)" />
|
||||
<rect x="80" y="64" width="6" height="6" strokeWidth="0.2" transform="rotate(45 83 67)" />
|
||||
<path d="M 62 62 L 68 56 M 70 70 L 76 64" strokeWidth="0.18" opacity="0.7" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SriYantraLite({ className = '', stroke = DEFAULT_STROKE, opacity = 0.4 }: MotifProps) {
|
||||
const cx = 50;
|
||||
const cy = 52;
|
||||
const tri = (r: number, flip: boolean) => {
|
||||
const pts = [0, 120, 240].map((d) => {
|
||||
const rad = ((d - 90) * Math.PI) / 180;
|
||||
const y = (flip ? -1 : 1) * r * Math.sin(rad);
|
||||
return `${cx + r * Math.cos(rad)},${cy + y}`;
|
||||
});
|
||||
return pts.join(' ');
|
||||
};
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity={opacity} fill="none" stroke={stroke} strokeWidth="0.16">
|
||||
<circle cx={cx} cy={cy} r="38" strokeDasharray="1.5 2" />
|
||||
<polygon points={tri(32, false)} />
|
||||
<polygon points={tri(24, true)} stroke="#ff2da6" opacity="0.65" />
|
||||
<polygon points={tri(16, false)} stroke="#00f5ff" opacity="0.55" />
|
||||
<circle cx={cx} cy={cy} r="1.2" fill={stroke} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export type MotifName =
|
||||
| 'flower'
|
||||
| 'seed'
|
||||
| 'metatron'
|
||||
| 'vesica'
|
||||
| 'hex'
|
||||
| 'spiral'
|
||||
| 'torus'
|
||||
| 'key'
|
||||
| 'yantra';
|
||||
|
||||
export function SacredMotif({
|
||||
name,
|
||||
className = '',
|
||||
stroke,
|
||||
opacity,
|
||||
}: MotifProps & { name: MotifName }) {
|
||||
const props = { className, stroke, opacity };
|
||||
switch (name) {
|
||||
case 'seed':
|
||||
return <SeedOfLife {...props} />;
|
||||
case 'metatron':
|
||||
return <MetatronCube {...props} />;
|
||||
case 'vesica':
|
||||
return <VesicaPiscis {...props} />;
|
||||
case 'hex':
|
||||
return <HexLattice {...props} />;
|
||||
case 'spiral':
|
||||
return <GoldenSpiral {...props} />;
|
||||
case 'torus':
|
||||
return <TorusRings {...props} />;
|
||||
case 'key':
|
||||
return <KnowledgeKey {...props} />;
|
||||
case 'yantra':
|
||||
return <SriYantraLite {...props} />;
|
||||
case 'flower':
|
||||
default:
|
||||
return <FlowerOfLifeWatermark {...props} />;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user