feat: alive UI wave, galaxy presence, spread and fleet enhancements
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
This commit is contained in:
AetherForge
2026-06-04 22:36:17 -07:00
parent 1551bd5dad
commit a32860b0d9
154 changed files with 17383 additions and 601 deletions

View File

@@ -0,0 +1,48 @@
import { usePresence } from '../../context/PresenceContext';
import { presenceActivityLine, presencePageLabel } from '../../help/presencePages';
import ComradeAvatar from './ComradeAvatar';
import './Presence.css';
interface AlsoHereProps {
page: string;
}
export default function AlsoHere({ page }: AlsoHereProps) {
const { comradesHere } = usePresence();
const here = comradesHere(page);
if (here.length === 0) return null;
const label = presencePageLabel(page);
const count = here.length;
return (
<div className="also-here-banner" role="status">
<div className="also-here-beacon" aria-hidden>
<span className="also-here-pulse-ring" />
<span className="also-here-dot" />
</div>
<div className="also-here-body">
<div className="also-here-avatars" aria-label={`${count} comrade${count === 1 ? '' : 's'} in ${label}`}>
{here.map((c) => (
<ComradeAvatar
key={c.user}
user={c.user}
size="sm"
title={presenceActivityLine(c.user, c.page)}
/>
))}
</div>
<div className="also-here-text">
<span className="also-here-headline">
{count} comrade{count === 1 ? '' : 's'} in the war room
</span>
<span className="also-here-detail">
Also here in <strong className="also-here-zone">{label}</strong>
{': '}
<span className="also-here-names">{here.map((c) => c.user).join(', ')}</span>
</span>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,24 @@
import './Presence.css';
export function comradeAvatarInitial(user: string): string {
const ch = user.trim().charAt(0);
return ch ? ch.toUpperCase() : '?';
}
interface ComradeAvatarProps {
user: string;
size?: 'sm' | 'md';
title?: string;
}
export default function ComradeAvatar({ user, size = 'md', title }: ComradeAvatarProps) {
return (
<span
className={`comrade-avatar comrade-avatar--${size}`}
title={title}
aria-label={title ?? user}
>
{comradeAvatarInitial(user)}
</span>
);
}

View File

@@ -0,0 +1,38 @@
import { usePresence } from '../../context/PresenceContext';
import { presenceActivityLine, presencePageLabel } from '../../help/presencePages';
import ComradeAvatar from './ComradeAvatar';
import './Presence.css';
export default function ComradeIndicators() {
const { comrades } = usePresence();
if (comrades.length === 0) return null;
return (
<div className="comrade-presence" aria-label="Online comrades">
<span className="comrade-presence-beacon" aria-hidden>
<span className="comrade-presence-ring" />
<span className="comrade-presence-core" />
</span>
<span className="comrade-presence-label">COMRADES</span>
<div className="comrade-avatar-list">
{comrades.map((c) => (
<ComradeAvatar
key={c.user}
user={c.user}
title={presenceActivityLine(c.user, c.page)}
/>
))}
</div>
<span className="comrade-activity-text">
{comrades.map((c, i) => (
<span key={c.user} className="comrade-activity-line">
{i > 0 && <span className="comrade-activity-sep"> · </span>}
<strong className="comrade-activity-user">{c.user}</strong>
<span className="comrade-activity-verb"> is in </span>
<span className="comrade-activity-page">{presencePageLabel(c.page)}</span>
</span>
))}
</span>
</div>
);
}

View File

@@ -0,0 +1,396 @@
/* ── Status bar: soft pulse when comrades online ── */
@keyframes comrade-status-pulse {
0%,
100% {
box-shadow:
0 4px 24px rgba(0, 0, 0, 0.25),
0 0 12px rgba(61, 214, 198, 0.08),
inset 0 -1px 0 rgba(61, 214, 198, 0.15);
border-bottom-color: rgba(61, 214, 198, 0.18);
}
50% {
box-shadow:
0 4px 28px rgba(0, 0, 0, 0.3),
0 0 28px rgba(61, 214, 198, 0.22),
inset 0 -1px 0 rgba(61, 214, 198, 0.35);
border-bottom-color: rgba(61, 214, 198, 0.38);
}
}
.system-status-bar--comrades-online {
animation: comrade-status-pulse 3.5s ease-in-out infinite;
border-bottom: 1px solid rgba(61, 214, 198, 0.18);
}
/* ── Comrade indicators (status bar) ── */
.comrade-presence {
display: flex;
align-items: center;
gap: 0.5rem;
margin-left: auto;
padding: 0.15rem 0.5rem;
border-radius: 999px;
border: 1px solid rgba(61, 214, 198, 0.2);
background: linear-gradient(135deg, rgba(61, 214, 198, 0.06), rgba(8, 6, 4, 0.5));
}
.comrade-presence-beacon {
position: relative;
width: 10px;
height: 10px;
flex-shrink: 0;
}
.comrade-presence-core {
position: absolute;
inset: 2px;
border-radius: 50%;
background: var(--neon-green);
box-shadow: 0 0 8px var(--neon-green);
}
.comrade-presence-ring {
position: absolute;
inset: -2px;
border-radius: 50%;
border: 1px solid rgba(61, 214, 198, 0.5);
animation: comrade-beacon-ring 2.4s ease-out infinite;
}
@keyframes comrade-beacon-ring {
0% {
transform: scale(0.85);
opacity: 0.9;
}
70%,
100% {
transform: scale(1.6);
opacity: 0;
}
}
.comrade-presence-label {
color: var(--text-muted);
font-size: 0.68rem;
letter-spacing: 0.08em;
}
.comrade-activity-text {
color: var(--neon-cyan);
font-size: 0.68rem;
opacity: 0.95;
max-width: 28rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.comrade-activity-user {
color: var(--text-primary);
font-weight: 600;
}
.comrade-activity-verb {
color: var(--text-muted);
}
.comrade-activity-page {
color: var(--neon-cyan);
font-family: var(--font-tech);
letter-spacing: 0.03em;
}
.comrade-activity-sep {
color: rgba(61, 214, 198, 0.45);
}
.comrade-avatar-list {
display: flex;
align-items: center;
gap: 0.35rem;
}
.comrade-avatar {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.5rem;
height: 1.5rem;
padding: 0 0.35rem;
border-radius: 999px;
border: 1px solid rgba(61, 214, 198, 0.35);
background: rgba(8, 20, 18, 0.9);
color: var(--neon-cyan);
font-size: 0.62rem;
text-transform: uppercase;
letter-spacing: 0.04em;
position: relative;
box-shadow: 0 0 10px rgba(61, 214, 198, 0.12);
}
.comrade-avatar--sm {
min-width: 1.25rem;
height: 1.25rem;
font-size: 0.55rem;
padding: 0 0.25rem;
}
.comrade-avatar::after {
content: '';
position: absolute;
right: -1px;
bottom: -1px;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--neon-green);
box-shadow: 0 0 6px var(--neon-green);
border: 1px solid rgba(8, 6, 4, 0.9);
animation: comrade-online-dot 2s ease-in-out infinite;
}
.comrade-avatar--sm::after {
width: 6px;
height: 6px;
}
@keyframes comrade-online-dot {
0%,
100% {
box-shadow: 0 0 4px var(--neon-green);
}
50% {
box-shadow: 0 0 10px var(--neon-green);
}
}
.comrade-avatar[title] {
cursor: default;
}
/* ── "Also here" war-room banners (Crucible, Emberwake) ── */
.also-here-banner {
display: flex;
align-items: flex-start;
gap: 0.65rem;
margin: 0 0 0.75rem;
padding: 0.55rem 0.85rem;
border-radius: 8px;
border: 1px solid rgba(61, 214, 198, 0.3);
background: linear-gradient(135deg, rgba(61, 214, 198, 0.1), rgba(8, 6, 4, 0.55));
font-size: 0.78rem;
color: var(--text-secondary);
box-shadow: 0 0 20px rgba(61, 214, 198, 0.06);
animation: also-here-glow 4s ease-in-out infinite;
}
@keyframes also-here-glow {
0%,
100% {
box-shadow: 0 0 16px rgba(61, 214, 198, 0.05);
}
50% {
box-shadow: 0 0 24px rgba(61, 214, 198, 0.14);
}
}
.also-here-beacon {
position: relative;
width: 12px;
height: 12px;
flex-shrink: 0;
margin-top: 0.15rem;
}
.also-here-dot {
position: absolute;
inset: 2px;
border-radius: 50%;
background: var(--neon-green);
box-shadow: 0 0 8px var(--neon-green);
}
.also-here-pulse-ring {
position: absolute;
inset: -3px;
border-radius: 50%;
border: 1px solid rgba(61, 214, 198, 0.55);
animation: comrade-beacon-ring 2.4s ease-out infinite;
}
.also-here-body {
display: flex;
align-items: center;
gap: 0.65rem;
flex-wrap: wrap;
min-width: 0;
}
.also-here-avatars {
display: flex;
align-items: center;
gap: 0.3rem;
flex-shrink: 0;
}
.also-here-text {
display: flex;
flex-direction: column;
gap: 0.1rem;
min-width: 0;
}
.also-here-headline {
font-family: var(--font-tech);
font-size: 0.68rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--neon-cyan);
}
.also-here-detail {
font-size: 0.76rem;
}
.also-here-zone {
color: var(--neon-cyan);
font-family: var(--font-tech);
letter-spacing: 0.03em;
}
.also-here-names {
color: var(--text-primary);
font-family: var(--font-tech);
letter-spacing: 0.03em;
}
/* ── Emberwake notes typing indicator ── */
.emberwake-typing-banner {
display: flex;
align-items: center;
gap: 0.55rem;
margin-bottom: 0.5rem;
padding: 0.45rem 0.75rem;
border-radius: 8px;
border: 1px solid rgba(255, 107, 44, 0.4);
background: linear-gradient(90deg, rgba(255, 107, 44, 0.12), rgba(8, 6, 4, 0.45));
font-size: 0.78rem;
color: var(--text-secondary);
animation: emberwake-typing-pulse 2s ease-in-out infinite;
box-shadow: 0 0 18px rgba(255, 107, 44, 0.1);
}
.emberwake-typing-body {
display: flex;
align-items: center;
gap: 0.35rem;
flex-wrap: wrap;
}
.emberwake-typing-cursor {
display: inline-block;
width: 2px;
height: 0.9em;
background: var(--neon-amber);
margin-left: 2px;
animation: emberwake-cursor-blink 1s step-end infinite;
}
.typing-dots {
display: inline-flex;
align-items: center;
gap: 3px;
margin-left: 4px;
vertical-align: middle;
}
.typing-dots span {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--neon-amber);
animation: typing-dot-bounce 1.2s ease-in-out infinite;
}
.typing-dots span:nth-child(2) {
animation-delay: 0.15s;
}
.typing-dots span:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes typing-dot-bounce {
0%,
60%,
100% {
transform: translateY(0);
opacity: 0.45;
}
30% {
transform: translateY(-3px);
opacity: 1;
}
}
@keyframes emberwake-typing-pulse {
0%,
100% {
opacity: 0.88;
box-shadow: 0 0 12px rgba(255, 107, 44, 0.08);
}
50% {
opacity: 1;
box-shadow: 0 0 22px rgba(255, 107, 44, 0.18);
}
}
@keyframes emberwake-cursor-blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
/* ── Layout sidebar glow when comrades online ── */
@keyframes sidebar-comrade-glow {
0%,
100% {
box-shadow: inset -1px 0 0 rgba(61, 214, 198, 0.12), 4px 0 20px rgba(61, 214, 198, 0.04);
}
50% {
box-shadow: inset -1px 0 0 rgba(61, 214, 198, 0.28), 4px 0 32px rgba(61, 214, 198, 0.1);
}
}
.layout--comrades-online .sidebar--desktop {
animation: sidebar-comrade-glow 4s ease-in-out infinite;
}
.sidebar-comrades {
display: flex;
align-items: center;
gap: 0.4rem;
margin: 0.35rem 0 0;
padding: 0.3rem 0.4rem;
border-radius: 6px;
border: 1px solid rgba(61, 214, 198, 0.18);
background: rgba(61, 214, 198, 0.04);
}
.sidebar-comrades-avatars {
display: flex;
align-items: center;
gap: 0.2rem;
}
.sidebar-comrades-label {
font-size: 0.62rem;
color: var(--neon-cyan);
font-family: var(--font-tech);
letter-spacing: 0.04em;
opacity: 0.9;
}