Files
ivanch.me/js/social.js
Jose Henrique 72c7d6c9e0
Some checks failed
Homepage Build and Deploy (Test) / Build Homepage Image (push) Successful in 32s
Homepage Build and Deploy (Test) / Deploy Homepage (push) Failing after 3s
new pipeline and build improvements
2026-07-10 17:06:16 -03:00

112 lines
4.3 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// ----------------------------------------------------------------
// Avatar glitch layers — clone the avatar image twice (R + B
// channel shift clones) so the CSS hover animation has elements
// to slice/displace. Skipped when the user prefers reduced motion.
// ----------------------------------------------------------------
const avatar = document.querySelector('.avatar');
const avatarImg = avatar && avatar.querySelector('img');
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (avatar && avatarImg && !prefersReducedMotion) {
const createGlitchLayers = () => {
if (avatar.querySelector('.glitch-layer')) return;
['r', 'b'].forEach((channel) => {
const clone = avatarImg.cloneNode(false);
clone.classList.add('glitch-layer', channel);
clone.removeAttribute('alt');
clone.setAttribute('aria-hidden', 'true');
avatar.appendChild(clone);
});
};
const removeGlitchLayers = () => {
avatar.querySelectorAll('.glitch-layer').forEach((layer) => layer.remove());
};
avatar.addEventListener('pointerenter', createGlitchLayers);
avatar.addEventListener('pointerleave', removeGlitchLayers);
avatar.addEventListener('pointercancel', removeGlitchLayers);
}
// ----------------------------------------------------------------
// Email copy-to-clipboard and tooltip logic
// ----------------------------------------------------------------
const emailLink = document.getElementById('email-link');
const emailTooltip = document.getElementById('email-tooltip');
const obfuscatedEmail = 'Bg4aC0IJDAAeCBgbCU8AGA0PKQkBAAACQgIGAw=='; // generated through utils/xor-enc.py
const xorKey = 'lain';
function decodeEmail(obf, key) {
// Double base64 decode
let b64 = atob(obf);
// XOR decode
let email = '';
for (let i = 0; i < b64.length; i++) {
email += String.fromCharCode(b64.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return email;
}
const emailAddress = decodeEmail(obfuscatedEmail, xorKey);
if (emailLink && emailTooltip) {
let tooltipTimeout;
async function copyEmail() {
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(emailAddress);
return true;
} catch (error) {
// Fall through to the legacy copy path when clipboard permissions fail.
}
}
const textarea = document.createElement('textarea');
textarea.value = emailAddress;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
let copied = false;
try {
copied = document.execCommand('copy');
} catch (error) {
copied = false;
}
document.body.removeChild(textarea);
return copied;
}
emailLink.addEventListener('click', async function(e) {
e.preventDefault();
const copied = await copyEmail();
if (!copied) return;
// Position tooltip above email link
const emailRect = emailLink.getBoundingClientRect();
const socialLinksRect = emailLink.parentElement.getBoundingClientRect();
const left = emailRect.left - socialLinksRect.left + (emailRect.width / 2);
const top = emailRect.top - socialLinksRect.top - 10;
emailTooltip.style.left = left + 'px';
emailTooltip.style.top = top + 'px';
emailTooltip.style.transform = 'translateX(-50%) translateY(-75%)';
// Show tooltip only after the copy operation succeeds.
emailTooltip.classList.add('is-visible');
// Hide after 1.5s
clearTimeout(tooltipTimeout);
tooltipTimeout = setTimeout(() => {
emailTooltip.classList.remove('is-visible');
}, 1500);
});
}
});