Dark theme, modern design, live activity feed, team grid, comparison table, contact CTA. Vanilla HTML/CSS/JS, mobile-first.
213 lines
8.1 KiB
JavaScript
213 lines
8.1 KiB
JavaScript
/* ========================================
|
|
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 = `
|
|
<div class="feed-avatar">${item.agent.initials}</div>
|
|
<div class="feed-content">
|
|
<p>
|
|
<span class="feed-action ${item.action}">${item.action}</span>
|
|
<strong>${item.agent.name}</strong> — ${item.message}
|
|
</p>
|
|
</div>
|
|
<span class="feed-time">${item.time}</span>
|
|
`;
|
|
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();
|
|
});
|
|
|
|
})();
|