PageSourceSearch

https://uncledrew.vercel.app/Js/script.js

js uncledrew.vercel.app collected 2026-10-03 07:56:09 UTC 11,203 bytes, 319 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2    const iconsContainer = document.querySelector('.social-icons-container');
3    const radialSection = document.querySelector('.radial-section');
4
5    // We adjust the radius dynamically to support smaller mobile screens.
6    // We adjust the radius dynamically to support smaller mobile screens.
7    let radius = getRadius();
8    let centerX = radialSection.clientWidth / 2;
9    let centerY = radialSection.clientHeight / 2;
10
11    // --- Rotation and Interactivity State ---
12    let currentRotation = 0; // Total rotation in radians
13    let isDragging = false;
14    let startAngle = 0;
15    let lastRotation = 0;
16    let velocity = 0;
17    let lastUpdateTime = 0;
18    let animationId = null;
19
20    function getRadius() {
21        // Calculate max allowed radius based on screen width with padding to avoid overflow
22        return Math.min(150, (radialSection.clientWidth / 2) - 40);
23    }
24
25    const icons = [
26        { class: 'fa-solid fa-briefcase', styleClass: 'portfolio', href: 'https://uncledrewdev.vercel.app/' },
27        { class: 'fa-brands fa-linkedin-in', styleClass: 'linkedin', href: 'https://www.linkedin.com/in/uncledrewdev' },
28        { class: 'fa-brands fa-dev', styleClass: 'devto', href: 'https://dev.to/uncle_drew' },
29        { class: 'fa-solid fa-code-branch', styleClass: 'builds', id: 'builds-btn', href: '#' },
30        // { class: 'fa-brands fa-x-twitter', styleClass: 'twitter', href: 'https://x.com/UncleDrewDev' }, // Updated to X icon
31        { class: 'fa-solid fa-wallet', styleClass: 'wallet', id: 'support-btn', href: 'https://selar.com/showlove/uncledrewdev' },
32        { class: 'fa-brands fa-github', styleClass: 'github', href: 'https://github.com/Tremy-t' },
33        { class: 'fas fa-shopping-bag', styleClass: 'store', href: 'https://selar.com/m/UDrew' }
34    ];
35
36    // Add the connecting ring
37    const ring = document.createElement('div');
38    ring.className = 'ring';
39    radialSection.appendChild(ring);
40
41    const numIcons = icons.length;
42    // We want to offset so an icon or gap aligns nicely.
43    // Let's start the first icon near the top. offset by -PI/2.
44    const baseAngleOffset = -Math.PI / 2;
45
46    // Create elements
47    const nodes = [];
48    const lines = [];
49
50    icons.forEach((icon, index) => {
51        // Create icon node
52        const node = document.createElement('a');
53        node.href = icon.href || '#';
54        if (icon.href !== '#') {
55            node.target = '_blank';
56            node.rel = 'noopener noreferrer';
57        } else {
58            node.addEventListener('click', (e) => e.preventDefault());
59        }
60        node.className = `social-node ${icon.styleClass}`;
61        if (icon.id) {
62            node.id = icon.id;
63        }
64
65        if (icon.src) {
66            const img = document.createElement('img');
67            img.src = icon.src;
68            img.alt = icon.styleClass;
69            img.className = 'custom-icon'; // styling hook
70            node.appendChild(img);
71        } else {
72            const i = document.createElement('i');
73            i.className = icon.class;
74            node.appendChild(i);
75        }
76
77        iconsContainer.appendChild(node);
78        nodes.push({ element: node, baseAngle: (index * (2 * Math.PI) / numIcons) });
79
80        // Draw connection line
81        const line = document.createElement('div');
82        line.className = 'line';
83        radialSection.appendChild(line);
84        lines.push(line);
85    });
86
87    // Positioning function to be called on load, resize, and during rotation
88    function setupPositions() {
89        radius = getRadius();
90        centerX = radialSection.clientWidth / 2;
91        centerY = radialSection.clientHeight / 2;
92
93        // Resize the inner ring continuously
94        ring.style.width = `${radius * 2}px`;
95        ring.style.height = `${radius * 2}px`;
96
97        nodes.forEach((nodeObj, index) => {
98            const angle = baseAngleOffset + nodeObj.baseAngle + currentRotation;
99            const x = (centerX + radius * Math.cos(angle)).toFixed(2);
100            const y = (centerY + radius * Math.sin(angle)).toFixed(2);
101            nodeObj.element.style.left = `${x}px`;
102            nodeObj.element.style.top = `${y}px`;
103
104            // Adjust line width to new radius and rotation
105            if (lines[index]) {
106                lines[index].style.width = `${radius}px`;
107                const degrees = (angle * (180 / Math.PI)).toFixed(2);
108                lines[index].style.transform = `rotate(${degrees}deg)`;
109            }
110        });
111    }
112
113    // --- Interaction Handlers ---
114
115    // Scroll wheel rotation
116    radialSection.addEventListener('wheel', (e) => {
117        e.preventDefault();
118        const delta = e.deltaY || e.deltaX;
119        currentRotation += delta * 0.005;
120        velocity = delta * 0.002; // Simple momentum
121        cancelAnimationFrame(animationId);
122        setupPositions();
123        applyInertia();
124    }, { passive: false });
125
126    // Helper to get angle from center to mouse/touch point
127    function getAngle(x, y) {
128        const rect = radialSection.getBoundingClientRect();
129        const centerX = rect.left + rect.width / 2;
130        const centerY = rect.top + rect.height / 2;
131        return Math.atan2(y - centerY, x - centerX);
132    }
133
134    function handleStart(e) {
135        const point = e.touches ? e.touches[0] : e;
136        isDragging = true;
137        startAngle = getAngle(point.clientX, point.clientY) - currentRotation;
138        lastRotation = currentRotation;
139        lastUpdateTime = performance.now();
140        velocity = 0;
141        cancelAnimationFrame(animationId);
142        radialSection.style.cursor = 'grabbing';
143    }
144
145    function handleMove(e) {
146        if (!isDragging) return;
147        const point = e.touches ? e.touches[0] : e;
148        const newAngle = getAngle(point.clientX, point.clientY);
149        const newRotation = newAngle - startAngle;
150
151        const now = performance.now();
152        const dt = now - lastUpdateTime;
153        if (dt > 0) {
154            velocity = (newRotation - currentRotation) / dt * 16; // Velocity per frame (~16ms)
155        }
156
157        currentRotation = newRotation;
158        lastUpdateTime = now;
159        setupPositions();
160    }
161
162    function handleEnd() {
163        if (!isDragging) return;
164        isDragging = false;
165        radialSection.style.cursor = 'grab';
166        applyInertia();
167    }
168
169    function applyInertia() {
170        if (Math.abs(velocity) < 0.001) return;
171
172        currentRotation += velocity;
173        velocity *= 0.95; // Friction
174        setupPositions();
175        animationId = requestAnimationFrame(applyInertia);
176    }
177
178    radialSection.style.cursor = 'grab';
179    radialSection.addEventListener('mousedown', handleStart);
180    window.addEventListener('mousemove', handleMove);
181    window.addEventListener('mouseup', handleEnd);
182
183    radialSection.addEventListener('touchstart', handleStart, { passive: false });
184    window.addEventListener('touchmove', handleMove, { passive: false });
185    window.addEventListener('touchend', handleEnd);
186
187    // Initial position setup
188    setupPositions();
189
190    // --- Navigation Logic ---
191    const navItems = document.querySelectorAll('.nav-item');
192    const views = document.querySelectorAll('.view');
193
194    function switchView(viewId) {
195        // Update Nav UI
196        navItems.forEach(item => {
197            if (item.getAttribute('data-view') === viewId) {
198                item.classList.add('active');
199            } else {
200                item.classList.remove('active');
201            }
202        });
203
204        // Update Views
205        views.forEach(view => {
206            if (view.id === `${viewId}-view`) {
207                view.classList.add('active');
208            } else {
209                view.classList.remove('active');
210            }
211        });
212
213        // Special handling for Home View (Radial Section)
214        if (viewId === 'home') {
215            // Recalculate positions to ensure everything is aligned
216            setTimeout(setupPositions, 10);
217        }
218    }
219
220    navItems.forEach(item => {
221        item.addEventListener('click', () => {
222            const viewId = item.getAttribute('data-view');
223
224            // External Redirection for specific views
225            if (viewId === 'store') {
226                window.open('https://selar.com/m/UDrew', '_blank');
227                return;
228            }
229            if (viewId === 'prompts') {
230                // window.open('https://androdojo.vercel.app/', '_blank');
231                return;
232            }
233            if (viewId === 'portfolio') {
234                window.open('https://uncledrewdev.vercel.app/', '_blank');
235                return;
236            }
237
238            switchView(viewId);
239        });
240    });
241
242
243    // Handle resizing
244    window.addEventListener('resize', () => {
245        if (document.getElementById('home-view').classList.contains('active')) {
246            setupPositions();
247        }
248    });
249
250
251    // Modal Logic Helper
252    function setupModal(btnId, modalId, closeId) {
253        const btn = document.getElementById(btnId);
254        const modal = document.getElementById(modalId);
255        const closeBtn = document.getElementById(closeId);
256
257        if (btn && modal && closeBtn) {
258            btn.addEventListener('click', (e) => {
259                e.stopPropagation();
260                modal.classList.add('active');
261                document.body.style.overflow = 'hidden';
262            });
263
264            closeBtn.addEventListener('click', () => {
265                modal.classList.remove('active');
266                if (!document.querySelector('.modal-overlay.active')) {
267                    document.body.style.overflow = '';
268                }
269            });
270
271            modal.addEventListener('click', (e) => {
272                if (e.target === modal) {
273                    modal.classList.remove('active');
274                    if (!document.querySelector('.modal-overlay.active')) {
275                        document.body.style.overflow = '';
276                    }
277                }
278            });
279        }
280    }
281
282    setupModal('profile-container', 'profile-modal', 'profile-modal-close');
283    
284    // Add listener for builds radial button
285    const buildsBtn = document.getElementById('builds-btn');
286    if (buildsBtn) {
287        buildsBtn.addEventListener('click', (e) => {
288            e.preventDefault();
289            switchView('builds');
290        });
291    }
292
293    // Universal Close on Escape key
294    document.addEventListener('keydown', (e) => {
295        if (e.key === 'Escape') {
296            const activeModal = document.querySelector('.modal-overlay.active');
297            if (activeModal) {
298                activeModal.classList.remove('active');
299                document.body.style.overflow = '';
300            }
301        }
302    });
303});
304
305
306setupModal('profile-container', 'profile-modal', 'profile-modal-close');
307// Removed builds-btn modal setup since it's now a view
308
309// Universal Close on Escape key
310document.addEventListener('keydown', (e) => {
311    if (e.key === 'Escape') {
312        const activeModal = document.querySelector('.modal-overlay.active');
313        if (activeModal) {
314            activeModal.classList.remove('active');
315            document.body.style.overflow = '';
316        }
317    }
318});
319

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.