1264 lines
41 KiB
HTML
1264 lines
41 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta
|
|
http-equiv="Content-Security-Policy"
|
|
content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' data: blob: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* https:; media-src 'self' data: blob: http: https:; frame-src 'self' http: https:; object-src 'none'; base-uri 'self'; form-action 'none'; worker-src 'self' blob:;"
|
|
/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/01.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/02.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/03.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/04.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/05.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/06.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/07.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/08.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/09.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/10.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/11.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/12.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<link
|
|
rel="preload"
|
|
as="image"
|
|
type="image/png"
|
|
href="./assets/participant-avatars/13.png"
|
|
fetchpriority="high"
|
|
/>
|
|
<title>Agent Teams AI</title>
|
|
<style>
|
|
/* Splash: animated gradient background */
|
|
#splash {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 9999;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
isolation: isolate;
|
|
background: linear-gradient(
|
|
180deg,
|
|
#0c0d13 0%,
|
|
#1a1535 20%,
|
|
#1e1245 35%,
|
|
#231740 50%,
|
|
#1a1535 65%,
|
|
#151230 80%,
|
|
#0c0d13 100%
|
|
);
|
|
background-size: 100% 300%;
|
|
animation: splash-bg 6s ease infinite;
|
|
transition:
|
|
opacity 0.42s ease-out,
|
|
filter 0.42s ease-out;
|
|
}
|
|
#splash.splash-exiting {
|
|
opacity: 0;
|
|
filter: blur(8px);
|
|
}
|
|
@keyframes splash-bg {
|
|
0% {
|
|
background-position: 50% 0%;
|
|
}
|
|
50% {
|
|
background-position: 50% 100%;
|
|
}
|
|
100% {
|
|
background-position: 50% 0%;
|
|
}
|
|
}
|
|
#splash-noise {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
opacity: 0.03;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
#splash-enhanced-canvas {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
opacity: 0;
|
|
animation: splash-canvas-in 0.62s ease-out 0.08s forwards;
|
|
}
|
|
@keyframes splash-canvas-in {
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
#splash-logo,
|
|
#splash-copy {
|
|
position: relative;
|
|
z-index: 3;
|
|
}
|
|
#splash-logo {
|
|
margin-bottom: 18px;
|
|
animation:
|
|
splash-breathe 3s ease-in-out infinite,
|
|
splash-glow 3s ease-in-out infinite;
|
|
}
|
|
#splash-copy {
|
|
display: flex;
|
|
width: min(84vw, 360px);
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
}
|
|
@keyframes splash-breathe {
|
|
0%,
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
transform: scale(1.08);
|
|
}
|
|
}
|
|
@keyframes splash-glow {
|
|
0%,
|
|
100% {
|
|
filter: drop-shadow(0 0 12px rgba(129, 140, 248, 0.4))
|
|
drop-shadow(0 0 28px rgba(167, 139, 250, 0.18));
|
|
}
|
|
50% {
|
|
filter: drop-shadow(0 0 20px rgba(129, 140, 248, 0.6))
|
|
drop-shadow(0 0 42px rgba(167, 139, 250, 0.3));
|
|
}
|
|
}
|
|
#splash-text {
|
|
font-family:
|
|
ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.05em;
|
|
color: #a1a1aa;
|
|
text-shadow: 0 0 22px rgba(129, 140, 248, 0.26);
|
|
}
|
|
#splash-tagline {
|
|
margin-top: 7px;
|
|
font-family:
|
|
ui-sans-serif,
|
|
system-ui,
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
'Segoe UI',
|
|
sans-serif;
|
|
font-size: 11px;
|
|
font-style: italic;
|
|
font-weight: 500;
|
|
letter-spacing: 0.01em;
|
|
color: rgba(212, 212, 216, 0.68);
|
|
text-shadow: 0 0 18px rgba(129, 140, 248, 0.18);
|
|
}
|
|
#splash-tagline > span {
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
clip-path: inset(0 100% 0 0);
|
|
animation: splash-tagline-type 1.05s steps(28, end) 0.22s forwards;
|
|
will-change: clip-path;
|
|
}
|
|
@keyframes splash-tagline-type {
|
|
to {
|
|
clip-path: inset(0 0 0 0);
|
|
}
|
|
}
|
|
|
|
/* Logo node breathing - cycles through 3 agent nodes */
|
|
@keyframes splash-node {
|
|
0%,
|
|
100% {
|
|
opacity: 0.18;
|
|
}
|
|
12%,
|
|
28% {
|
|
opacity: 1;
|
|
}
|
|
45% {
|
|
opacity: 0.18;
|
|
}
|
|
}
|
|
.splash-node {
|
|
animation: splash-node 3s cubic-bezier(0.4, 0, 0.2, 1) infinite both;
|
|
}
|
|
.splash-edge {
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
/* Light theme splash overrides */
|
|
:root.light #splash {
|
|
background: linear-gradient(
|
|
180deg,
|
|
#e0e7ff 0%,
|
|
#c7d2fe 18%,
|
|
#ddd6fe 36%,
|
|
#f0abfc 50%,
|
|
#ddd6fe 64%,
|
|
#c7d2fe 82%,
|
|
#e0e7ff 100%
|
|
);
|
|
background-size: 100% 300%;
|
|
}
|
|
:root.light #splash-text {
|
|
color: #52525b;
|
|
}
|
|
:root.light #splash-tagline {
|
|
color: rgba(63, 63, 70, 0.66);
|
|
}
|
|
:root.light #splash-noise {
|
|
opacity: 0.02;
|
|
}
|
|
:root.light #splash-logo {
|
|
animation:
|
|
splash-breathe 3s ease-in-out infinite,
|
|
splash-glow-light 3s ease-in-out infinite;
|
|
}
|
|
@keyframes splash-glow-light {
|
|
0%,
|
|
100% {
|
|
filter: drop-shadow(0 0 10px rgba(79, 70, 229, 0.3))
|
|
drop-shadow(0 0 24px rgba(139, 92, 246, 0.15));
|
|
}
|
|
50% {
|
|
filter: drop-shadow(0 0 16px rgba(79, 70, 229, 0.45))
|
|
drop-shadow(0 0 36px rgba(139, 92, 246, 0.25));
|
|
}
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#splash,
|
|
#splash-enhanced-canvas,
|
|
#splash-logo,
|
|
#splash-tagline > span,
|
|
.splash-node {
|
|
animation: none !important;
|
|
}
|
|
#splash-enhanced-canvas {
|
|
opacity: 1;
|
|
}
|
|
#splash-tagline > span {
|
|
clip-path: inset(0 0 0 0);
|
|
}
|
|
}
|
|
</style>
|
|
<script>
|
|
// Flash prevention: Apply cached theme before React loads
|
|
(function () {
|
|
try {
|
|
window.__claudeTeamsSplashStartedAt = performance.now();
|
|
var theme =
|
|
localStorage.getItem('agent-teams-theme-cache') ||
|
|
localStorage.getItem('claude-devtools-theme-cache');
|
|
if (theme === 'light') {
|
|
document.documentElement.classList.add('light');
|
|
}
|
|
} catch (e) {}
|
|
})();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="splash">
|
|
<!-- SVG noise texture - matte paper grain -->
|
|
<svg id="splash-noise" xmlns="http://www.w3.org/2000/svg">
|
|
<filter id="noiseFilter">
|
|
<feTurbulence
|
|
type="fractalNoise"
|
|
baseFrequency="0.65"
|
|
numOctaves="3"
|
|
stitchTiles="stitch"
|
|
/>
|
|
</filter>
|
|
<rect width="100%" height="100%" filter="url(#noiseFilter)" />
|
|
</svg>
|
|
<!-- Logo with animated agent nodes -->
|
|
<svg
|
|
id="splash-logo"
|
|
viewBox="0 0 56 56"
|
|
width="56"
|
|
height="56"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<rect class="splash-logo-bg" width="56" height="56" rx="14" fill="#151620" />
|
|
<!-- Edges connecting nodes -->
|
|
<line
|
|
class="splash-edge"
|
|
x1="19"
|
|
y1="19"
|
|
x2="37"
|
|
y2="19"
|
|
stroke="#818cf8"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
opacity="0.35"
|
|
/>
|
|
<line
|
|
class="splash-edge"
|
|
x1="37"
|
|
y1="19"
|
|
x2="28"
|
|
y2="37"
|
|
stroke="#a78bfa"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
opacity="0.35"
|
|
/>
|
|
<line
|
|
class="splash-edge"
|
|
x1="28"
|
|
y1="37"
|
|
x2="19"
|
|
y2="19"
|
|
stroke="#c084fc"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
opacity="0.35"
|
|
/>
|
|
<!-- Agent nodes -->
|
|
<circle
|
|
class="splash-node splash-node-fill"
|
|
style="animation-delay: 0s"
|
|
cx="19"
|
|
cy="19"
|
|
r="5.5"
|
|
fill="#818cf8"
|
|
/>
|
|
<circle
|
|
class="splash-node splash-node-fill"
|
|
style="animation-delay: 1s"
|
|
cx="37"
|
|
cy="19"
|
|
r="5.5"
|
|
fill="#a78bfa"
|
|
/>
|
|
<circle
|
|
class="splash-node splash-node-fill"
|
|
style="animation-delay: 2s"
|
|
cx="28"
|
|
cy="37"
|
|
r="6"
|
|
fill="#c084fc"
|
|
/>
|
|
<!-- Core highlights -->
|
|
<circle
|
|
class="splash-node splash-core-fill"
|
|
style="animation-delay: 0s"
|
|
cx="19"
|
|
cy="19"
|
|
r="2"
|
|
fill="#e0e7ff"
|
|
/>
|
|
<circle
|
|
class="splash-node splash-core-fill"
|
|
style="animation-delay: 1s"
|
|
cx="37"
|
|
cy="19"
|
|
r="2"
|
|
fill="#ede9fe"
|
|
/>
|
|
<circle
|
|
class="splash-node splash-core-fill"
|
|
style="animation-delay: 2s"
|
|
cx="28"
|
|
cy="37"
|
|
r="2.2"
|
|
fill="#f3e8ff"
|
|
/>
|
|
</svg>
|
|
<div id="splash-copy">
|
|
<div id="splash-text">Agent Teams AI</div>
|
|
<div id="splash-tagline"><span>Get more done by doing less.</span></div>
|
|
</div>
|
|
</div>
|
|
<div id="root"></div>
|
|
<script>
|
|
(function () {
|
|
if (window.location.protocol !== 'file:') return;
|
|
|
|
var TAU = Math.PI * 2;
|
|
var TEAM_MEMBER_COUNTS = [4, 3, 5];
|
|
var TEAM_MEMBER_OFFSETS = [0, 4, 7];
|
|
var TEAM_LABELS = ['Marketing', 'Researchers', 'Coding'];
|
|
var MAX_DPR = 2;
|
|
var AVATAR_URLS = [
|
|
'./assets/participant-avatars/01.png',
|
|
'./assets/participant-avatars/02.png',
|
|
'./assets/participant-avatars/03.png',
|
|
'./assets/participant-avatars/04.png',
|
|
'./assets/participant-avatars/05.png',
|
|
'./assets/participant-avatars/06.png',
|
|
'./assets/participant-avatars/07.png',
|
|
'./assets/participant-avatars/08.png',
|
|
'./assets/participant-avatars/09.png',
|
|
'./assets/participant-avatars/10.png',
|
|
'./assets/participant-avatars/11.png',
|
|
'./assets/participant-avatars/12.png',
|
|
'./assets/participant-avatars/13.png',
|
|
];
|
|
var avatarCache = new Map();
|
|
var avatarLoading = new Map();
|
|
|
|
function startFileSplashScene(splash) {
|
|
if (window.__claudeTeamsSplashScene && splash.querySelector('#splash-enhanced-canvas')) {
|
|
return window.__claudeTeamsSplashScene;
|
|
}
|
|
|
|
var ready = preloadAvatarImages();
|
|
var previousCanvas = splash.querySelector('#splash-enhanced-canvas');
|
|
if (previousCanvas) previousCanvas.remove();
|
|
|
|
var canvas = document.createElement('canvas');
|
|
canvas.id = 'splash-enhanced-canvas';
|
|
canvas.setAttribute('aria-hidden', 'true');
|
|
splash.appendChild(canvas);
|
|
|
|
var ctx = canvas.getContext('2d', { alpha: true });
|
|
if (!ctx)
|
|
return {
|
|
stop: function () {
|
|
canvas.remove();
|
|
},
|
|
ready: ready,
|
|
};
|
|
|
|
var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
var state = {
|
|
width: 1,
|
|
height: 1,
|
|
dpr: 1,
|
|
particles: [],
|
|
running: true,
|
|
frameId: 0,
|
|
startedAt: performance.now(),
|
|
};
|
|
|
|
function resize() {
|
|
var rect = splash.getBoundingClientRect();
|
|
var width = Math.max(1, Math.round(rect.width));
|
|
var height = Math.max(1, Math.round(rect.height));
|
|
var dpr = Math.min(MAX_DPR, window.devicePixelRatio || 1);
|
|
|
|
if (state.width === width && state.height === height && state.dpr === dpr) return;
|
|
|
|
state.width = width;
|
|
state.height = height;
|
|
state.dpr = dpr;
|
|
canvas.width = Math.ceil(width * dpr);
|
|
canvas.height = Math.ceil(height * dpr);
|
|
canvas.style.width = width + 'px';
|
|
canvas.style.height = height + 'px';
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
state.particles = createParticles(width, height);
|
|
}
|
|
|
|
function render(now) {
|
|
if (!state.running) return;
|
|
resize();
|
|
drawScene(
|
|
ctx,
|
|
state.width,
|
|
state.height,
|
|
reducedMotion ? 1.2 : (now - state.startedAt) / 1000,
|
|
state.particles
|
|
);
|
|
if (!reducedMotion) state.frameId = window.requestAnimationFrame(render);
|
|
}
|
|
|
|
function onResize() {
|
|
resize();
|
|
}
|
|
|
|
window.addEventListener('resize', onResize);
|
|
resize();
|
|
render(performance.now());
|
|
|
|
var handle = {
|
|
stop: function () {
|
|
state.running = false;
|
|
window.cancelAnimationFrame(state.frameId);
|
|
window.removeEventListener('resize', onResize);
|
|
canvas.remove();
|
|
if (window.__claudeTeamsSplashScene === handle) {
|
|
window.__claudeTeamsSplashScene = undefined;
|
|
window.__claudeTeamsSplashEnhancedStartedAt = undefined;
|
|
}
|
|
},
|
|
ready: ready,
|
|
};
|
|
|
|
window.__claudeTeamsSplashScene = handle;
|
|
window.__claudeTeamsSplashEnhancedStartedAt = performance.now();
|
|
return handle;
|
|
}
|
|
|
|
function palette() {
|
|
var isLight = document.documentElement.classList.contains('light');
|
|
return isLight
|
|
? {
|
|
isLight: true,
|
|
centerGlow: '#4f46e5',
|
|
teamColors: ['#0369a1', '#047857', '#b45309'],
|
|
robotShade: '#dbe4ff',
|
|
robotEye: '#ffffff',
|
|
messageAccent: '#7c3aed',
|
|
particle: '#312e81',
|
|
}
|
|
: {
|
|
isLight: false,
|
|
centerGlow: '#7c83f7',
|
|
teamColors: ['#24a8d8', '#23b488', '#d58a19'],
|
|
robotShade: '#1a2438',
|
|
robotEye: '#d8f3ff',
|
|
messageAccent: '#8b5cf6',
|
|
particle: '#a6a4d6',
|
|
};
|
|
}
|
|
|
|
function drawScene(ctx, width, height, time, particles) {
|
|
var p = palette();
|
|
var mobile = width < 560 || height < 620;
|
|
var center = { x: width / 2, y: height * (mobile ? 0.47 : 0.49) };
|
|
var teams = buildTeams(width, height, time, mobile, p, center);
|
|
|
|
ctx.clearRect(0, 0, width, height);
|
|
drawParticles(ctx, width, height, time, particles, p, mobile);
|
|
drawAura(ctx, center, time, p, mobile);
|
|
drawGuides(ctx, teams, time, p);
|
|
|
|
for (var i = 0; i < teams.length; i++) drawHalo(ctx, teams[i], time, p);
|
|
drawMessages(ctx, teams, time, p, mobile);
|
|
for (var ti = 0; ti < teams.length; ti++) {
|
|
for (var ri = 0; ri < teams[ti].robots.length; ri++) {
|
|
drawRobot(ctx, teams[ti].robots[ri], time, p);
|
|
}
|
|
}
|
|
for (var labelIndex = 0; labelIndex < teams.length; labelIndex++) {
|
|
drawTeamLabel(ctx, teams[labelIndex], p, mobile);
|
|
}
|
|
}
|
|
|
|
function buildTeams(width, height, time, mobile, p, center) {
|
|
var spreadX = mobile ? Math.min(width * 0.36, 148) : Math.min(width * 0.34, 380);
|
|
var spreadY = mobile ? Math.min(height * 0.22, 154) : Math.min(height * 0.22, 220);
|
|
var teamRadius = mobile
|
|
? clamp(Math.min(width, height) * 0.092, 31, 42)
|
|
: clamp(Math.min(width, height) * 0.072, 42, 62);
|
|
var robotSize = mobile ? 9.8 : 11.8;
|
|
var centers = [
|
|
{ x: center.x - spreadX, y: center.y - spreadY * (mobile ? 0.66 : 0.58) },
|
|
{ x: center.x + spreadX, y: center.y - spreadY * (mobile ? 0.66 : 0.58) },
|
|
{ x: center.x, y: center.y + spreadY * (mobile ? 1.34 : 1.18) },
|
|
];
|
|
|
|
return centers.map(function (teamCenter, teamIndex) {
|
|
var drift = Math.sin(time * 0.75 + teamIndex * 1.7) * (mobile ? 2.2 : 4.2);
|
|
var centerWithDrift = {
|
|
x: teamCenter.x + Math.cos(teamIndex * 2.1 + time * 0.35) * (mobile ? 1.4 : 2.8),
|
|
y: teamCenter.y + drift,
|
|
};
|
|
var color = p.teamColors[teamIndex];
|
|
var memberCount = TEAM_MEMBER_COUNTS[teamIndex];
|
|
var robots = [];
|
|
for (var robotIndex = 0; robotIndex < memberCount; robotIndex++) {
|
|
var baseAngle =
|
|
-Math.PI / 2 + robotIndex * (TAU / memberCount) + (teamIndex === 2 ? TAU / 20 : 0);
|
|
var orbit = baseAngle + Math.sin(time * 0.55 + teamIndex + robotIndex) * 0.07;
|
|
var orbitRadius =
|
|
teamRadius *
|
|
(0.94 + (memberCount > 4 ? 0.07 : 0) + 0.03 * Math.sin(time + robotIndex));
|
|
robots.push({
|
|
teamIndex: teamIndex,
|
|
robotIndex: robotIndex,
|
|
color: color,
|
|
size: memberCount > 4 ? robotSize * 0.88 : robotSize,
|
|
bob: Math.sin(time * 2.2 + teamIndex * 0.8 + robotIndex * 1.1),
|
|
receivePulse: 0,
|
|
avatarUrl:
|
|
AVATAR_URLS[(TEAM_MEMBER_OFFSETS[teamIndex] || 0) + robotIndex] || AVATAR_URLS[0],
|
|
x: centerWithDrift.x + Math.cos(orbit) * orbitRadius,
|
|
y: centerWithDrift.y + Math.sin(orbit) * orbitRadius,
|
|
});
|
|
}
|
|
return {
|
|
index: teamIndex,
|
|
center: centerWithDrift,
|
|
color: color,
|
|
radius: teamRadius,
|
|
robots: robots,
|
|
};
|
|
});
|
|
}
|
|
|
|
function drawParticles(ctx, width, height, time, particles, p, mobile) {
|
|
var count = mobile ? Math.floor(particles.length * 0.6) : particles.length;
|
|
for (var i = 0; i < count; i++) {
|
|
var particle = particles[i];
|
|
var y = (particle.y + time * particle.speed) % (height + 24);
|
|
var x = particle.x + Math.sin(time * 0.45 + particle.phase) * 8;
|
|
ctx.fillStyle = withAlpha(p.particle, particle.alpha);
|
|
ctx.beginPath();
|
|
ctx.arc(x, y - 12, particle.size, 0, TAU);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
function drawAura(ctx, center, time, p, mobile) {
|
|
var radius = mobile ? 86 : 128;
|
|
var glow = ctx.createRadialGradient(center.x, center.y, 20, center.x, center.y, radius);
|
|
glow.addColorStop(0, withAlpha(p.centerGlow, p.isLight ? 0.1 : 0.14));
|
|
glow.addColorStop(0.48, withAlpha(p.messageAccent, p.isLight ? 0.04 : 0.07));
|
|
glow.addColorStop(1, withAlpha(p.centerGlow, 0));
|
|
ctx.fillStyle = glow;
|
|
ctx.beginPath();
|
|
ctx.arc(center.x, center.y, radius, 0, TAU);
|
|
ctx.fill();
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = withAlpha(p.centerGlow, 0.07 - i * 0.014);
|
|
ctx.lineWidth = 1;
|
|
ctx.setLineDash([8 + i * 2, 12 + i * 3]);
|
|
ctx.lineDashOffset = -time * (18 + i * 8);
|
|
ctx.arc(center.x, center.y, radius * (0.42 + i * 0.18), 0, TAU);
|
|
ctx.stroke();
|
|
}
|
|
ctx.setLineDash([]);
|
|
}
|
|
|
|
function drawGuides(ctx, teams, time, p) {
|
|
for (var i = 0; i < teams.length; i++) {
|
|
var from = teams[i];
|
|
var to = teams[(i + 1) % teams.length];
|
|
ctx.beginPath();
|
|
ctx.moveTo(from.center.x, from.center.y);
|
|
ctx.lineTo(to.center.x, to.center.y);
|
|
ctx.strokeStyle = withAlpha(p.messageAccent, p.isLight ? 0.14 : 0.18);
|
|
ctx.lineWidth = 1.05;
|
|
ctx.setLineDash([7, 12]);
|
|
ctx.lineDashOffset = -time * 34;
|
|
ctx.stroke();
|
|
}
|
|
ctx.setLineDash([]);
|
|
}
|
|
|
|
function drawHalo(ctx, team, time, p) {
|
|
var pulse = 1 + Math.sin(time * 1.8 + team.index) * 0.035;
|
|
var glow = ctx.createRadialGradient(
|
|
team.center.x,
|
|
team.center.y,
|
|
team.radius * 0.35,
|
|
team.center.x,
|
|
team.center.y,
|
|
team.radius * 2
|
|
);
|
|
glow.addColorStop(0, withAlpha(team.color, p.isLight ? 0.045 : 0.065));
|
|
glow.addColorStop(1, withAlpha(team.color, 0));
|
|
ctx.fillStyle = glow;
|
|
ctx.beginPath();
|
|
ctx.ellipse(
|
|
team.center.x,
|
|
team.center.y,
|
|
team.radius * 1.82,
|
|
team.radius * 1.36,
|
|
0,
|
|
0,
|
|
TAU
|
|
);
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.ellipse(
|
|
team.center.x,
|
|
team.center.y,
|
|
team.radius * 1.56 * pulse,
|
|
team.radius * 1.14 * pulse,
|
|
time * 0.08,
|
|
0,
|
|
TAU
|
|
);
|
|
ctx.strokeStyle = withAlpha(team.color, p.isLight ? 0.2 : 0.24);
|
|
ctx.lineWidth = 1;
|
|
ctx.setLineDash([12, 10]);
|
|
ctx.lineDashOffset = -time * (22 + team.index * 4);
|
|
ctx.stroke();
|
|
ctx.setLineDash([]);
|
|
}
|
|
|
|
function drawMessages(ctx, teams, time, p, mobile) {
|
|
for (var i = 0; i < teams.length; i++) {
|
|
var pairs = localPairs(i, teams[i].robots.length);
|
|
for (var pi = 0; pi < pairs.length; pi++) {
|
|
var raw =
|
|
positiveModulo(time + i * 0.7 + pi * 0.36, 2.15 + i * 0.12) / (2.15 + i * 0.12);
|
|
var from = teams[i].robots[pairs[pi][0]];
|
|
var to = teams[i].robots[pairs[pi][1]];
|
|
if (to) applyReceivePulse(to, receivePulse(raw, 0.76));
|
|
var flightState = messageFlightState(raw, 0.76, 0.12);
|
|
if (!flightState) continue;
|
|
if (from && to)
|
|
drawFlight(
|
|
ctx,
|
|
localCurve(from, to, teams[i].center, teams[i].radius * 0.42),
|
|
flightState,
|
|
teams[i].color,
|
|
mobile ? 4.6 : 5.8,
|
|
p,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
var routes = [
|
|
[0, 3, 1, 1, 0, false],
|
|
[1, 2, 2, 0, 1.34, true],
|
|
[2, 4, 0, 1, 2.68, false],
|
|
];
|
|
for (var r = 0; r < routes.length; r++) {
|
|
var route = routes[r];
|
|
var rawCross = positiveModulo(time + route[4], 4.25) / 4.25;
|
|
var fromTeam = teams[route[0]];
|
|
var toTeam = teams[route[2]];
|
|
var crossFrom = fromTeam.robots[route[1] % fromTeam.robots.length];
|
|
var crossTo = toTeam.robots[route[3] % toTeam.robots.length];
|
|
applyReceivePulse(crossTo, receivePulse(rawCross, 0.64) * 0.88);
|
|
var crossState = messageFlightState(rawCross, 0.64, 0.1);
|
|
if (!crossState) continue;
|
|
drawFlight(
|
|
ctx,
|
|
straightCurve(crossFrom, crossTo),
|
|
crossState,
|
|
route[5] ? p.messageAccent : fromTeam.color,
|
|
mobile ? 5.2 : 6.8,
|
|
p,
|
|
true
|
|
);
|
|
}
|
|
}
|
|
|
|
function drawFlight(ctx, curve, state, color, size, p, crossTeam) {
|
|
var p0 = curve[0],
|
|
p1 = curve[1],
|
|
p2 = curve[2],
|
|
p3 = curve[3];
|
|
ctx.save();
|
|
|
|
var progress = state.progress;
|
|
var speed = clamp(state.motionSpeed, 0, 1);
|
|
if (speed > 0.045) {
|
|
drawSpeedTrail(ctx, curve, progress, speed, color, size, p, crossTeam);
|
|
}
|
|
|
|
var position = cubicPoint(p0, p1, p2, p3, progress);
|
|
var tangent = cubicTangent(p0, p1, p2, p3, progress);
|
|
drawBubble(
|
|
ctx,
|
|
position,
|
|
Math.atan2(tangent.y, tangent.x),
|
|
size,
|
|
color,
|
|
p,
|
|
crossTeam,
|
|
state.bubbleScale,
|
|
state.bubbleAlpha
|
|
);
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawSpeedTrail(ctx, curve, progress, speed, color, size, p, crossTeam) {
|
|
var p0 = curve[0],
|
|
p1 = curve[1],
|
|
p2 = curve[2],
|
|
p3 = curve[3];
|
|
var trailLength = (crossTeam ? 0.26 : 0.21) * (0.24 + speed * 1.08);
|
|
var segmentCount = Math.round(9 + speed * 10);
|
|
var alphaBase = (p.isLight ? 0.22 : 0.32) * speed;
|
|
|
|
ctx.save();
|
|
ctx.lineCap = 'round';
|
|
ctx.lineJoin = 'round';
|
|
ctx.shadowColor = withAlpha(color, alphaBase * 0.58);
|
|
ctx.shadowBlur = size * (0.78 + speed * 1.28);
|
|
|
|
for (var segment = 0; segment < segmentCount; segment++) {
|
|
var startRatio = segment / segmentCount;
|
|
var endRatio = (segment + 1) / segmentCount;
|
|
var t0 = progress - trailLength * (1 - startRatio);
|
|
var t1 = progress - trailLength * (1 - endRatio);
|
|
if (t1 <= 0) continue;
|
|
|
|
var from = cubicPoint(p0, p1, p2, p3, Math.max(0, t0));
|
|
var to = cubicPoint(p0, p1, p2, p3, Math.max(0, t1));
|
|
var headWeight = endRatio * endRatio;
|
|
var width = size * (0.12 + headWeight * 0.48) * (0.9 + speed * 0.45);
|
|
var alpha = alphaBase * headWeight;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(from.x, from.y);
|
|
ctx.lineTo(to.x, to.y);
|
|
ctx.strokeStyle = withAlpha(color, alpha * 0.34);
|
|
ctx.lineWidth = width * 2.35;
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(from.x, from.y);
|
|
ctx.lineTo(to.x, to.y);
|
|
ctx.strokeStyle = withAlpha(color, alpha);
|
|
ctx.lineWidth = width;
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function messageFlightState(raw, activeWindow, settleWindow) {
|
|
if (raw > activeWindow + settleWindow) return null;
|
|
|
|
if (raw <= activeWindow) {
|
|
var phase = raw / activeWindow;
|
|
return {
|
|
progress: ease(phase),
|
|
motionSpeed: easedMotionSpeed(phase),
|
|
bubbleScale: 1,
|
|
bubbleAlpha: 1,
|
|
};
|
|
}
|
|
|
|
var settlePhase = (raw - activeWindow) / settleWindow;
|
|
var eased = easeOutCubic(settlePhase);
|
|
return {
|
|
progress: 1,
|
|
motionSpeed: 0,
|
|
bubbleScale: Math.max(0.12, 1 - eased * 0.88),
|
|
bubbleAlpha: Math.max(0, 1 - eased),
|
|
};
|
|
}
|
|
|
|
function applyReceivePulse(robot, pulse) {
|
|
robot.receivePulse = Math.max(robot.receivePulse, pulse);
|
|
}
|
|
|
|
function receivePulse(raw, activeWindow) {
|
|
var previousStart = activeWindow * 0.78;
|
|
var previousEnd = Math.min(0.96, activeWindow + 0.11);
|
|
var duration = (previousEnd - previousStart) / 3;
|
|
var start = activeWindow - duration * 0.62;
|
|
var end = activeWindow + duration * 0.38;
|
|
if (raw < start || raw > end) return 0;
|
|
var phase = (raw - start) / (end - start);
|
|
return Math.sin(phase * Math.PI) * (1 - phase * 0.28);
|
|
}
|
|
|
|
function easedMotionSpeed(value) {
|
|
var t = clamp(value, 0, 1);
|
|
var derivative = t < 0.5 ? 12 * t * t : 12 * (1 - t) * (1 - t);
|
|
return clamp(derivative / 3, 0, 1);
|
|
}
|
|
|
|
function easeOutCubic(value) {
|
|
var t = clamp(value, 0, 1);
|
|
return 1 - Math.pow(1 - t, 3);
|
|
}
|
|
|
|
function drawBubble(ctx, position, angle, size, color, p, crossTeam, scale, alpha) {
|
|
scale = scale === undefined ? 1 : scale;
|
|
alpha = alpha === undefined ? 1 : alpha;
|
|
if (scale <= 0.02 || alpha <= 0.01) return;
|
|
|
|
ctx.save();
|
|
ctx.translate(position.x, position.y);
|
|
ctx.rotate(angle * 0.08);
|
|
ctx.scale(scale, scale);
|
|
ctx.globalAlpha = alpha;
|
|
ctx.shadowColor = withAlpha(color, (p.isLight ? 0.16 : 0.3) * alpha);
|
|
ctx.shadowBlur = (crossTeam ? 12 : 8) * (0.5 + scale * 0.5);
|
|
var width = size * (crossTeam ? 2.28 : 2.06);
|
|
var height = size * 1.42;
|
|
roundRectPath(ctx, -width / 2, -height / 2, width, height, size * 0.28);
|
|
ctx.fillStyle = withAlpha(color, p.isLight ? 0.82 : 0.9);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.moveTo(-width * 0.24, height * 0.42);
|
|
ctx.lineTo(-width * 0.32, height * 0.68);
|
|
ctx.lineTo(-width * 0.03, height * 0.42);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.shadowBlur = 0;
|
|
ctx.fillStyle = p.robotEye;
|
|
for (var i = -1; i <= 1; i++) {
|
|
ctx.beginPath();
|
|
ctx.arc(i * size * 0.4, -size * 0.02, size * 0.095, 0, TAU);
|
|
ctx.fill();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawTeamLabel(ctx, team, p, mobile) {
|
|
var label = TEAM_LABELS[team.index] || '';
|
|
if (!label) return;
|
|
|
|
var fontSize = mobile ? 7.5 : 8.5;
|
|
var y = team.center.y + team.radius * (mobile ? 1.65 : 1.58);
|
|
ctx.save();
|
|
ctx.font =
|
|
'600 ' +
|
|
fontSize +
|
|
'px ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
|
|
var metrics = ctx.measureText(label);
|
|
var paddingX = mobile ? 4 : 5;
|
|
var paddingY = mobile ? 2 : 2.5;
|
|
var width = metrics.width + paddingX * 2;
|
|
var height = fontSize + paddingY * 2;
|
|
var x = team.center.x - width / 2;
|
|
var rectY = y - height / 2;
|
|
|
|
roundRectPath(ctx, x, rectY, width, height, height / 2);
|
|
ctx.fillStyle = withAlpha(p.isLight ? '#ffffff' : '#090a14', p.isLight ? 0.36 : 0.24);
|
|
ctx.fill();
|
|
ctx.strokeStyle = withAlpha(team.color, p.isLight ? 0.18 : 0.24);
|
|
ctx.lineWidth = 0.75;
|
|
ctx.stroke();
|
|
|
|
ctx.shadowColor = withAlpha(team.color, p.isLight ? 0.12 : 0.22);
|
|
ctx.shadowBlur = mobile ? 4 : 6;
|
|
ctx.fillStyle = withAlpha(p.isLight ? '#3f3f46' : '#e4e4e7', p.isLight ? 0.58 : 0.66);
|
|
ctx.fillText(label, team.center.x, y + 0.2);
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawRobot(ctx, robot, time, p) {
|
|
var size = robot.size;
|
|
var y = robot.y + robot.bob * 0.9 - robot.receivePulse * size * 0.24;
|
|
var img = getAvatarImage(robot.avatarUrl);
|
|
var avatarSize = size * 2.65;
|
|
ctx.save();
|
|
ctx.translate(robot.x, y);
|
|
ctx.rotate(Math.sin(time * 1.5 + robot.teamIndex + robot.robotIndex * 0.8) * 0.045);
|
|
ctx.scale(1 + robot.receivePulse * 0.065, 1 + robot.receivePulse * 0.065);
|
|
ctx.shadowColor = withAlpha(robot.color, p.isLight ? 0.2 : 0.34);
|
|
ctx.shadowBlur = size * (1.25 + robot.receivePulse * 0.72);
|
|
|
|
if (img) {
|
|
ctx.globalAlpha = p.isLight ? 0.92 : 0.86;
|
|
ctx.drawImage(img, -avatarSize / 2, -avatarSize / 2, avatarSize, avatarSize);
|
|
ctx.globalAlpha = 1;
|
|
} else {
|
|
drawAvatarFallback(ctx, size, robot.color, p);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function getAvatarImage(url) {
|
|
var cached = avatarCache.get(url);
|
|
if (cached) {
|
|
avatarCache.delete(url);
|
|
avatarCache.set(url, cached);
|
|
return cached;
|
|
}
|
|
|
|
loadAvatarImage(url);
|
|
return null;
|
|
}
|
|
|
|
function preloadAvatarImages() {
|
|
return Promise.allSettled(
|
|
AVATAR_URLS.map(function (url) {
|
|
return loadAvatarImage(url);
|
|
})
|
|
).then(function () {});
|
|
}
|
|
|
|
function loadAvatarImage(url) {
|
|
var cached = avatarCache.get(url);
|
|
if (cached) return Promise.resolve(cached);
|
|
|
|
var loading = avatarLoading.get(url);
|
|
if (loading) return loading;
|
|
|
|
var promise = new Promise(function (resolve) {
|
|
var img = new Image();
|
|
img.decoding = 'async';
|
|
img.onload = function () {
|
|
var finish = function () {
|
|
avatarCache.set(url, img);
|
|
avatarLoading.delete(url);
|
|
resolve(img);
|
|
};
|
|
|
|
if (typeof img.decode === 'function') {
|
|
img.decode().then(finish, finish);
|
|
} else {
|
|
finish();
|
|
}
|
|
};
|
|
img.onerror = function () {
|
|
avatarLoading.delete(url);
|
|
resolve(null);
|
|
};
|
|
img.src = url;
|
|
});
|
|
|
|
avatarLoading.set(url, promise);
|
|
return promise;
|
|
}
|
|
|
|
function drawAvatarFallback(ctx, size, color, p) {
|
|
ctx.strokeStyle = withAlpha(color, p.isLight ? 0.44 : 0.56);
|
|
ctx.lineWidth = Math.max(1, size * 0.08);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, -size * 0.72);
|
|
ctx.lineTo(0, -size * 1.0);
|
|
ctx.stroke();
|
|
ctx.fillStyle = withAlpha(color, p.isLight ? 0.64 : 0.78);
|
|
ctx.beginPath();
|
|
ctx.arc(0, -size * 1.08, size * 0.13, 0, TAU);
|
|
ctx.fill();
|
|
ctx.fillStyle = p.robotEye;
|
|
ctx.beginPath();
|
|
ctx.arc(-size * 0.24, -size * 0.13, size * 0.095, 0, TAU);
|
|
ctx.arc(size * 0.24, -size * 0.13, size * 0.095, 0, TAU);
|
|
ctx.fill();
|
|
}
|
|
|
|
function localPairs(teamIndex, count) {
|
|
var map = [
|
|
[
|
|
[0, 2],
|
|
[3, 1],
|
|
[1, 0],
|
|
],
|
|
[
|
|
[2, 0],
|
|
[0, 1],
|
|
[1, 2],
|
|
],
|
|
[
|
|
[4, 1],
|
|
[0, 3],
|
|
[2, 4],
|
|
[3, 0],
|
|
],
|
|
];
|
|
return map[teamIndex].filter(function (pair) {
|
|
return pair[0] < count && pair[1] < count;
|
|
});
|
|
}
|
|
|
|
function localCurve(from, to, center, lift) {
|
|
var mid = mix(from, to, 0.5);
|
|
var away = normalize({ x: mid.x - center.x, y: mid.y - center.y });
|
|
var control = { x: mid.x + away.x * lift, y: mid.y + away.y * lift };
|
|
return [from, mix(from, control, 0.72), mix(to, control, 0.72), to];
|
|
}
|
|
|
|
function straightCurve(from, to) {
|
|
return [from, mix(from, to, 0.33), mix(from, to, 0.66), to];
|
|
}
|
|
|
|
function createParticles(width, height) {
|
|
var count = width < 560 ? 46 : 78;
|
|
var particles = [];
|
|
for (var i = 0; i < count; i++) {
|
|
var seed = i * 97.13;
|
|
particles.push({
|
|
x: pseudo(seed) * width,
|
|
y: pseudo(seed + 12.4) * (height + 24),
|
|
size: 0.45 + pseudo(seed + 22.8) * 1.15,
|
|
speed: 8 + pseudo(seed + 31.2) * 18,
|
|
phase: pseudo(seed + 48.7) * TAU,
|
|
alpha: 0.06 + pseudo(seed + 72.1) * 0.16,
|
|
});
|
|
}
|
|
return particles;
|
|
}
|
|
|
|
function cubicPoint(p0, p1, p2, p3, t) {
|
|
var clamped = clamp(t, 0, 1);
|
|
var mt = 1 - clamped;
|
|
return {
|
|
x:
|
|
mt * mt * mt * p0.x +
|
|
3 * mt * mt * clamped * p1.x +
|
|
3 * mt * clamped * clamped * p2.x +
|
|
clamped * clamped * clamped * p3.x,
|
|
y:
|
|
mt * mt * mt * p0.y +
|
|
3 * mt * mt * clamped * p1.y +
|
|
3 * mt * clamped * clamped * p2.y +
|
|
clamped * clamped * clamped * p3.y,
|
|
};
|
|
}
|
|
|
|
function cubicTangent(p0, p1, p2, p3, t) {
|
|
var clamped = clamp(t, 0, 1);
|
|
var mt = 1 - clamped;
|
|
return {
|
|
x:
|
|
3 * mt * mt * (p1.x - p0.x) +
|
|
6 * mt * clamped * (p2.x - p1.x) +
|
|
3 * clamped * clamped * (p3.x - p2.x),
|
|
y:
|
|
3 * mt * mt * (p1.y - p0.y) +
|
|
6 * mt * clamped * (p2.y - p1.y) +
|
|
3 * clamped * clamped * (p3.y - p2.y),
|
|
};
|
|
}
|
|
|
|
function roundRectPath(ctx, x, y, width, height, radius) {
|
|
var r = Math.min(radius, width / 2, height / 2);
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + r, y);
|
|
ctx.lineTo(x + width - r, y);
|
|
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
ctx.lineTo(x + width, y + height - r);
|
|
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
ctx.lineTo(x + r, y + height);
|
|
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
ctx.lineTo(x, y + r);
|
|
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
ctx.closePath();
|
|
}
|
|
|
|
function mix(from, to, amount) {
|
|
return { x: from.x + (to.x - from.x) * amount, y: from.y + (to.y - from.y) * amount };
|
|
}
|
|
|
|
function normalize(point) {
|
|
var length = Math.hypot(point.x, point.y) || 1;
|
|
return { x: point.x / length, y: point.y / length };
|
|
}
|
|
|
|
function ease(value) {
|
|
var t = clamp(value, 0, 1);
|
|
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
}
|
|
|
|
function positiveModulo(value, divisor) {
|
|
return ((value % divisor) + divisor) % divisor;
|
|
}
|
|
|
|
function pseudo(seed) {
|
|
var value = Math.sin(seed * 12.9898) * 43758.5453;
|
|
return value - Math.floor(value);
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
|
|
function withAlpha(hex, alpha) {
|
|
var value = hex.replace('#', '');
|
|
var r = parseInt(value.slice(0, 2), 16);
|
|
var g = parseInt(value.slice(2, 4), 16);
|
|
var b = parseInt(value.slice(4, 6), 16);
|
|
return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + clamp(alpha, 0, 1) + ')';
|
|
}
|
|
|
|
function start() {
|
|
var splash = document.getElementById('splash');
|
|
if (splash && !window.__claudeTeamsSplashScene) startFileSplashScene(splash);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', start, { once: true });
|
|
} else {
|
|
start();
|
|
}
|
|
})();
|
|
</script>
|
|
<script type="module">
|
|
if (window.location.protocol !== 'file:') {
|
|
const { startSplashScene } = await import('./components/splash/splashScene.ts');
|
|
const splash = document.getElementById('splash');
|
|
if (splash && !window.__claudeTeamsSplashScene) {
|
|
window.__claudeTeamsSplashScene = startSplashScene(splash);
|
|
}
|
|
}
|
|
</script>
|
|
<script type="module">
|
|
if (window.location.protocol !== 'file:') {
|
|
import('./main.tsx');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|