-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
128 lines (114 loc) · 3.65 KB
/
script.js
File metadata and controls
128 lines (114 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Terminal typewriter animation
(function () {
const terminal = document.getElementById('terminal');
if (!terminal) return;
const lines = [
{ type: 'prompt', text: '$ ' },
{ type: 'command', text: 'kanbu start --workspace mblock', delay: 40 },
{ type: 'newline' },
{ type: 'output', text: '[kanbu] 154 MCP tools loaded ', instant: true },
{ type: 'success', text: '✓', instant: true },
{ type: 'newline' },
{ type: 'output', text: '[kanbu] Knowledge graph: online ', instant: true },
{ type: 'success', text: '✓', instant: true },
{ type: 'newline' },
{ type: 'pause', duration: 400 },
{ type: 'prompt', text: '$ ' },
{ type: 'command', text: 'sdr start --mode ads-b', delay: 40 },
{ type: 'newline' },
{ type: 'output', text: '[sdr] Tracking 12 aircraft above BRU ', instant: true },
{ type: 'success', text: '✓', instant: true },
{ type: 'newline' },
{ type: 'pause', duration: 400 },
{ type: 'prompt', text: '$ ' },
{ type: 'command', text: 'tandem status', delay: 40 },
{ type: 'newline' },
{ type: 'output', text: '[tandem] Browser ready, 180 threats blocked today ', instant: true },
{ type: 'success', text: '✓', instant: true },
{ type: 'newline' },
{ type: 'pause', duration: 300 },
{ type: 'prompt', text: '$ ' },
{ type: 'cursor' },
];
let currentSpan = null;
function createSpan(className) {
const span = document.createElement('span');
span.className = className;
terminal.appendChild(span);
return span;
}
async function typeChar(span, char, delay) {
return new Promise(resolve => {
setTimeout(() => {
span.textContent += char;
resolve();
}, delay);
});
}
async function pause(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
async function runTerminal() {
for (const line of lines) {
if (line.type === 'newline') {
terminal.appendChild(document.createElement('br'));
continue;
}
if (line.type === 'pause') {
await pause(line.duration);
continue;
}
if (line.type === 'cursor') {
const cursor = document.createElement('span');
cursor.className = 'cursor';
terminal.appendChild(cursor);
continue;
}
currentSpan = createSpan(line.type);
if (line.instant) {
currentSpan.textContent = line.text;
await pause(80);
} else {
for (const char of line.text) {
await typeChar(currentSpan, char, line.delay || 0);
}
}
}
}
// Start after a short delay
setTimeout(runTerminal, 600);
})();
// Nav scroll behavior
(function () {
const navbar = document.getElementById('navbar');
if (!navbar) return;
window.addEventListener('scroll', () => {
if (window.scrollY > 40) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
}, { passive: true });
})();
// Scroll reveal animation
(function () {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry, i) => {
if (entry.isIntersecting) {
// Stagger the animation based on position in grid
const siblings = entry.target.parentElement.children;
const index = Array.from(siblings).indexOf(entry.target);
setTimeout(() => {
entry.target.classList.add('visible');
}, index * 100);
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
document.querySelectorAll('.project-card, .skill-group, .hire-card, .exp-item').forEach(el => {
observer.observe(el);
});
})();