mirror of
https://github.com/Alexander-D-Karpov/about.git
synced 2026-03-16 22:06:08 +03:00
648 lines
22 KiB
JavaScript
648 lines
22 KiB
JavaScript
(function(){
|
|
'use strict';
|
|
|
|
let isShuffling = false;
|
|
|
|
const $ = (q, c=document) => c.querySelector(q);
|
|
const $$ = (q, c=document) => Array.from(c.querySelectorAll(q));
|
|
const on = (el, ev, fn, opts) => el && el.addEventListener(ev, fn, opts);
|
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
const now = () => Date.now();
|
|
|
|
const throttle = (fn, ms = 100) => {
|
|
let t = 0, to, last;
|
|
return (...args) => {
|
|
const n = now();
|
|
if (n - t > ms) {
|
|
t = n;
|
|
fn(...args);
|
|
} else {
|
|
last = args;
|
|
clearTimeout(to);
|
|
to = setTimeout(() => {
|
|
t = now();
|
|
fn(...(last || []));
|
|
}, ms);
|
|
}
|
|
};
|
|
};
|
|
|
|
const isInteractive = (node) => !!node.closest('button, a, input, select, textarea, [contenteditable], .plugin-btn');
|
|
|
|
function ensureProjectsAlwaysLast(){
|
|
const mosaic = window.mosaicUtils?.getMosaic();
|
|
if (!mosaic) return;
|
|
|
|
const items = Array.from(mosaic.querySelectorAll('.projects-section, .projects-section.plugin'))
|
|
.map(n => n.closest('.plugin') || n);
|
|
|
|
items.forEach(node => {
|
|
if (node.parentElement === mosaic && node !== mosaic.lastElementChild) {
|
|
mosaic.appendChild(node);
|
|
}
|
|
});
|
|
|
|
$$('.plugin', mosaic).forEach((plugin, idx) => {
|
|
if (plugin.classList.contains('projects-section')) plugin.dataset.order = '9999';
|
|
else if (!plugin.dataset.order || +plugin.dataset.order >= 9999) plugin.dataset.order = String(idx);
|
|
});
|
|
}
|
|
|
|
let currentFilter = null;
|
|
let filterPopup;
|
|
|
|
function createFilterPopup(){
|
|
if (filterPopup) return filterPopup;
|
|
|
|
filterPopup = document.createElement('div');
|
|
filterPopup.className = 'tech-filter-popup';
|
|
|
|
filterPopup.innerHTML = `
|
|
<span class="filter-icon" style="font-size:18px;" aria-hidden="true">🔧</span>
|
|
<span class="filter-text">Filtering:</span>
|
|
<strong class="filter-tech" style="color:#7aa2ff;"></strong>
|
|
<button class="clear-filter-btn" type="button" aria-label="Clear project filter"
|
|
style="margin-left:4px;padding:6px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.2);background:rgba(255,107,107,.9);color:white;cursor:pointer;font-weight:700;font-size:12px;transition:all .2s ease;">
|
|
Clear ✕
|
|
</button>
|
|
`;
|
|
|
|
const clearBtn = filterPopup.querySelector('.clear-filter-btn');
|
|
clearBtn.addEventListener('mouseover', () => {
|
|
clearBtn.style.background = 'rgba(220,38,38,1)';
|
|
clearBtn.style.transform = 'scale(1.05)';
|
|
});
|
|
clearBtn.addEventListener('mouseout', () => {
|
|
clearBtn.style.background = 'rgba(255,107,107,.9)';
|
|
clearBtn.style.transform = 'scale(1)';
|
|
});
|
|
clearBtn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
clearTechFilter();
|
|
}, {passive: false});
|
|
|
|
document.body.appendChild(filterPopup);
|
|
return filterPopup;
|
|
}
|
|
|
|
function showFilterPopup(name){
|
|
const p = createFilterPopup();
|
|
const label = p.querySelector('.filter-tech');
|
|
if (label) label.textContent = name;
|
|
|
|
p.classList.add('show');
|
|
p.style.transition = 'opacity 200ms ease, transform 200ms ease';
|
|
p.style.transform = 'translateX(-50%) translateY(0)';
|
|
}
|
|
|
|
function hideFilterPopup() {
|
|
if (!filterPopup) return;
|
|
filterPopup.classList.remove('show');
|
|
}
|
|
|
|
function clearTechFilter(){
|
|
const projectsSection = document.querySelector('.projects-section, .projects-section.plugin, .plugin.projects-section');
|
|
if (!projectsSection) {
|
|
console.warn('Projects section not found for clearing');
|
|
return;
|
|
}
|
|
|
|
projectsSection.querySelectorAll('.project-card').forEach(card => {
|
|
card.style.transition = 'all 0.3s ease';
|
|
card.style.opacity = '1';
|
|
card.style.transform = 'scale(1)';
|
|
card.style.filter = 'none';
|
|
card.style.outline = '';
|
|
card.style.outlineOffset = '';
|
|
});
|
|
|
|
document.querySelectorAll('.tech-item.filtered').forEach(x => x.classList.remove('filtered'));
|
|
currentFilter = null;
|
|
|
|
hideFilterPopup();
|
|
|
|
if (window.mosaicUtils) window.mosaicUtils.resizeAll();
|
|
}
|
|
|
|
function applyTechFilter(name){
|
|
if (!name) return;
|
|
|
|
const techSection = document.querySelector('.tech-section, .tech-section.plugin, .plugin.tech-section');
|
|
const projectsSection = document.querySelector('.projects-section, .projects-section.plugin, .plugin.projects-section');
|
|
|
|
if (!projectsSection) {
|
|
console.warn('Projects section not found');
|
|
return;
|
|
}
|
|
|
|
console.log(`Applying filter for: ${name}`);
|
|
currentFilter = name;
|
|
|
|
if (techSection) {
|
|
techSection.querySelectorAll('.tech-item').forEach(item => {
|
|
const label = item.querySelector('.tech-name')?.textContent ||
|
|
item.title ||
|
|
item.querySelector('img')?.alt || '';
|
|
|
|
if (label.toLowerCase() === name.toLowerCase()) {
|
|
item.classList.add('filtered');
|
|
} else {
|
|
item.classList.remove('filtered');
|
|
}
|
|
});
|
|
}
|
|
|
|
let matchCount = 0;
|
|
const matchingCards = [];
|
|
|
|
projectsSection.querySelectorAll('.project-card').forEach(card => {
|
|
const tags = Array.from(card.querySelectorAll('.tech-tag'));
|
|
const tagTexts = tags.map(t => t.textContent.trim().toLowerCase());
|
|
const searchName = name.toLowerCase();
|
|
|
|
const isMatch = tagTexts.some(tag => tag === searchName);
|
|
|
|
card.style.transition = 'all 0.3s ease';
|
|
|
|
if (isMatch) {
|
|
card.style.opacity = '1';
|
|
card.style.transform = 'scale(1)';
|
|
card.style.filter = 'none';
|
|
matchingCards.push(card);
|
|
matchCount++;
|
|
} else {
|
|
card.style.opacity = '0.25';
|
|
card.style.transform = 'scale(0.96)';
|
|
card.style.filter = 'grayscale(70%)';
|
|
}
|
|
});
|
|
|
|
console.log(`Found ${matchCount} matching projects`);
|
|
|
|
if (matchCount > 0) {
|
|
showFilterPopup(name);
|
|
|
|
projectsSection.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
|
|
setTimeout(() => {
|
|
matchingCards.forEach(card => {
|
|
card.style.outline = '2px solid var(--accent)';
|
|
card.style.outlineOffset = '4px';
|
|
});
|
|
|
|
setTimeout(() => {
|
|
matchingCards.forEach(card => {
|
|
card.style.outline = '';
|
|
card.style.outlineOffset = '';
|
|
});
|
|
}, 2000);
|
|
}, 500);
|
|
} else {
|
|
console.warn(`No projects found matching "${name}"`);
|
|
clearTechFilter();
|
|
}
|
|
|
|
if (window.mosaicUtils) {
|
|
setTimeout(() => window.mosaicUtils.resizeAll(), 100);
|
|
}
|
|
}
|
|
|
|
function initTechFiltering() {
|
|
const techSection = document.querySelector('.tech-section, .tech-section.plugin, .plugin.tech-section');
|
|
const projectsSection = document.querySelector('.projects-section, .projects-section.plugin, .plugin.projects-section');
|
|
|
|
if (!techSection) return;
|
|
if (!projectsSection) return;
|
|
|
|
techSection.querySelectorAll('.tech-item').forEach(item => {
|
|
if (item.dataset.techFilterAttached === '1') return;
|
|
item.dataset.techFilterAttached = '1';
|
|
|
|
const name = item.querySelector('.tech-name')?.textContent ||
|
|
item.title ||
|
|
item.querySelector('img')?.alt || '';
|
|
|
|
if (!name) return;
|
|
|
|
item.style.cursor = 'pointer';
|
|
|
|
item.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
console.log(`Tech item clicked: ${name}`);
|
|
currentFilter = name;
|
|
applyTechFilter(name);
|
|
}, {passive: false});
|
|
|
|
// Replaced JS hover with CSS for performance
|
|
});
|
|
|
|
projectsSection.querySelectorAll('.tech-tag').forEach(tag => {
|
|
if (tag.dataset.techFilterAttached === '1') return;
|
|
tag.dataset.techFilterAttached = '1';
|
|
|
|
tag.style.cursor = 'pointer';
|
|
|
|
tag.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const name = tag.textContent.trim();
|
|
currentFilter = name;
|
|
|
|
projectsSection.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
|
|
setTimeout(() => {
|
|
applyTechFilter(name);
|
|
}, 300);
|
|
}, {passive: false});
|
|
});
|
|
|
|
if (!window.applyTechFilter) window.applyTechFilter = applyTechFilter;
|
|
if (!window.clearTechFilter) window.clearTechFilter = clearTechFilter;
|
|
}
|
|
|
|
function initCodeToggles(){
|
|
const sec = $('.code-section');
|
|
if (!sec) return;
|
|
|
|
$$('.section-toggle', sec).forEach(toggle => {
|
|
if (toggle.dataset.listenerAttached === '1') return;
|
|
toggle.dataset.listenerAttached = '1';
|
|
|
|
const id = toggle.dataset.target;
|
|
if (!id) return;
|
|
|
|
const content = sec.querySelector('#' + id);
|
|
const icon = toggle.querySelector('.toggle-icon');
|
|
if (!content || !icon) return;
|
|
|
|
if (id === 'wakatime-langs') {
|
|
content.classList.remove('collapsed');
|
|
icon.textContent = 'â–¼';
|
|
toggle.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
toggle.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const willCollapse = !content.classList.contains('collapsed');
|
|
content.classList.toggle('collapsed', willCollapse);
|
|
icon.textContent = willCollapse ? 'â–¶' : 'â–¼';
|
|
toggle.setAttribute('aria-expanded', willCollapse ? 'false' : 'true');
|
|
|
|
if (window.mosaicUtils) {
|
|
window.mosaicUtils.resizeAll();
|
|
setTimeout(() => window.mosaicUtils.resizeAll(), 50);
|
|
}
|
|
}, { passive: false });
|
|
});
|
|
}
|
|
|
|
function initLastFM(){
|
|
const sec = $('.lastfm-section'); if (!sec) return;
|
|
$$('.recent-track-item', sec).forEach(item => {
|
|
item.style.cursor = 'pointer';
|
|
// Hover handled by CSS
|
|
on(item, 'click', () => {
|
|
const t = $('.recent-track-name', item)?.textContent || '';
|
|
const a = $('.recent-track-artist', item)?.textContent || '';
|
|
if (t && a && window.playTrack) window.playTrack(`${a} ${t}`);
|
|
});
|
|
});
|
|
}
|
|
|
|
function initHealthInteractions() {
|
|
const healthSection = document.querySelector('.health-section');
|
|
if (!healthSection) return;
|
|
|
|
healthSection.querySelectorAll('.health-card').forEach(card => {
|
|
card.style.cursor = 'default';
|
|
});
|
|
}
|
|
|
|
function initMeme(){
|
|
const sec = document.querySelector('.meme-section');
|
|
if (!sec) return;
|
|
|
|
let btn = sec.querySelector('.meme-refresh-btn');
|
|
if (!btn){
|
|
const header = sec.querySelector('.plugin-header');
|
|
if (header){
|
|
const toolbar = header.querySelector('.plugin-toolbar');
|
|
if (toolbar) {
|
|
btn = document.createElement('button');
|
|
btn.className = 'icon-btn plugin-btn meme-refresh-btn';
|
|
btn.type = 'button';
|
|
btn.title = 'Random Meme';
|
|
btn.setAttribute('aria-label', 'Get random meme');
|
|
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
svg.setAttribute('viewBox', '0 0 24 24');
|
|
svg.setAttribute('width', '16');
|
|
svg.setAttribute('height', '16');
|
|
svg.setAttribute('fill', 'currentColor');
|
|
svg.innerHTML = '<path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm7 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-4 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm8 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-4 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/>';
|
|
|
|
btn.appendChild(svg);
|
|
toolbar.appendChild(btn);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (btn && btn.dataset.listenerAttached !== '1') {
|
|
btn.dataset.listenerAttached = '1';
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (btn.disabled) return;
|
|
|
|
btn.disabled = true;
|
|
const svg = btn.querySelector('svg');
|
|
if (svg) {
|
|
svg.style.animation = 'spin 0.8s linear infinite';
|
|
}
|
|
|
|
if (typeof window.refreshMeme === 'function') {
|
|
window.refreshMeme();
|
|
}
|
|
|
|
setTimeout(() => {
|
|
btn.disabled = false;
|
|
if (svg) {
|
|
svg.style.animation = '';
|
|
}
|
|
}, 1500);
|
|
}, {passive: false});
|
|
}
|
|
|
|
const content = sec.querySelector('.meme-content');
|
|
if (content) {
|
|
content.style.cursor = 'default';
|
|
}
|
|
}
|
|
|
|
function initVisitors(){ $$('.visitors-section .visitor-stat').forEach(s => s.style.cursor='pointer'); }
|
|
|
|
function initServices(){
|
|
const sec = $('.services-section');
|
|
if (!sec) return;
|
|
|
|
$$('.service-item', sec).forEach(card => {
|
|
const url = card.dataset.url;
|
|
if (!url) return;
|
|
|
|
const overlay = card.querySelector('.card-overlay');
|
|
if (overlay) {
|
|
overlay.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
}
|
|
|
|
// Hover handled by CSS
|
|
|
|
if (!overlay) {
|
|
card.style.cursor = 'pointer';
|
|
card.addEventListener('click', (e) => {
|
|
if (e.target.closest('a, button')) return;
|
|
window.open(url, '_blank');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function initWebring(){
|
|
const sec = $('.webring-section'); if (!sec) return;
|
|
const home = $('.webring-home', sec);
|
|
if (home) on(home, 'click', (e) => {
|
|
e.preventDefault();
|
|
const base = sec.dataset.baseUrl;
|
|
if (base) window.open(base, '_blank');
|
|
});
|
|
}
|
|
|
|
function initNeofetchSwitch(){
|
|
const sec = document.querySelector('.neofetch-section');
|
|
if (!sec) return;
|
|
|
|
autoScaleAllNeofetch();
|
|
|
|
sec.querySelectorAll('.machine-btn').forEach(btn => {
|
|
if (btn.dataset.listenerAttached === '1') return;
|
|
btn.dataset.listenerAttached = '1';
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
sec.querySelectorAll('.machine-btn').forEach(b => {
|
|
b.classList.remove('active');
|
|
b.removeAttribute('data-active');
|
|
});
|
|
btn.classList.add('active');
|
|
btn.setAttribute('data-active','true');
|
|
|
|
const idx = btn.dataset.machine || '0';
|
|
sec.querySelectorAll('.neofetch-output').forEach(o => o.style.display = 'none');
|
|
const out = sec.querySelector(`#neofetch-${idx}`);
|
|
if (out) out.style.display = 'block';
|
|
|
|
requestAnimationFrame(autoScaleAllNeofetch);
|
|
setTimeout(() => {
|
|
if (window.mosaicUtils) window.mosaicUtils.resizeAll();
|
|
}, 50);
|
|
});
|
|
});
|
|
|
|
window.addEventListener('resize', throttle(autoScaleAllNeofetch, 100));
|
|
}
|
|
|
|
function autoScaleAllNeofetch(){
|
|
document.querySelectorAll('.neofetch-output').forEach(out => {
|
|
if (out.style.display === 'none') return;
|
|
const term = out.querySelector('.terminal');
|
|
const pre = out.querySelector('.neofetch-pre');
|
|
if (!term || !pre) return;
|
|
|
|
pre.style.transform = '';
|
|
|
|
const maxW = term.clientWidth - 20;
|
|
const needW = pre.scrollWidth;
|
|
const scale = needW > maxW ? Math.max(0.6, maxW/needW) : 1;
|
|
term.style.setProperty('--neo-scale', String(scale));
|
|
|
|
const headerH = (out.querySelector('.terminal-header')?.offsetHeight || 0);
|
|
const paletteH = (out.querySelector('.color-palette')?.offsetHeight || 0);
|
|
const bodyPad = 20;
|
|
const scaledH = Math.ceil(pre.scrollHeight * scale);
|
|
term.style.height = (headerH + bodyPad + scaledH + paletteH + 8) + 'px';
|
|
});
|
|
}
|
|
|
|
function initAnimatedCounters(){
|
|
const anim = (el) => {
|
|
const text = el.textContent;
|
|
const n = parseFloat(text.replace(/[^\d.]/g,''));
|
|
if (isNaN(n)) return;
|
|
const suffix = text.replace(/[\d.,]/g,'');
|
|
const dur = 1000, steps = 30, stepVal = n/steps;
|
|
let i = 0, cur = 0;
|
|
const t = setInterval(() => {
|
|
i++; cur += stepVal;
|
|
if (i >= steps){ cur=n; clearInterval(t); }
|
|
el.textContent = Math.floor(cur).toLocaleString() + suffix;
|
|
}, dur/steps);
|
|
};
|
|
$$('.visitor-number, .stat-value').forEach(el => {
|
|
const io = new IntersectionObserver(ents => {
|
|
ents.forEach(en => { if (en.isIntersecting){ anim(el); io.unobserve(el); } });
|
|
});
|
|
io.observe(el);
|
|
});
|
|
}
|
|
|
|
|
|
function shufflePlugins() {
|
|
if (isShuffling) return;
|
|
|
|
const mosaic = document.querySelector('.mosaic');
|
|
if (!mosaic) return;
|
|
|
|
const plugins = Array.from(mosaic.querySelectorAll('.plugin'));
|
|
if (plugins.length < 2) return;
|
|
|
|
isShuffling = true;
|
|
|
|
const originalPositions = plugins.map(plugin => {
|
|
const rect = plugin.getBoundingClientRect();
|
|
return {el: plugin, x: rect.left, y: rect.top};
|
|
});
|
|
|
|
for (let i = plugins.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[plugins[i], plugins[j]] = [plugins[j], plugins[i]];
|
|
}
|
|
|
|
originalPositions.forEach(({el, x, y}) => {
|
|
const newRect = el.getBoundingClientRect();
|
|
const deltaX = x - newRect.left;
|
|
const deltaY = y - newRect.top;
|
|
|
|
el.style.transition = 'none';
|
|
el.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
});
|
|
|
|
requestAnimationFrame(() => {
|
|
plugins.forEach(plugin => mosaic.appendChild(plugin));
|
|
|
|
requestAnimationFrame(() => {
|
|
originalPositions.forEach(({el}) => {
|
|
el.style.transition = 'transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
el.style.transform = '';
|
|
});
|
|
|
|
setTimeout(() => {
|
|
originalPositions.forEach(({el}) => {
|
|
el.style.transition = '';
|
|
});
|
|
|
|
if (window.mosaicUtils) {
|
|
window.mosaicUtils.resizeAll();
|
|
}
|
|
|
|
isShuffling = false;
|
|
}, 700);
|
|
});
|
|
});
|
|
}
|
|
|
|
function initEasterEgg() {
|
|
const statusIndicator = document.getElementById('connection-status');
|
|
if (!statusIndicator) return;
|
|
|
|
statusIndicator.addEventListener('click', function (e) {
|
|
if (this.classList.contains('status-online')) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
shufflePlugins();
|
|
}
|
|
});
|
|
|
|
statusIndicator.style.cursor = 'pointer';
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initEasterEgg);
|
|
} else {
|
|
setTimeout(initEasterEgg, 100);
|
|
}
|
|
|
|
window.shufflePlugins = shufflePlugins;
|
|
|
|
function init(){
|
|
const waitForMosaic = () => {
|
|
if (!window.mosaicUtils?.getMosaic()) {
|
|
setTimeout(waitForMosaic, 50);
|
|
return;
|
|
}
|
|
|
|
const waitForSections = (attempt = 0) => {
|
|
const techSection = document.querySelector('.tech-section, .tech-section.plugin, .plugin.tech-section');
|
|
const projectsSection = document.querySelector('.projects-section, .projects-section.plugin, .plugin.projects-section');
|
|
|
|
if (!techSection || !projectsSection) {
|
|
if (attempt < 20) {
|
|
setTimeout(() => waitForSections(attempt + 1), 100);
|
|
return;
|
|
}
|
|
}
|
|
|
|
setTimeout(() => {
|
|
ensureProjectsAlwaysLast();
|
|
|
|
initTechFiltering();
|
|
initCodeToggles();
|
|
initLastFM(); // Hover handled by CSS now, click handled here
|
|
initMeme();
|
|
initVisitors();
|
|
initServices();
|
|
initWebring();
|
|
initNeofetchSwitch();
|
|
initAnimatedCounters();
|
|
initHealthInteractions();
|
|
|
|
setTimeout(() => {
|
|
ensureProjectsAlwaysLast();
|
|
window.mosaicUtils && window.mosaicUtils.resizeAll();
|
|
}, 120);
|
|
|
|
window.mosaicUtils && window.mosaicUtils.resizeAll();
|
|
}, 80);
|
|
};
|
|
|
|
waitForSections();
|
|
};
|
|
|
|
waitForMosaic();
|
|
}
|
|
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
else init();
|
|
|
|
window.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && currentFilter) {
|
|
clearTechFilter();
|
|
}
|
|
});
|
|
|
|
})(); |