1
0

Refactor animeCat.js to improve structure and readability

This commit is contained in:
2025-05-26 03:15:13 +02:00
parent 6a4622b368
commit c46b5c211c

View File

@@ -1,5 +1,6 @@
// animeCat.js
//miau
// miau!
window.AnimeCat = class AnimeCat {
/**
* @param {HTMLElement} container
@@ -12,10 +13,10 @@ window.AnimeCat = class AnimeCat {
eyesClosed: 'eyes_closed.png',
mouthOpen: 'mouth_open.png'
}, options.images);
this.blinkMin = options.blinkMin ?? 5000;
this.blinkMax = options.blinkMax ?? 15000;
this.blinkDuration = options.blinkDuration?? 175;
this.talkInterval = options.talkInterval ?? 300;
this.blinkMin = options.blinkMin ?? 5000;
this.blinkMax = options.blinkMax ?? 15000;
this.blinkDuration = options.blinkDuration ?? 175;
this.talkInterval = options.talkInterval ?? 300;
this._isSpeaking = false;
this._blinkTimeout = null;
@@ -29,48 +30,88 @@ window.AnimeCat = class AnimeCat {
}
_createElements() {
// Flexbox: Katze und Bubble nebeneinander
this.wrapper = document.createElement('div');
this.wrapper.style.position = 'relative';
this.wrapper.style.display = 'inline-block';
Object.assign(this.wrapper.style, {
position: 'relative',
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
minHeight: '60px',
minWidth: '180px', // Passe an falls du willst
zIndex: '1'
});
// cat image
// --- Cat image ---
this.img = document.createElement('img');
this.img.src = this.images.default;
// disable drag & selection
this.img.draggable = false;
this.img.style.userSelect = 'none';
this.img.style.userSelect = 'none';
this.img.style.webkitUserSelect = 'none';
this.img.style.MozUserSelect = 'none';
this.img.style.msUserSelect = 'none';
// some browsers need this to stop the default drag ghost
this.img.style.webkitUserDrag = 'none';
this.img.style.MozUserSelect = 'none';
this.img.style.msUserSelect = 'none';
this.img.style.webkitUserDrag = 'none';
this.img.style.width = '60px';
this.img.style.height = '60px';
this.img.style.zIndex = '2';
this.wrapper.appendChild(this.img);
// speech bubble
// --- Speech bubble ---
this.bubble = document.createElement('div');
Object.assign(this.bubble.style, {
position: 'absolute',
bottom: '100%',
left: '50%',
transform: 'translateX(-50%)',
padding: '8px 12px',
position: 'relative', // jetzt relativ zur wrapper-Flexbox
marginLeft: '14px',
padding: '10px 16px',
background: 'white',
border: '1px solid #ccc',
borderRadius: '4px',
boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
borderRadius: '16px 16px 16px 0',
boxShadow: '0 2px 8px rgba(0,0,0,0.18)',
opacity: '0',
transition: 'opacity 0.3s',
maxWidth: '200px',
wordWrap: 'break-word',
maxWidth: '240px',
wordBreak: 'break-word',
fontFamily: 'sans-serif',
fontSize: '14px',
fontSize: '15px',
color: '#333',
pointerEvents: 'none'
pointerEvents: 'none',
minHeight: '30px',
zIndex: '10',
overflowWrap: 'anywhere',
display: 'flex',
alignItems: 'center'
});
this.wrapper.appendChild(this.bubble);
// --- Bubble "Tail" (kleines Dreieck) ---
this.bubblePointer = document.createElement('div');
Object.assign(this.bubblePointer.style, {
position: 'absolute',
left: '-13px',
bottom: '12px',
width: '0',
height: '0',
borderTop: '8px solid transparent',
borderBottom: '8px solid transparent',
borderRight: '13px solid #fff',
filter: 'drop-shadow(-2px 1px 2px rgba(0,0,0,0.10))',
zIndex: '11'
});
this.bubble.appendChild(this.bubblePointer);
// --- Zusammenbauen ---
this.wrapper.appendChild(this.img);
this.wrapper.appendChild(this.bubble);
this.container.appendChild(this.wrapper);
// Optional: Passe die Bubble-Dynamik an das Fenster an
// window.addEventListener('resize', () => this._adaptBubbleWidth());
// this._adaptBubbleWidth();
}
// Passe Bubble-Breite an verfügbaren Platz an (optional)
_adaptBubbleWidth() {
const rect = this.wrapper.getBoundingClientRect();
const parentRect = this.container.getBoundingClientRect();
const spaceRight = parentRect.right - rect.right - 14;
this.bubble.style.maxWidth = Math.max(140, Math.min(spaceRight, 320)) + 'px';
}
_startBlinking() {
@@ -90,7 +131,6 @@ window.AnimeCat = class AnimeCat {
_bindMouseHold() {
let holdTimer = null;
const closeEyes = () => {
clearTimeout(holdTimer);
this.img.src = this.images.eyesClosed;
@@ -103,13 +143,10 @@ window.AnimeCat = class AnimeCat {
};
this.img.addEventListener('mousedown', () => {
// if currently talking, ignore hold-to-close
if (this._isSpeaking) return;
closeEyes();
// force reopen after max 5s
holdTimer = setTimeout(reopenEyes, 4000);
});
['mouseup', 'mouseleave'].forEach(evt =>
this.img.addEventListener(evt, reopenEyes)
);
@@ -124,7 +161,15 @@ window.AnimeCat = class AnimeCat {
this._mouthOpen = false;
this.img.src = this.images.default;
this.bubble.style.opacity = '1';
this.bubble.textContent = '';
this.bubble.style.visibility = 'visible';
// Bubble-Inhalt zurücksetzen (ohne den Pointer zu löschen!)
Array.from(this.bubble.childNodes).forEach(node => {
if (node !== this.bubblePointer) node.remove();
});
this.bubblePointer.style.display = '';
// Neuen (leeren) Textnode einfügen:
this._bubbleTextNode = document.createTextNode('');
this.bubble.appendChild(this._bubbleTextNode);
this._talkIntervalId = setInterval(() => {
this._mouthOpen = !this._mouthOpen;
@@ -136,7 +181,8 @@ window.AnimeCat = class AnimeCat {
/** Append a chunk of streamed text */
appendSpeech(chunk) {
this.bubble.textContent += chunk;
if (this._bubbleTextNode)
this._bubbleTextNode.textContent += chunk;
}
/** Call when the stream ends */
@@ -145,6 +191,7 @@ window.AnimeCat = class AnimeCat {
this.img.src = this.images.default;
this._speechTimeout = setTimeout(() => {
this.bubble.style.opacity = '0';
this.bubblePointer.style.display = 'none';
this._isSpeaking = false;
}, 3000);
}