Initial project structure
This commit is contained in:
+228
@@ -0,0 +1,228 @@
|
||||
/* ================================================
|
||||
FRAXLE.NET — main.js
|
||||
================================================ */
|
||||
|
||||
// --- Scroll position: always start at top unless coming from form ---
|
||||
if (history.scrollRestoration) {
|
||||
history.scrollRestoration = 'manual';
|
||||
}
|
||||
(function () {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
if (!params.has('sent') && !params.has('error')) {
|
||||
window.scrollTo(0, 0);
|
||||
if (window.location.hash) {
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// --- Hero starfield ---
|
||||
(function () {
|
||||
const container = document.getElementById('hero-stars');
|
||||
if (!container) return;
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes twinkle {
|
||||
0%, 100% { opacity: 0.2; transform: scale(0.8); }
|
||||
50% { opacity: 1; transform: scale(1.3); }
|
||||
}
|
||||
@keyframes shootingStar {
|
||||
0% { transform: translateX(0) translateY(0); opacity: 1; }
|
||||
100% { transform: translateX(200px) translateY(80px); opacity: 0; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Static stars
|
||||
for (let i = 0; i < 160; i++) {
|
||||
const s = document.createElement('div');
|
||||
const size = Math.random() * 2.5 + 0.5;
|
||||
s.style.cssText = [
|
||||
'position:absolute',
|
||||
`width:${size}px`,
|
||||
`height:${size}px`,
|
||||
`background:rgba(255,255,255,${(Math.random() * 0.5 + 0.3).toFixed(2)})`,
|
||||
'border-radius:50%',
|
||||
`top:${(Math.random() * 100).toFixed(2)}%`,
|
||||
`left:${(Math.random() * 100).toFixed(2)}%`,
|
||||
`animation:twinkle ${(Math.random() * 3 + 2).toFixed(1)}s ease-in-out ${(Math.random() * 5).toFixed(1)}s infinite`,
|
||||
].join(';');
|
||||
container.appendChild(s);
|
||||
}
|
||||
|
||||
// Gold sparkles
|
||||
for (let i = 0; i < 28; i++) {
|
||||
const s = document.createElement('div');
|
||||
s.style.cssText = [
|
||||
'position:absolute',
|
||||
'width:3px', 'height:3px',
|
||||
'background:rgba(251,191,36,0.9)',
|
||||
'border-radius:50%',
|
||||
`top:${(Math.random() * 100).toFixed(2)}%`,
|
||||
`left:${(Math.random() * 100).toFixed(2)}%`,
|
||||
`animation:twinkle ${(Math.random() * 2 + 1.5).toFixed(1)}s ease-in-out ${(Math.random() * 4).toFixed(1)}s infinite`,
|
||||
].join(';');
|
||||
container.appendChild(s);
|
||||
}
|
||||
})();
|
||||
|
||||
// --- Nav scroll effect ---
|
||||
const header = document.getElementById('site-header');
|
||||
window.addEventListener('scroll', function () {
|
||||
header.classList.toggle('scrolled', window.scrollY > 60);
|
||||
}, { passive: true });
|
||||
|
||||
// --- Hamburger menu ---
|
||||
const hamburger = document.getElementById('nav-hamburger');
|
||||
const navLinks = document.getElementById('nav-links');
|
||||
|
||||
if (hamburger && navLinks) {
|
||||
hamburger.addEventListener('click', function () {
|
||||
const isOpen = navLinks.classList.toggle('open');
|
||||
hamburger.classList.toggle('open', isOpen);
|
||||
hamburger.setAttribute('aria-expanded', isOpen);
|
||||
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||
});
|
||||
|
||||
// Close on link click
|
||||
navLinks.querySelectorAll('a').forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
navLinks.classList.remove('open');
|
||||
hamburger.classList.remove('open');
|
||||
hamburger.setAttribute('aria-expanded', 'false');
|
||||
document.body.style.overflow = '';
|
||||
});
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!hamburger.contains(e.target) && !navLinks.contains(e.target)) {
|
||||
navLinks.classList.remove('open');
|
||||
hamburger.classList.remove('open');
|
||||
hamburger.setAttribute('aria-expanded', 'false');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Scroll reveal ---
|
||||
const reveals = document.querySelectorAll('.reveal');
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
|
||||
|
||||
reveals.forEach(function (el) { observer.observe(el); });
|
||||
} else {
|
||||
// Fallback: show all
|
||||
reveals.forEach(function (el) { el.classList.add('visible'); });
|
||||
}
|
||||
|
||||
// --- Magic wand button effect ---
|
||||
var WAND_CHARS = ['✦', '✧', '✶', '✦', '✧'];
|
||||
var WAND_COLORS = [
|
||||
'rgba(255,255,255,1)',
|
||||
'rgba(251,191,36,1)',
|
||||
'rgba(253,224,120,1)',
|
||||
'rgba(255,255,255,0.88)',
|
||||
];
|
||||
|
||||
function spawnSparkleCluster(btn, x, btnH) {
|
||||
var n = 16 + Math.floor(Math.random() * 13); // 16–28 per cluster
|
||||
for (var i = 0; i < n; i++) {
|
||||
var s = document.createElement('span');
|
||||
s.className = 'btn-sparkle';
|
||||
var ox = (Math.random() - 0.5) * 40; // ±20px horizontal spread
|
||||
var fy = Math.random() * btnH - 8; // shifted up a few px
|
||||
var sz = (0.28 + Math.random() * 0.55).toFixed(2);
|
||||
var dur = (0.38 + Math.random() * 0.45).toFixed(3);
|
||||
var col = WAND_COLORS[Math.floor(Math.random() * WAND_COLORS.length)];
|
||||
var ch = WAND_CHARS [Math.floor(Math.random() * WAND_CHARS.length)];
|
||||
s.textContent = ch;
|
||||
s.style.cssText = [
|
||||
'left:' + (x + ox).toFixed(1) + 'px',
|
||||
'top:' + fy.toFixed(1) + 'px',
|
||||
'font-size:' + sz + 'rem',
|
||||
'color:' + col,
|
||||
'text-shadow:0 0 5px ' + col + ',0 0 11px rgba(255,255,255,0.35)',
|
||||
'--dur:' + dur + 's',
|
||||
].join(';');
|
||||
btn.appendChild(s);
|
||||
(function (el, d) {
|
||||
setTimeout(function () { if (el.parentNode) el.remove(); }, (parseFloat(d) + 0.1) * 1000);
|
||||
}(s, dur));
|
||||
}
|
||||
}
|
||||
|
||||
function triggerWandEffect(btn) {
|
||||
if (btn.dataset.wandActive) return;
|
||||
btn.dataset.wandActive = '1';
|
||||
|
||||
var btnW = btn.offsetWidth;
|
||||
var btnH = btn.offsetHeight;
|
||||
var duration = 520;
|
||||
var start = null;
|
||||
var lastCluster = -1;
|
||||
|
||||
function ease(t) { return t < 0.5 ? 2*t*t : -1 + (4 - 2*t) * t; }
|
||||
|
||||
function frame(ts) {
|
||||
if (!start) start = ts;
|
||||
var elapsed = ts - start;
|
||||
var p = Math.min(elapsed / duration, 1);
|
||||
var x = p * btnW;
|
||||
|
||||
var ci = Math.floor(elapsed / 36);
|
||||
if (ci > lastCluster) {
|
||||
lastCluster = ci;
|
||||
spawnSparkleCluster(btn, x, btnH);
|
||||
}
|
||||
|
||||
if (p < 1) {
|
||||
requestAnimationFrame(frame);
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
delete btn.dataset.wandActive;
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
document.querySelectorAll('.btn').forEach(function (btn) {
|
||||
btn.addEventListener('mouseenter', function () {
|
||||
triggerWandEffect(btn);
|
||||
});
|
||||
});
|
||||
|
||||
// --- Stagger cards/rows within grids and lists ---
|
||||
document.querySelectorAll('.services-grid, .projects-list').forEach(function (grid) {
|
||||
grid.querySelectorAll('.reveal').forEach(function (card, i) {
|
||||
card.style.transitionDelay = (i * 0.1) + 's';
|
||||
});
|
||||
});
|
||||
|
||||
// --- Smooth active nav highlight on scroll ---
|
||||
const sections = document.querySelectorAll('section[id]');
|
||||
const navAnchors = document.querySelectorAll('.nav-links a[href^="#"]');
|
||||
|
||||
window.addEventListener('scroll', function () {
|
||||
let current = '';
|
||||
sections.forEach(function (sec) {
|
||||
if (window.scrollY >= sec.offsetTop - 120) {
|
||||
current = sec.getAttribute('id');
|
||||
}
|
||||
});
|
||||
navAnchors.forEach(function (a) {
|
||||
a.style.color = a.getAttribute('href') === '#' + current
|
||||
? 'var(--white)'
|
||||
: '';
|
||||
});
|
||||
}, { passive: true });
|
||||
Reference in New Issue
Block a user