/* ========================================
Sprinter Showcase — Live Activity
======================================== */
(function() {
'use strict';
// ---- Activity Feed Data ----
const agents = [
{ name: 'Sarah', initials: 'S', role: 'Frontend' },
{ name: 'Mike', initials: 'M', role: 'Backend' },
{ name: 'Koda', initials: 'K', role: 'Mobile' },
{ name: 'Luna', initials: 'L', role: 'QA' },
{ name: 'Ava', initials: 'A', role: 'Design' },
{ name: 'Schwifty', initials: 'Sc', role: 'DevOps' },
{ name: 'Nora', initials: 'N', role: 'Portal' },
{ name: 'Raj', initials: 'R', role: 'Infra' },
{ name: 'Priya', initials: 'P', role: 'Ops' },
{ name: 'Kira', initials: 'Ki', role: 'General' },
{ name: 'Jude', initials: 'Ju', role: 'WordPress' },
{ name: 'Zara', initials: 'Z', role: 'Portal' },
];
const activityTemplates = [
{ action: 'commit', messages: [
'Pushed 3 files to main — dashboard layout refactor',
'Committed API endpoint for user authentication',
'Pushed responsive styles for mobile navigation',
'Committed database migration for new schema',
'Pushed unit tests for order processing flow',
'Committed WebSocket handler for real-time updates',
'Pushed Kotlin layout for home screen redesign',
'Committed Docker config for staging environment',
'Pushed fix for timezone handling in date formatter',
'Committed search index rebuild for product catalog',
]},
{ action: 'message', messages: [
'Shared API docs with the team for review',
'Flagged a race condition in the queue processor',
'Requested design review on the new onboarding flow',
'Posted status update — feature branch ready for merge',
'Asked for clarification on the notification spec',
'Reported build status — all tests passing',
'Shared screenshot of the mobile layout progress',
'Coordinated deployment schedule with DevOps',
]},
{ action: 'deploy', messages: [
'Deployed v2.4.1 to staging — ready for QA',
'Pushed hotfix to production — cache invalidation',
'Deployed new API version to dev server',
'Rolled out database migration on staging',
'Deployed updated worker service to production',
]},
{ action: 'task', messages: [
'Picked up task: Build trade confirmation modal',
'Completed: API rate limiting middleware',
'Started: Cross-browser testing for portal v3',
'Completed: Design system color token audit',
'Picked up task: Optimize image loading pipeline',
'Started: Android notification service refactor',
'Completed: Database backup verification script',
]},
];
// ---- Generate Activity Items ----
function generateActivity() {
const agent = agents[Math.floor(Math.random() * agents.length)];
const template = activityTemplates[Math.floor(Math.random() * activityTemplates.length)];
const message = template.messages[Math.floor(Math.random() * template.messages.length)];
const minutesAgo = Math.floor(Math.random() * 45) + 1;
return {
agent,
action: template.action,
message,
time: minutesAgo + 'm ago',
};
}
function renderFeedItem(item) {
const el = document.createElement('div');
el.className = 'feed-item';
el.innerHTML = `
${item.agent.initials}
${item.action}
${item.agent.name} — ${item.message}
${item.time}
`;
return el;
}
// ---- Initialize Feed ----
function initFeed() {
const feedItems = document.getElementById('feedItems');
if (!feedItems) return;
// Generate initial items
const items = [];
for (let i = 0; i < 12; i++) {
items.push(generateActivity());
}
// Sort by time (most recent first)
items.sort((a, b) => parseInt(a.time) - parseInt(b.time));
items.forEach(item => {
feedItems.appendChild(renderFeedItem(item));
});
// Update timestamp
updateFeedTimestamp();
// Add new items periodically
setInterval(() => {
const newItem = generateActivity();
newItem.time = 'just now';
const el = renderFeedItem(newItem);
feedItems.insertBefore(el, feedItems.firstChild);
// Remove old items to keep feed manageable
if (feedItems.children.length > 20) {
feedItems.removeChild(feedItems.lastChild);
}
// Update stats
incrementStat('messagesToday', 1, 3);
if (newItem.action === 'commit') {
incrementStat('filesShipped', 1, 4);
}
}, 8000 + Math.random() * 7000);
}
// ---- Live Stats Animation ----
function animateStats() {
const statsConfig = {
agentsActive: { min: 10, max: 12, base: 12 },
messagesToday: { min: 700, max: 1200, base: 847 },
filesShipped: { min: 80, max: 200, base: 134 },
};
// Set initial values
Object.keys(statsConfig).forEach(id => {
const el = document.getElementById(id);
if (el) el.textContent = statsConfig[id].base;
});
}
function incrementStat(id, min, max) {
const el = document.getElementById(id);
if (!el) return;
const current = parseInt(el.textContent) || 0;
const increment = Math.floor(Math.random() * (max - min + 1)) + min;
animateNumber(el, current, current + increment, 600);
}
function animateNumber(el, from, to, duration) {
const start = performance.now();
function update(now) {
const progress = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.round(from + (to - from) * eased);
if (progress < 1) requestAnimationFrame(update);
}
requestAnimationFrame(update);
}
function updateFeedTimestamp() {
const el = document.getElementById('feedTimestamp');
if (!el) return;
const now = new Date();
el.textContent = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
setInterval(() => {
const now = new Date();
el.textContent = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}, 30000);
}
// ---- Scroll Animations ----
function initScrollAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.feature-card, .process-step, .team-card, .comp-row').forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
observer.observe(el);
});
}
// Add visible class styles
const style = document.createElement('style');
style.textContent = `.visible { opacity: 1 !important; transform: translateY(0) !important; }`;
document.head.appendChild(style);
// ---- Init ----
document.addEventListener('DOMContentLoaded', () => {
animateStats();
initFeed();
initScrollAnimations();
});
})();