Skip to content

Commit 89f66ad

Browse files
authored
Add files via upload
1 parent 1b56988 commit 89f66ad

4 files changed

Lines changed: 568 additions & 0 deletions

File tree

index.html

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6+
<title>Javagar's Projects</title>
7+
8+
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%23080808%22/><text x=%2250%22 y=%2275%22 font-family=%22sans-serif%22 font-weight=%22bold%22 font-size=%2270%22 fill=%22%23D4AF37%22 text-anchor=%22middle%22>J</text><rect x=%225%22 y=%225%22 width=%2290%22 height=%2290%22 rx=%2215%22 fill=%22none%22 stroke=%22%23D4AF37%22 stroke-width=%225%22/></svg>">
9+
10+
<link rel="preconnect" href="https://fonts.googleapis.com">
11+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12+
<link href="https://fonts.googleapis.com/css2?family=Audiowide&family=Fascinate+Inline&display=swap" rel="stylesheet">
13+
14+
<link rel="stylesheet" href="style.css">
15+
</head>
16+
<body>
17+
<div id="ui-layer">
18+
<div class="top-section">
19+
<header>
20+
<h1 class="fascinate-font">Javagar's Projects</h1>
21+
<div class="subtitle audiowide-font">Link Page</div>
22+
</header>
23+
24+
<div class="search-container">
25+
<input type="text" id="search-input" placeholder="Search Node..." autocomplete="off">
26+
</div>
27+
</div>
28+
29+
<div class="controls">
30+
<button id="toggle-motion" aria-pressed="false">Pause Motion</button>
31+
<div class="footer audiowide-font" style="margin-top: 15px; font-size: 0.65rem; color: #666; letter-spacing: 1px;">
32+
[ TAB ] NAVIGATE | [ CLICK ] View
33+
</div>
34+
</div>
35+
</div>
36+
37+
<div id="scene-container"></div>
38+
<canvas id="connections"></canvas>
39+
40+
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
41+
42+
<script src="script.js"></script>
43+
</body>
44+
</html>

links.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
- text: "TOTP Research"
2+
url: "https://totp-research.kudos-kebab-kept.workers.dev/"
3+
- text: "Authenticator"
4+
url: "https://authenticator.kudos-kebab-kept.workers.dev/"
5+
- text: "Bio Audio Research"
6+
url: "https://bio-audio-research.kudos-kebab-kept.workers.dev/"
7+
- text: "Neon Blob Jump"
8+
url: "https://neon-blob-jump.kudos-kebab-kept.workers.dev/"
9+
- text: "Digital Garden"
10+
url: "https://digital-garden.kudos-kebab-kept.workers.dev/"
11+
- text: "Shift Context"
12+
url: "https://shift-context.kudos-kebab-kept.workers.dev/"
13+
- text: "Encrypt Tool"
14+
url: "https://java-idl.github.io/Encrypt/"
15+
- text: "Smart Blob"
16+
url: "https://java-idl.github.io/Smart-Blob-Engine/"
17+
- text: "Javagar's Authenticator Audit"
18+
url: "https://javagar-authenticator-audit.vercel.app/"

script.js

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
const canvas = document.getElementById('connections');
2+
const ctx = canvas.getContext('2d');
3+
const scene = document.getElementById('scene-container');
4+
const motionBtn = document.getElementById('toggle-motion');
5+
const searchInput = document.getElementById('search-input');
6+
7+
// --- Configuration ---
8+
const config = {
9+
sphereRadius: 280,
10+
baseRotationSpeed: 0.002,
11+
mouseSensitivity: 0.00008,
12+
perspective: 800,
13+
connectionDistance: 120,
14+
isPaused: false
15+
};
16+
17+
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
18+
if (mediaQuery.matches) {
19+
config.isPaused = true;
20+
motionBtn.textContent = "Resume Motion";
21+
motionBtn.setAttribute('aria-pressed', 'true');
22+
}
23+
24+
let links = [];
25+
let points = [];
26+
let width, height, cx, cy;
27+
let mouseX = window.innerWidth / 2;
28+
let mouseY = window.innerHeight / 2;
29+
let isMouseOver = false;
30+
let focusedPoint = null;
31+
let searchedPoint = null;
32+
let isSearchHovering = false;
33+
let currentRotX = 0;
34+
let currentRotY = 0;
35+
36+
// --- Load Data ---
37+
fetch('links.yaml')
38+
.then(response => response.text())
39+
.then(yamlText => {
40+
links = jsyaml.load(yamlText);
41+
init();
42+
})
43+
.catch(error => console.error('Error loading links.yaml:', error));
44+
45+
class Point {
46+
constructor(data, id, total) {
47+
this.data = data;
48+
const phi = Math.acos(1 - 2 * (id + 0.5) / total);
49+
const theta = Math.PI * (1 + Math.sqrt(5)) * (id + 0.5);
50+
this.x = config.sphereRadius * Math.sin(phi) * Math.cos(theta);
51+
this.y = config.sphereRadius * Math.sin(phi) * Math.sin(theta);
52+
this.z = config.sphereRadius * Math.cos(phi);
53+
this.screenX = 0;
54+
this.screenY = 0;
55+
this.alpha = 1;
56+
57+
this.element = document.createElement('a');
58+
this.element.href = data.url;
59+
this.element.className = 'node-link';
60+
this.element.target = "_blank";
61+
this.element.setAttribute('aria-label', `Visit ${data.text}`);
62+
63+
this.element.innerHTML = `
64+
<div class="node-dot"></div>
65+
<span class="node-text">${data.text}</span>
66+
`;
67+
68+
this.element.addEventListener('focus', () => {
69+
focusedPoint = this;
70+
config.isPaused = true;
71+
});
72+
73+
this.element.addEventListener('blur', () => {
74+
focusedPoint = null;
75+
if (!mediaQuery.matches && motionBtn.innerText === "PAUSE MOTION") {
76+
config.isPaused = false;
77+
}
78+
});
79+
80+
scene.appendChild(this.element);
81+
}
82+
83+
rotate(angleX, angleY) {
84+
let cosY = Math.cos(angleY);
85+
let sinY = Math.sin(angleY);
86+
let x1 = this.x * cosY - this.z * sinY;
87+
let z1 = this.z * cosY + this.x * sinY;
88+
let cosX = Math.cos(angleX);
89+
let sinX = Math.sin(angleX);
90+
let y1 = this.y * cosX - z1 * sinX;
91+
let z2 = z1 * cosX + this.y * sinX;
92+
this.x = x1;
93+
this.y = y1;
94+
this.z = z2;
95+
}
96+
97+
updateDOM() {
98+
const scale = config.perspective / (config.perspective - this.z);
99+
this.screenX = this.x * scale;
100+
this.screenY = this.y * scale;
101+
this.alpha = Math.max(0.2, (this.z + config.sphereRadius) / (2 * config.sphereRadius));
102+
103+
this.element.style.transform = `translate3d(${this.screenX}px, ${this.screenY}px, 0) scale(${scale}) translate(-50%, -50%)`;
104+
this.element.style.opacity = this.alpha;
105+
this.element.style.zIndex = Math.floor(this.z + config.sphereRadius) + 100;
106+
107+
const blurAmount = Math.max(0, (config.sphereRadius - this.z) / 80);
108+
this.element.style.filter = `blur(${blurAmount}px)`;
109+
this.element.style.visibility = 'visible';
110+
}
111+
}
112+
113+
function init() {
114+
resize();
115+
points = links.map((link, i) => new Point(link, i, links.length));
116+
loop();
117+
}
118+
119+
function resize() {
120+
width = canvas.width = window.innerWidth;
121+
height = canvas.height = window.innerHeight;
122+
cx = width / 2;
123+
cy = height / 2;
124+
const isPortrait = width < height;
125+
const multiplier = isPortrait ? 0.42 : 0.35;
126+
config.sphereRadius = Math.min(width, height) * multiplier;
127+
if (!isMouseOver) {
128+
mouseX = cx;
129+
mouseY = cy;
130+
}
131+
}
132+
133+
function drawConnections() {
134+
ctx.clearRect(0, 0, width, height);
135+
ctx.save();
136+
ctx.translate(cx, cy);
137+
ctx.strokeStyle = 'rgba(212, 175, 55, 0.2)';
138+
ctx.lineWidth = 1;
139+
for (let i = 0; i < points.length; i++) {
140+
const p1 = points[i];
141+
if (p1.alpha < 0.3) continue;
142+
for (let j = i + 1; j < points.length; j++) {
143+
const p2 = points[j];
144+
if (p2.alpha < 0.3) continue;
145+
const dx = p1.x - p2.x;
146+
const dy = p1.y - p2.y;
147+
const dz = p1.z - p2.z;
148+
const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
149+
if (dist < config.connectionDistance) {
150+
ctx.beginPath();
151+
ctx.moveTo(p1.screenX, p1.screenY);
152+
ctx.lineTo(p2.screenX, p2.screenY);
153+
ctx.stroke();
154+
}
155+
}
156+
}
157+
ctx.restore();
158+
}
159+
160+
searchInput.addEventListener('mouseenter', () => { isSearchHovering = true; });
161+
searchInput.addEventListener('mouseleave', () => { isSearchHovering = false; });
162+
searchInput.addEventListener('touchstart', () => { isSearchHovering = true; }, {passive: true});
163+
searchInput.addEventListener('touchend', () => { setTimeout(() => isSearchHovering = false, 5000); });
164+
165+
searchInput.addEventListener('input', (e) => {
166+
const val = e.target.value.toLowerCase().trim();
167+
points.forEach(p => p.element.classList.remove('is-searched'));
168+
searchedPoint = null;
169+
if (val.length >= 2) {
170+
const match = points.find(p => p.data.text.toLowerCase().includes(val));
171+
if (match) {
172+
searchedPoint = match;
173+
match.element.classList.add('is-searched');
174+
}
175+
}
176+
});
177+
178+
function loop() {
179+
let targetRotX = 0;
180+
let targetRotY = 0;
181+
const activeTarget = focusedPoint || searchedPoint;
182+
if (activeTarget) {
183+
const k = 0.05;
184+
targetRotY = Math.atan2(activeTarget.x, activeTarget.z) * k;
185+
targetRotX = Math.atan2(activeTarget.y, activeTarget.z) * k;
186+
} else if (isSearchHovering) {
187+
targetRotY = 0.005;
188+
targetRotX = 0;
189+
} else if (!config.isPaused) {
190+
if (isMouseOver) {
191+
targetRotY = (mouseX - cx) * config.mouseSensitivity;
192+
targetRotX = (mouseY - cy) * config.mouseSensitivity;
193+
let nearestDist = Infinity;
194+
points.forEach(p => {
195+
if (p.z > 0) {
196+
const dx = mouseX - (cx + p.screenX);
197+
const dy = mouseY - (cy + p.screenY);
198+
const d = Math.sqrt(dx*dx + dy*dy);
199+
if (d < nearestDist) nearestDist = d;
200+
}
201+
});
202+
const brakeThreshold = 120;
203+
if (nearestDist < brakeThreshold) {
204+
const brakeFactor = Math.max(0.05, nearestDist / brakeThreshold);
205+
targetRotY *= brakeFactor;
206+
targetRotX *= brakeFactor;
207+
}
208+
} else {
209+
targetRotY = config.baseRotationSpeed;
210+
}
211+
}
212+
const smoothness = 0.05;
213+
currentRotX += (targetRotX - currentRotX) * smoothness;
214+
currentRotY += (targetRotY - currentRotY) * smoothness;
215+
points.forEach(p => {
216+
p.rotate(currentRotX, currentRotY);
217+
p.updateDOM();
218+
});
219+
drawConnections();
220+
requestAnimationFrame(loop);
221+
}
222+
223+
window.addEventListener('resize', resize);
224+
window.addEventListener('mousemove', e => { mouseX = e.clientX; mouseY = e.clientY; isMouseOver = true; });
225+
window.addEventListener('mouseout', () => { isMouseOver = false; });
226+
window.addEventListener('touchmove', e => { mouseX = e.touches[0].clientX; mouseY = e.touches[0].clientY; isMouseOver = true; }, {passive: true});
227+
window.addEventListener('touchend', () => { isMouseOver = false; });
228+
229+
motionBtn.addEventListener('click', () => {
230+
config.isPaused = !config.isPaused;
231+
motionBtn.textContent = config.isPaused ? "Resume Motion" : "Pause Motion";
232+
motionBtn.setAttribute('aria-pressed', config.isPaused);
233+
});
234+
235+
window.addEventListener('keydown', (e) => {
236+
if (e.key === 'Tab') {
237+
const interactiveElements = [searchInput, motionBtn, ...document.querySelectorAll('.node-link')];
238+
const first = interactiveElements[0];
239+
const last = interactiveElements[interactiveElements.length - 1];
240+
if (e.shiftKey) {
241+
if (document.activeElement === first) { e.preventDefault(); last.focus(); }
242+
} else {
243+
if (document.activeElement === last) { e.preventDefault(); first.focus(); }
244+
}
245+
}
246+
});

0 commit comments

Comments
 (0)