timeline feaky

This commit is contained in:
deploy
2026-06-04 23:35:09 +00:00
parent 6ceb800855
commit de42c30bab
3 changed files with 265 additions and 40 deletions
+1 -1
View File
@@ -334,7 +334,7 @@ async def _refresh_presence():
await bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.playing,
name=f"Playing SRE in {GUILD_TOTAL} servers!"
name=f"Playing SQB in {GUILD_TOTAL} servers!"
)
)
+7 -1
View File
@@ -900,8 +900,14 @@ app.get('/docs', (req, res) => {
});
app.get('/timeline', (req, res) => {
let serverCount = 0;
try {
const report = fs.readFileSync(path.join(STORAGE_ROOT, 'SREBOT_GUILDS.txt'), 'utf8').trim();
serverCount = report ? report.split('\n').length : 0;
} catch { /* file may not exist yet */ }
res.render('timeline', {
botName: 'Toothless SQB Bot'
botName: 'Toothless SQB Bot',
serverCount
});
});
+248 -29
View File
@@ -3,8 +3,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Timeline - <%= botName %></title>
<meta name="description" content="The history of <%= botName %>, from a Discord side project to a War Thunder Squadron Battles companion used across the community.">
<title>SREBOT History</title>
<meta name="description" content="The history of SREBOT, starting from an idea to being the standard for SQB in War Thunder.">
<meta property="og:title" content="SREBOT History">
<meta property="og:description" content="The history of SREBOT, starting from an idea to being the standard for SQB in War Thunder.">
<meta property="og:type" content="website">
<meta name="theme-color" content="#90EE90">
<link rel="icon" type="image/png" href="/images/transparent_toothlessssss.png">
@@ -80,6 +83,22 @@
fill: #F5F5DC;
filter: drop-shadow(0 0 8px rgba(144, 238, 144, 0.7));
}
.timeline-line .end-marker {
fill: #1b1b1b;
stroke: rgba(144, 238, 144, 0.35);
stroke-width: 1.5;
}
.timeline-card-footer {
margin-top: 1rem;
padding-top: 0.75rem;
border-top: 1px solid rgba(245, 245, 220, 0.07);
display: flex;
align-items: center;
gap: 0.45rem;
color: rgba(144, 238, 144, 0.65);
font-size: 0.75rem;
letter-spacing: 0.06em;
}
/* The grid of milestone nodes. Default = single column (mobile). */
.timeline-grid {
@@ -94,6 +113,7 @@
.timeline-grid {
grid-template-columns: repeat(3, 1fr);
gap: 6rem 2.5rem;
align-items: start;
}
/* The line snakes 3 by 3: row 1 left to right, row 2 right to left,
row 3 left to right, so the connector never crosses itself. */
@@ -106,6 +126,9 @@
.timeline-node:nth-child(7) { grid-area: 3 / 1; }
.timeline-node:nth-child(8) { grid-area: 3 / 2; }
.timeline-node:nth-child(9) { grid-area: 3 / 3; }
.timeline-node:nth-child(10) { grid-area: 4 / 3; }
.timeline-node:nth-child(11) { grid-area: 4 / 2; }
.timeline-node:nth-child(12) { grid-area: 4 / 1; }
}
.timeline-card {
@@ -163,9 +186,49 @@
/* Reveal animation: node is always opaque (solid background blocks the SVG
line); only the inner card fades in. This prevents the line from being
visible through the semi-transparent card during the fade-in. */
#scroll-hint {
position: fixed;
bottom: 3.5rem;
left: 50%;
transform: translateX(-50%);
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.6rem;
color: rgba(245, 245, 220, 0.4);
font-size: 0.68rem;
letter-spacing: 0.22em;
text-transform: uppercase;
pointer-events: none;
transition: opacity 0.5s ease;
white-space: nowrap;
}
.hint-line {
width: 1px;
height: 52px;
background: rgba(245, 245, 220, 0.12);
position: relative;
overflow: hidden;
}
.hint-line::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 45%;
background: linear-gradient(to bottom, transparent, #90EE90, transparent);
animation: hintScroll 1.5s ease-in-out infinite;
}
@keyframes hintScroll {
0% { top: -45%; }
100% { top: 100%; }
}
.timeline-node {
position: relative;
z-index: 2;
border-radius: 1rem;
/* background is set dynamically in JS only when a card starts
revealing, so unrevealed cards never block the SVG track line */
}
@@ -192,8 +255,7 @@
<p class="text-muted text-sm font-semibold tracking-[0.25em] uppercase mb-4">Our Story</p>
<h1 class="text-4xl lg:text-6xl font-extrabold mb-5 gradient-text">The Timeline</h1>
<p class="text-lg text-white/70 max-w-2xl mx-auto">
Our history, the milestones we passed along the way, and the giants whose
shoulders helped us climb higher.
The history of SREBOT, starting from an idea to being the standard for SQB in War Thunder.
</p>
</div>
</section>
@@ -212,6 +274,7 @@
</defs>
<path class="track" id="timelineTrack" d=""></path>
<path class="progress" id="timelineProgress" d=""></path>
<circle class="end-marker" id="timelineEndMarker" r="5" cx="0" cy="0"></circle>
<circle class="comet" id="timelineComet" r="6" cx="0" cy="0" style="opacity:0"></circle>
</svg>
@@ -236,6 +299,9 @@
within a week, with match data still entered by hand. Within weeks, the prototype
is already being shared with other servers.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>2 Servers</span>
</div>
</div>
</div>
</article>
@@ -253,6 +319,30 @@
The project outgrows its original name. SNLK SQB BOT becomes
<strong class="text-accent">SREBOT</strong>, the identity it still carries today.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>4 Servers</span>
</div>
</div>
</div>
</article>
<article class="timeline-node">
<div class="timeline-card">
<div class="timeline-marker"><i class="fas fa-users"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">03 · Alliance</span>
<span class="timeline-date">January 2025</span>
</div>
<h3 class="timeline-title mb-3">Lux_ and Our Contributions to Each Other</h3>
<p class="timeline-desc">
Lux_ was working on his own bot at the time. We traded knowledge on War Thunder's
APIs and community management, back when neither of us knew how to read a winner
from a replay. His bot later became Spectra, and the cooperation never stopped.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>8 Servers</span>
</div>
</div>
</div>
</article>
@@ -262,15 +352,18 @@
<div class="timeline-marker"><i class="fas fa-file-code"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">03 · Parser</span>
<span class="timeline-date">January 2025</span>
<span class="timeline-index">04 · Parser</span>
<span class="timeline-date">February 2025</span>
</div>
<h3 class="timeline-title mb-3">Frovy Opens the Door</h3>
<p class="timeline-desc">
Frovy shows us the first parser and gives SREBOT a real path toward automatic
scoreboards. He had also figured out how to request clandata and receive
immediate point updates, even if he kept the method from us for months.
scoreboards. He had also figured out how to request data from the game and receive
immediate point updates, even if he kept the method from us for months. :)
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>15 Servers</span>
</div>
</div>
</div>
</article>
@@ -280,15 +373,18 @@
<div class="timeline-marker"><i class="fas fa-robot"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">04 · Logs</span>
<span class="timeline-date">March 13, 2025</span>
<span class="timeline-index">05 · Logs</span>
<span class="timeline-date">March 2025</span>
</div>
<h3 class="timeline-title mb-3">Dagor &amp; The First Logs</h3>
<h3 class="timeline-title mb-3">LivingTheDagor and the New Parser</h3>
<p class="timeline-desc">
Dagor is introduced and the second parser is integrated, a much more robust one
that SREBOT still uses today. The logging pipeline matures around the parser work,
making scoreboards more informative.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>20 Servers</span>
</div>
</div>
</div>
</article>
@@ -298,14 +394,17 @@
<div class="timeline-marker"><i class="fas fa-fire"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">05 · Momentum</span>
<span class="timeline-index">06 · Momentum</span>
<span class="timeline-date">July 2025</span>
</div>
<h3 class="timeline-title mb-3">Word Gets Around</h3>
<p class="timeline-desc">
Squadrons start to take notice. The bot picks up real momentum and grows
Squadrons start to take notice. The bot picks up momentum and grows
in popularity as more communities bring it into their servers.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>60 Servers</span>
</div>
</div>
</div>
</article>
@@ -315,15 +414,19 @@
<div class="timeline-marker"><i class="fas fa-laptop-code"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">06 · Website</span>
<span class="timeline-index">07 · Website</span>
<span class="timeline-date">August 2025</span>
</div>
<h3 class="timeline-title mb-3">The Site Goes Live</h3>
<h3 class="timeline-title mb-3">Clippi Builds the Website</h3>
<p class="timeline-desc">
Friendly competition with the Boris bot pushes the developers to raise
their game. The SREBOT website goes live, turning stats, scoreboards, and
replays into a browser experience players can explore.
Clippi (Sophie) joins, mostly fueled by her hatred of Boris Bot, and takes
the lead on the website. She also pushes us off of Replit and onto proper
infrastructure, forcing me to actually learn how this stuff works. The site
goes live and keeps growing from there.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>100 Servers</span>
</div>
</div>
</div>
</article>
@@ -333,7 +436,7 @@
<div class="timeline-marker"><i class="fas fa-handshake"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">07 · Partnership</span>
<span class="timeline-index">08 · Partnership</span>
<span class="timeline-date">January 2026</span>
</div>
<h3 class="timeline-title mb-3">Teaming Up with Spectra</h3>
@@ -342,6 +445,30 @@
consolidates and partners with Spectra to receive games directly, instead of
downloading and parsing every match on its own.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>340 Servers</span>
</div>
</div>
</div>
</article>
<article class="timeline-node">
<div class="timeline-card">
<div class="timeline-marker"><i class="fas fa-route"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">09 · Paths</span>
<span class="timeline-date">March 2026</span>
</div>
<h3 class="timeline-title mb-3">Max and His Paths</h3>
<p class="timeline-desc">
Max was reviving the WT Heatmaps project and gave us an API to request
images with player paths drawn on the map for each game. We do this
ourselves now, but it started here.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>450 Servers</span>
</div>
</div>
</div>
</article>
@@ -351,7 +478,7 @@
<div class="timeline-marker"><i class="fas fa-coins"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">08 · Monetization</span>
<span class="timeline-index">10 · Monetization</span>
<span class="timeline-date">April 2026</span>
</div>
<h3 class="timeline-title mb-3">Fueling the Future</h3>
@@ -359,6 +486,30 @@
Monetization brings in real income for the first time, funding better
servers and helping new features ship faster.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>580 Servers</span>
</div>
</div>
</div>
</article>
<article class="timeline-node">
<div class="timeline-card">
<div class="timeline-marker"><i class="fas fa-plug"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">11 · Clients</span>
<span class="timeline-date">May 2026</span>
</div>
<h3 class="timeline-title mb-3">Supporting Client Ports</h3>
<p class="timeline-desc">
We start supporting a client port of our project, letting other bots
build on top of what we made. First up is AXBot, which serves the
Chinese portion of the SQB playerbase.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span>600 Servers</span>
</div>
</div>
</div>
</article>
@@ -368,15 +519,18 @@
<div class="timeline-marker"><i class="fas fa-location-dot"></i></div>
<div class="pt-3">
<div class="flex items-center justify-between mb-2">
<span class="timeline-index">09 · Today</span>
<span class="timeline-index">12 · Today</span>
<span class="timeline-date">Now</span>
</div>
<h3 class="timeline-title mb-3">Where We Are Now</h3>
<p class="timeline-desc">
Automated parsing through Spectra, funded development, and a growing
community of squadrons define the project today. The next chapter is already
underway.
community of squadrons define the project today. We're already working on
our next product, <a href="https://tss.pawjob.us" target="_blank" rel="noopener" class="text-accent font-semibold hover:underline">TSSBOT</a>.
</p>
<div class="timeline-card-footer">
<i class="fab fa-discord"></i><span><%= serverCount %> Servers</span>
</div>
</div>
</div>
</article>
@@ -392,6 +546,12 @@
</div>
</section>
<!-- Scroll hint — fades out on first scroll -->
<div id="scroll-hint">
<span>SCROLL</span>
<div class="hint-line"></div>
</div>
<!-- Footer -->
<%- include('partials/footer') %>
@@ -426,6 +586,7 @@
if (!timeline || !svg) return;
var nodes = Array.prototype.slice.call(timeline.querySelectorAll('.timeline-node'));
var endMarker = document.getElementById('timelineEndMarker');
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var useGsap = !!(window.gsap && window.ScrollTrigger);
var drawProgress = 0; // 0 to 1, how much of the line is drawn
@@ -437,6 +598,7 @@
var lineTrigger = null;
var nodeTriggers = [];
var nextRevealIndex = 0;
var preRevealProgress = 0;
// Build a rounded-corner path that threads through a list of points.
function roundedPath(pts, radius) {
@@ -496,6 +658,11 @@
return nodePathProgress(node, index, pathLength);
});
if (endMarker && pts.length) {
var ep = pts[pts.length - 1];
endMarker.setAttribute('cx', ep.x.toFixed(1));
endMarker.setAttribute('cy', ep.y.toFixed(1));
}
updateComet(pathLength);
return { pts: pts, len: pathLength };
}
@@ -552,6 +719,23 @@
return lengthAtPoint(getMarkerCenter(node), len) / len;
}
function initPreReveal() {
var vh = window.innerHeight;
var sp = 0;
nodes.forEach(function (node, idx) {
if (node.getBoundingClientRect().top < vh * 0.95) {
sp = Math.max(sp, nodeProgresses[idx] || 0);
}
});
if (sp > state.progress) {
preRevealProgress = sp;
drawProgress = sp;
state.progress = sp;
targetDrawProgress = sp;
renderProgress();
}
}
function revealReachedNodes() {
// Scan forward only — nodes are in path order so we never need to re-check
while (nextRevealIndex < nodes.length) {
@@ -561,8 +745,11 @@
nodeRevealed[nextRevealIndex] = true;
node.classList.add('is-visible');
if (useGsap) {
node.style.background = '#1b1b1b';
gsap.fromTo(node, { y: -16, scale: 0.97 }, { y: 0, scale: 1, duration: 0.55, ease: 'power2.out', overwrite: true });
node.style.background = 'rgba(27,27,27,0)';
gsap.fromTo(node,
{ y: -16, scale: 0.97, backgroundColor: 'rgba(27,27,27,0)' },
{ y: 0, scale: 1, backgroundColor: 'rgba(27,27,27,1)', duration: 0.55, ease: 'power2.out', overwrite: true,
onComplete: function () { node.style.background = '#1b1b1b'; } });
var card = node.querySelector('.timeline-card');
if (card) gsap.fromTo(card, { opacity: 0 }, { opacity: 1, duration: 0.55, ease: 'power2.out' });
}
@@ -646,12 +833,12 @@
trigger: timeline,
start: 'top 70%',
endTrigger: nodes[nodes.length - 1] || timeline,
end: 'top 40%',
end: 'top 60%',
scrub: 0.6,
invalidateOnRefresh: true,
onUpdate: function (self) {
var p = Math.min(1, Math.max(0, self.progress));
if (p <= state.progress + 0.0005) return;
var p = Math.min(1, preRevealProgress + self.progress * (1 - preRevealProgress));
if (p <= state.progress) return;
state.progress = p;
targetDrawProgress = p;
drawProgress = p;
@@ -673,6 +860,23 @@
createScrollTriggers();
renderProgress();
ScrollTrigger.refresh();
requestAnimationFrame(function () {
if (lineTrigger && lineTrigger.progress > state.progress) {
var p = lineTrigger.progress;
state.progress = p;
drawProgress = p;
targetDrawProgress = p;
renderProgress();
}
initPreReveal();
});
var hint = document.getElementById('scroll-hint');
function hideScrollHint() {
if (!hint) return;
hint.style.opacity = '0';
hint = null;
}
if (window.Lenis) {
var lenis = new Lenis({
@@ -680,9 +884,14 @@
easing: function (t) { return 1 - Math.pow(1 - t, 3); },
smoothWheel: true
});
lenis.on('scroll', ScrollTrigger.update);
lenis.on('scroll', function () {
hideScrollHint();
ScrollTrigger.update();
});
gsap.ticker.add(function (time) { lenis.raf(time * 1000); });
gsap.ticker.lagSmoothing(0);
} else {
window.addEventListener('scroll', hideScrollHint, { once: true });
}
} else {
advanceTo(1, 0);
@@ -713,6 +922,16 @@
if (!reduceMotion && useGsap) {
createScrollTriggers();
ScrollTrigger.refresh();
requestAnimationFrame(function () {
if (lineTrigger && lineTrigger.progress > state.progress) {
var p = lineTrigger.progress;
state.progress = p;
drawProgress = p;
targetDrawProgress = p;
renderProgress();
}
initPreReveal();
});
}
});
})();