- Add comprehensive JAV studios quick reference guide - Update documentation index with JAV reference - Add logo animation components and test files - Update CSS styling for cards, buttons, forms, and theme - Add utility scripts for configuration and import workflows - Update templates and UI components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
104 lines
3.4 KiB
JavaScript
104 lines
3.4 KiB
JavaScript
class LogoAnimator {
|
|
constructor() {
|
|
this.isAnimating = false;
|
|
this.logoElement = null;
|
|
}
|
|
|
|
init(svgElement) {
|
|
this.logoElement = svgElement;
|
|
this.identifyParts();
|
|
}
|
|
|
|
identifyParts() {
|
|
if (!this.logoElement) return;
|
|
const nipples = [];
|
|
const breasts = [];
|
|
|
|
const breastCandidates = [
|
|
this.logoElement.querySelector('#breast-left'),
|
|
this.logoElement.querySelector('#breast-right')
|
|
].filter(Boolean);
|
|
const nippleCandidates = [
|
|
this.logoElement.querySelector('#nipple-left'),
|
|
this.logoElement.querySelector('#nipple-right')
|
|
].filter(Boolean);
|
|
|
|
breasts.push(...breastCandidates);
|
|
nipples.push(...nippleCandidates);
|
|
|
|
if (nipples.length < 2) {
|
|
const circ = Array.from(this.logoElement.querySelectorAll('circle, ellipse'));
|
|
while (nipples.length < 2 && circ.length) nipples.push(circ.shift());
|
|
}
|
|
if (breasts.length < 2) {
|
|
const shapes = Array.from(this.logoElement.querySelectorAll('path, polygon, rect'));
|
|
while (breasts.length < 2 && shapes.length) breasts.push(shapes.shift());
|
|
}
|
|
if (breasts.length === 0) breasts.push(this.logoElement);
|
|
if (breasts.length === 1) breasts.push(this.logoElement);
|
|
|
|
if (breasts[0]) breasts[0].classList.add('breast-left');
|
|
if (breasts[1]) breasts[1].classList.add('breast-right');
|
|
|
|
if (nipples.length === 0) nipples.push(breasts[0], breasts[1]);
|
|
nipples.slice(0, 2).forEach((el, idx) => el && el.classList.add(idx === 0 ? 'nipple-left' : 'nipple-right'));
|
|
}
|
|
|
|
startBounce() {
|
|
if (!this.logoElement || this.isAnimating) return;
|
|
this.logoElement.classList.add('goondex-logo-animated');
|
|
this.isAnimating = true;
|
|
}
|
|
|
|
stopBounce() {
|
|
if (!this.logoElement) return;
|
|
this.logoElement.classList.remove('goondex-logo-animated');
|
|
this.isAnimating = false;
|
|
}
|
|
}
|
|
|
|
async function loadSVG(urls, targetId) {
|
|
const target = document.getElementById(targetId);
|
|
if (!target) return null;
|
|
for (const url of urls) {
|
|
try {
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error('fetch failed');
|
|
const svgText = await res.text();
|
|
target.innerHTML = svgText;
|
|
const svg = target.querySelector('svg');
|
|
return svg;
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
// Fallback to img if all fetches fail
|
|
target.innerHTML = `<img src="${urls[0]}" alt="Goondex Logo" width="100%" height="100%">`;
|
|
return null;
|
|
}
|
|
|
|
(async function initLogoAnim() {
|
|
const logoURLs = [
|
|
"/static/img/logo/GOONDEX_Titty.svg",
|
|
"http://localhost:8788/static/img/logo/GOONDEX_Titty.svg",
|
|
];
|
|
|
|
const staticSvg = await loadSVG(logoURLs, 'static-logo');
|
|
const animatedSvg = await loadSVG(logoURLs, 'animated-logo');
|
|
const loaderSvg = await loadSVG(logoURLs, 'loader-logo');
|
|
|
|
window.goondexLogoAnim = { animator: null, loaderAnimator: null };
|
|
|
|
if (animatedSvg) {
|
|
const animator = new LogoAnimator();
|
|
animator.init(animatedSvg);
|
|
animator.startBounce();
|
|
window.goondexLogoAnim.animator = animator;
|
|
}
|
|
if (loaderSvg) {
|
|
const l = new LogoAnimator();
|
|
l.init(loaderSvg);
|
|
window.goondexLogoAnim.loaderAnimator = l;
|
|
}
|
|
})();
|