分页: 3 / 3

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 18日 10:42
ejsoon
圖標再改,css改,刪最後一值刪動畫。

代码: 全选

改動:

一,etaniAVMove圖標再次更改,其svg的path的d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"。

二,css更改:
.etaniItemLeft去掉background-color和border-right
.etaniItem加上background-color:lightyellow

三,當etaniAVDelete刪掉最後一個值時,則這個動畫將被刪除。如果刪的類型是transform,則它的<use>的兩個父元素也要刪除。

代碼中所有的注釋都要使用英文。

代码: 全选

// Global variable to hold the SVG clone and ensure state is maintained
let etani_clone = null;

// Track copy, move, and delete mode states, selected item, and last click time for double-click detection
let isCopyMode = false;
let isMoveMode = false;
let isDeleteMode = false;
let selectedMoveItem = null;
let lastClickTime = 0;
let lastClickedItem = null;

// Add dynamic CSS styles to the document
function addDynamicStyles() {
    if (document.getElementById('dynamic-et-styles')) {
        return;
    }

    const styleSheet = document.createElement('style');
    styleSheet.id = 'dynamic-et-styles'; 
    styleSheet.textContent = `
        /* Styles for etaniouter and button */
        .etaniouter {
            margin-top: 10px;
            padding: 10px;
            border: 1px solid #ddd;
            background-color: #f5f5f5;
            overflow: auto;
        }

        /* Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
        .etaniinner {
            margin-top: 10px;
        }
        
        .etaniCtrl {
            margin-bottom: 10px; 
            clear: both;
            padding: 5px;
            border: 1px solid #c0c0c0;
            text-align: center;
        }
        
        /* Styles for etaniContent, etaniSetting, etaniAllAppend, etaniValueCtrl */
        .etaniContent, .etaniSetting, .etaniAllAppend, .etaniValueCtrl {
            display: inline-block;
            vertical-align: top;
            padding: 5px;
            border: 1px solid #ccc;
            margin: 0 5px 5px 5px;
            text-align: left;
        }

        .etaniCol {
            border: 1px solid #aaa;
            padding: 5px;
            overflow: auto;
            margin-bottom: 10px; 
            clear: both;
        }

        /* Styles for etaniResult */
        .etaniResult {
            text-align: center;
            margin-bottom: 10px;
            padding: 10px;
            border: 1px solid #bbb;
            overflow: auto;
        }
        
        /* Control and button styles */
        .etaniContent a, .etaniValueCtrl a {
            display: inline-block;
            margin: 0 5px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 14px;
        }
        
        .etaniCenter {
            border: 1px solid green;
            color: green;
        }
        
        .etaniContentHTML {
            border: 1px solid #0099ff;
            color: #0099ff;
            margin-right: 15px;
        }

        .etaniValueCtrlUp {
            border: 1px solid purple;
            color: purple;
        }

        /* Styles for etaniAllAppend buttons */
        .etaniAllAppend button {
            padding: 5px 10px;
            font-size: 16px;
            margin: 0 5px;
            cursor: pointer;
            border: 1px solid #333;
            background-color: #fff;
        }
        
        /* Custom radio button styles */
        .etaniSettingMode {
            display: inline-block;
            cursor: pointer;
            padding: 4px 8px;
            margin: 0 3px;
            font-size: 14px;
            border: 1px solid #888;
            background-color: #eee;
            color: #333;
            user-select: none;
        }

        .etaniSettingMode.active {
            background-color: #008CBA;
            color: white;
            border-color: #008CBA;
        }

        /* Result and animation item styles */
        .etaniResultDR {
            text-align: center;
            margin-bottom: 10px;
        }

        .etaniResultDownload, .etaniResultRename {
            display: inline-block;
            margin-right: 15px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 16px; 
        }
        
        .etaniResultDownload {
            border: 1px solid blue;
            color: blue;
        }

        .etaniResultRename {
            border: 1px solid brown;
            color: brown;
        }

        .etaniResultImage {
            display: block;
            max-width: 480px;
            width: 100%;
            height: auto;
            margin: 0 auto 10px auto;
            border: 1px solid #000;
        }
        
        .etaniResultSize {
            display: inline-block;
            margin-left: 10px;
            font-size: 12px; 
            color: #555;
        }
        
        /* etaniItem structure */
        .etaniItem {
            min-height: 48px;
            border: 1px solid #ccc;
            box-sizing: border-box;
            width: 100%;
            margin-bottom: -1px;
            overflow: auto;
            background-color: lightyellow;
        }
        
        .etaniItemLeft {
            float: left; 
            width: 60px;
            min-height: 48px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 2px 0;
        }
        
        .etaniItemRight {
            margin-left: 60px; 
            padding: 7px;
            min-height: 48px; 
            background-color: #fff;
        }

        .tileid {
            text-align: center;
            font-size: 12px; 
            word-break: break-all;
            padding-top: 2px;
        }
        
        /* Animation controls */
        .etaniAnimate {
            border: 1px solid #999;
            padding: 5px;
            margin-bottom: 5px;
        }

        .etaniAnimateName {
            display: inline-block;
            padding: 2px 5px;
            background-color: #555;
            color: white;
            margin-right: 10px;
            font-size: 12px;
        }

        .etaniAnimateDur {
            display: inline-block;
            margin-right: 10px;
            font-size: 14px;
        }

        .etaniAnimateValue {
            margin-top: 5px;
        }

        .etaniAVCtrl {
            display: inline-block;
            vertical-align: top;
            margin-right: 5px;
            margin-bottom: 3px;
        }
        
        .etaniAVCtrl svg {
            margin-left: -1px;
            margin-top: -1px;
        }

        .etaniAVCtrl span {
            display: inline-block;
            width: 24px;
            height: 24px;
            line-height: 24px;
            text-align: center;
            cursor: pointer;
            box-sizing: border-box;
            vertical-align: top;
            margin-right: 3px;
        }

        .etaniAVAdd {
            background-color: #a7fca7;
            border: 1px solid #71c371;
        }
        
        .etaniAVDelete {
            background-color: #ffcccc;
            border: 1px solid #cc3333;
        }

        .etaniAVDelete.deleting {
            background-color: #cc3333;
            color: white;
        }

        .etaniAVCopy {
            background-color: #ccccff;
            border: 1px solid #6666cc;
        }

        .etaniAVCopy.copying {
            background-color: #6666cc;
            color: white;
        }

        .etaniAVMove {
            background-color: #ffcc99;
            border: 1px solid #cc9966;
        }

        .etaniAVMove.moving {
            background-color: #cc9966;
            color: white;
        }
        
        .etaniAVLabel {
            font-size: 14px;
            margin-right: 5px;
        }

        .etaniAV {
            display: inline-block;
            vertical-align: top;
        }

        .etaniAVItem {
            display: inline-block;
            height: 24px;
            background-color: #ff9933;
            border: 1px dashed #00bfff;
            margin: 0 5px 3px;
            padding: 0 5px;
            box-sizing: border-box;
            cursor: pointer;
            position: relative;
            text-align: center;
            line-height: 24px;
            font-size: 12px;
            color: #333;
        }

        .etaniAVItem.deleting-target, .etaniAVItem.copying-target, .etaniAVItem.moving-target {
            background-color: #ff4d4d;
            border: 2px solid red;
        }

        .etaniAVItem.selected-move {
            background-color: #66ccff;
            border: 2px solid #0066cc;
        }
        
        /* Modal/popup styles */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }
        .modal-content {
            position: fixed;
            width: 98%;
            height: 48%;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            z-index: 1001;
            box-sizing: border-box;
        }
        .modal-content textarea {
            width: 100%;
            height: calc(100% - 50px);
            resize: none;
            border: 1px solid #ccc;
            font-size: 12px;
            box-sizing: border-box;
        }
        .modal-close {
            position: absolute;
            top: 0;
            right: 10px;
            font-size: 48px;
            line-height: 1; 
            cursor: pointer;
            color: #333;
        }
    `;

    document.head.appendChild(styleSheet);
}

// Find the first missing letter in the sequence starting from 'a'
function findFirstMissingLetter(existingLetters) {
    let letter = 'a';
    while (existingLetters.includes(letter)) {
        const code = letter.charCodeAt(0);
        if (code >= 97 && code < 122) { // lowercase a-y
            letter = String.fromCharCode(code + 1);
        } else if (code === 122) { // z -> A
            letter = 'A';
        } else if (code >= 65 && code < 90) { // uppercase A-Y
            letter = String.fromCharCode(code + 1);
        } else if (code === 90) { // Z -> a (loop back)
            letter = 'a';
        }
    }
    return letter;
}

// Extract a specific transform function and its value from a transform string
function extractTransformPart(transformString, type) {
    const regex = new RegExp(`(${type})\\(([^)]+)\\)`, 'i');
    const match = transformString.match(regex);
    if (match) {
        return { func: match[1], value: match[2].trim() };
    }
    return { func: '', value: '' };
}

// Parse transform string to get individual transform values
function parseTransform(transformString) {
    const defaultTransform = { translate: '0,0', scale: '1,1', rotate: '0' };
    if (!transformString) return defaultTransform;

    const transform = {};

    const getMatch = (type) => {
        const part = extractTransformPart(transformString, type).value;
        if (!part) return null;
        return part.split(/[,\s]+/).join(',');
    };

    transform.translate = getMatch('translate') || defaultTransform.translate;
    transform.scale = getMatch('scale') || defaultTransform.scale;
    if (transform.scale.split(',').length === 1) {
        transform.scale += `,${transform.scale}`;
    }
    transform.rotate = getMatch('rotate') || defaultTransform.rotate;

    return transform;
}

// Convert SVG string to a Base64 data URL
function svgToBase64(svgString) {
    const base64 = btoa(unescape(encodeURIComponent(svgString)));
    return `data:image/svg+xml;base64,${base64}`;
}

// Format byte size into human-readable string
function formatBytes(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

// Update the result section with the current state of etani_clone
function updateEtaniResult() {
    if (!etani_clone) return;

    const svgString = new XMLSerializer().serializeToString(etani_clone);
    const sizeInBytes = new Blob([svgString]).size;
    const base64Url = svgToBase64(svgString);
    
    const now = new Date();
    const yyyy = now.getFullYear();
    const mm = String(now.getMonth() + 1).padStart(2, '0');
    const dd = String(now.getDate()).padStart(2, '0');
    const hh = String(now.getHours()).padStart(2, '0');
    const mi = String(now.getMinutes()).padStart(2, '0');
    const ss = String(now.getSeconds()).padStart(2, '0');
    const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;

    const imgElement = document.querySelector('.etaniResultImage');
    const downloadElement = document.querySelector('.etaniResultDownload');
    const renameElement = document.querySelector('.etaniResultRename');
    const sizeElement = document.querySelector('.etaniResultSize');

    if (imgElement && downloadElement && renameElement && sizeElement) {
        imgElement.src = base64Url;
        sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
        downloadElement.href = base64Url;
        downloadElement.download = defaultFilename;
        
        renameElement.onclick = (e) => {
            e.preventDefault();
            let currentDownloadName = downloadElement.download;
            let newFilename = prompt("Enter new filename:", currentDownloadName);
            if (newFilename) {
                if (!newFilename.toLowerCase().endsWith('.svg')) {
                    newFilename += '.svg';
                }
                downloadElement.download = newFilename;
                alert(`Filename changed to: ${newFilename}`);
            }
        };
    }
}

// Handle the 'Center' button click to reset the .etdrop transform
function handleCenterClick(e) {
    e.preventDefault();
    if (etani_clone) {
        const etdrop = etani_clone.querySelector('.etdrop');
        if (etdrop) {
            etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
            updateEtaniResult();
        }
    }
}

// Handle the HTML popup window
function handleContentHTMLClick(e) {
    e.preventDefault();
    if (!etani_clone) return;

    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    
    const content = document.createElement('div');
    content.className = 'modal-content';
    
    const close = document.createElement('span');
    close.className = 'modal-close';
    close.innerHTML = '&times;';
    
    const textarea = document.createElement('textarea');
    textarea.value = new XMLSerializer().serializeToString(etani_clone);

    const closeModal = () => {
        if (document.body.contains(overlay)) document.body.removeChild(overlay);
        if (document.body.contains(content)) document.body.removeChild(content);
    };
    close.onclick = closeModal;
    overlay.onclick = closeModal;

    content.appendChild(close);
    content.appendChild(textarea);
    
    document.body.appendChild(overlay);
    document.body.appendChild(content);
}

// Calculate additive transform or opacity value for a given type
function calculateAdditiveValue(type, originalValue, currentBaseValue, scaleValue = '1,1') {
    const roundToThreeDecimals = (value) => {
        return Math.round(value * 1000) / 1000;
    };
    
    if (type === 'rotate') {
        const ovAngle = parseFloat(currentBaseValue.trim());
        const cvAngle = parseFloat(originalValue.trim());
        return roundToThreeDecimals(cvAngle - ovAngle);
    } else if (type === 'scale') {
        const ovScales = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvScales = originalValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvScales[0] / ovScales[0]);
        const v2 = roundToThreeDecimals(cvScales.length > 1 ? (cvScales[1] / ovScales[1]) : (cvScales[0] / ovScales[0]));
        return v1 === v2 ? `${v1}` : `${v1},${v2}`;
    } else if (type === 'translate') {
        const ovCoords = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvCoords = originalValue.split(',').map(c => parseFloat(c.trim()));
        const scaleFactors = scaleValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvCoords[0] - (ovCoords[0] * scaleFactors[0]));
        const v2 = roundToThreeDecimals(cvCoords[1] - (ovCoords[1] * (scaleFactors.length > 1 ? scaleFactors[1] : scaleFactors[0])));
        return `${v1},${v2}`;
    } else if (type === 'opacity') {
        return originalValue;
    }
    return '';
}

// Update duration based on the number of values
function updateDuration(animate, etaniAnimate, newValuesLength) {
    const newDur = newValuesLength > 1 ? newValuesLength - 1 : 0;
    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    if (newDur > 0) {
        animate.setAttribute('dur', `${newDur}s`);
        if (isRepeat) {
            animate.removeAttribute('fill');
            animate.setAttribute('repeatCount', 'indefinite');
        } else {
            animate.removeAttribute('repeatCount');
            animate.setAttribute('fill', 'freeze');
        }
    } else {
        animate.removeAttribute('dur');
        animate.removeAttribute('fill');
        animate.removeAttribute('repeatCount');
    }

    etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}

// Handle click event for the '+' button for transform or opacity
function handleAVAddClick(e, useElementId, animateType = 'transform') {
    e.preventDefault();
    if (!etani_clone) return;

    const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
    const etaniAV = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
    const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);

    if (animateType === 'transform') {
        const originalUseElement = document.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!originalUseElement) return;

        const originalTransformString = originalUseElement.getAttribute('transform') || '';
        const originalTransforms = parseTransform(originalTransformString);

        const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useElementId}"]`);
        if (!translateWrapper) return;

        const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useElementId}"]`);
        const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
        
        if (!scaleWrapper || !cloneUseElement) return;

        const cloneBaseTransformString = cloneUseElement.getAttribute('transform') || 'translate(0,0) scale(1,1) rotate(0)'; 
        const currentBaseTransforms = parseTransform(cloneBaseTransformString);
        
        const currentBaseTranslate = currentBaseTransforms.translate;
        const currentBaseScale = currentBaseTransforms.scale;
        const currentBaseRotate = currentBaseTransforms.rotate;

        const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
        const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
        const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
        
        const allAnimates = [...animatesRotate, ...animatesScale, ...animatesTranslate];
        
        const scaleValue = calculateAdditiveValue('scale', originalTransforms.scale, currentBaseScale);
        
        allAnimates.forEach(animate => {
            const type = animate.getAttribute('type').toLowerCase();
            let currentValueString = animate.getAttribute('values') || '';
            let newValue = '';
            
            if (type === 'rotate') {
                newValue = calculateAdditiveValue('rotate', originalTransforms.rotate, currentBaseRotate);
            } else if (type === 'scale') {
                newValue = scaleValue;
            } else if (type === 'translate') {
                newValue = calculateAdditiveValue('translate', originalTransforms.translate, currentBaseTranslate, scaleValue);
            }
            
            const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
            animate.setAttribute('values', newValueString);
            updateDuration(animate, etaniAnimate, newValueString.split(';').length);
        });
    } else if (animateType === 'opacity') {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const currentValueString = animateOpacity.getAttribute('values') || '';
        const values = currentValueString ? currentValueString.split(';') : [];
        const newValue = values.length > 0 ? values[values.length - 1] : '1';
        const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
        animateOpacity.setAttribute('values', newValueString);
        updateDuration(animateOpacity, etaniAnimate, newValueString.split(';').length);
    }

    // Add new etaniAVItem with appropriate label
    if (etaniAV && etaniAnimate) {
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));

        const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
        if (animateType === 'transform') {
            const existingLetters = Array.from(existingItems).map(item => item.textContent);
            newAVItem.textContent = findFirstMissingLetter(existingLetters);
        } else if (animateType === 'opacity') {
            const values = animateType === 'opacity' ? 
                etaniAnimate.querySelector('animate[attributeName="opacity"]')?.getAttribute('values')?.split(';') || ['1'] :
                ['1'];
            newAVItem.textContent = values[values.length - 1];
        }

        etaniAV.appendChild(newAVItem);
    }

    updateEtaniResult();
}

// Reset all mode states for a specific animateType
function resetModes(animateType, excludeMode = null) {
    const etaniCol = document.querySelector('.etaniCol');
    const avItems = document.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);

    if (excludeMode !== 'delete') {
        isDeleteMode = false;
        etaniCol.classList.remove(`deleting-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVDelete[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('deleting');
            btn.title = 'Delete Value';
        });
    }

    if (excludeMode !== 'copy') {
        isCopyMode = false;
        etaniCol.classList.remove(`copying-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVCopy[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('copying');
            btn.title = 'Copy Value';
        });
    }

    if (excludeMode !== 'move') {
        isMoveMode = false;
        selectedMoveItem = null;
        etaniCol.classList.remove(`moving-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVMove[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('moving');
            btn.title = 'Move Value';
        });
    }

    avItems.forEach(item => {
        item.classList.remove('deleting-target', 'copying-target', 'moving-target', 'selected-move');
    });
}

// Handle click event for the '-' button to toggle deletion mode
function handleAVDeleteToggle(e, animateType = 'transform') {
    e.preventDefault();
    const deleteButton = e.currentTarget;
    resetModes(animateType, 'delete');
    isDeleteMode = !isDeleteMode;
    deleteButton.classList.toggle('deleting', isDeleteMode);
    
    const etaniItemRight = deleteButton.closest('.etaniItemRight');
    const etaniCol = deleteButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('deleting-target', isDeleteMode);
    });

    etaniCol.classList.toggle(`deleting-mode-${animateType}`, isDeleteMode);

    if (isDeleteMode) {
        deleteButton.title = "Click value item to delete (Click again to cancel)";
    } else {
        deleteButton.title = "Delete Value";
        
        avItems.forEach(item => {
            item.classList.remove('deleting-target');
        });
    }
}

// Handle click event for the 'Copy' button to toggle copy mode
function handleAVCopyToggle(e, animateType) {
    e.preventDefault();
    const copyButton = e.currentTarget;
    resetModes(animateType, 'copy');
    isCopyMode = !isCopyMode;
    copyButton.classList.toggle('copying', isCopyMode);
    
    const etaniItemRight = copyButton.closest('.etaniItemRight');
    const etaniCol = copyButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('copying-target', isCopyMode);
    });

    etaniCol.classList.toggle(`copying-mode-${animateType}`, isCopyMode);

    if (isCopyMode) {
        copyButton.title = "Click value item to copy (Click again to cancel)";
    } else {
        copyButton.title = "Copy Value";
        
        avItems.forEach(item => {
            item.classList.remove('copying-target');
        });
    }
}

// Handle click event for the 'Move' button to toggle move mode
function handleAVMoveToggle(e, animateType) {
    e.preventDefault();
    const moveButton = e.currentTarget;
    resetModes(animateType, 'move');
    isMoveMode = !isMoveMode;
    moveButton.classList.toggle('moving', isMoveMode);
    
    const etaniItemRight = moveButton.closest('.etaniItemRight');
    const etaniCol = moveButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('moving-target', isMoveMode);
    });

    etaniCol.classList.toggle(`moving-mode-${animateType}`, isMoveMode);

    if (isMoveMode) {
        moveButton.title = "Click value item to select, then click another to move (Click again to cancel)";
    } else {
        moveButton.title = "Move Value";
        selectedMoveItem = null;
        
        avItems.forEach(item => {
            item.classList.remove('moving-target');
            item.classList.remove('selected-move');
        });
    }
}

// Handle click event for an .etaniAVItem in deletion, copy, or move mode
function handleAVItemClick(e, animateType = 'transform') {
    const item = e.currentTarget;
    const itemIndex = Array.from(item.parentNode.children).indexOf(item);
    const etaniItemRight = item.closest('.etaniItemRight');
    const etaniCol = item.closest('.etaniCol');
    const useId = etaniItemRight.closest('.etaniItem').querySelector('.tileid').textContent;

    const currentTime = Date.now();
    const isDoubleClick = (item === lastClickedItem && (currentTime - lastClickTime) < 2000);

    lastClickTime = currentTime;
    lastClickedItem = item;

    if (etaniCol.classList.contains(`moving-mode-${animateType}`)) {
        const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (!selectedMoveItem) {
            selectedMoveItem = item;
            item.classList.add('selected-move');
            return;
        } else if (selectedMoveItem === item) {
            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            return;
        } else {
            const targetIndex = itemIndex;
            const sourceIndex = Array.from(item.parentNode.children).indexOf(selectedMoveItem);
            
            const parent = item.parentNode;
            if (sourceIndex < targetIndex) {
                if (item.nextSibling) {
                    parent.insertBefore(selectedMoveItem, item.nextSibling);
                } else {
                    parent.appendChild(selectedMoveItem);
                }
            } else {
                parent.insertBefore(selectedMoveItem, item);
            }

            if (animateType === 'transform') {
                const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
                if (!translateWrapper) return;
                
                const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
                const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
                if (!scaleWrapper || !cloneUseElement) return;

                const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
                const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
                const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
                
                const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
                
                allAnimates.forEach(animate => {
                    const values = animate.getAttribute('values').split(';');
                    const valueToMove = values[sourceIndex];
                    values.splice(sourceIndex, 1);
                    if (sourceIndex < targetIndex) {
                        values.splice(targetIndex, 0, valueToMove);
                    } else {
                        values.splice(targetIndex, 0, valueToMove);
                    }
                    animate.setAttribute('values', values.join(';'));
                });
            } else if (animateType === 'opacity') {
                const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
                if (!cloneUseElement) return;

                const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
                if (!animateOpacity) return;

                const values = animateOpacity.getAttribute('values').split(';');
                const valueToMove = values[sourceIndex];
                values.splice(sourceIndex, 1);
                if (sourceIndex < targetIndex) {
                    values.splice(targetIndex, 0, valueToMove);
                } else {
                    values.splice(targetIndex, 0, valueToMove);
                }
                animateOpacity.setAttribute('values', values.join(';'));
            }

            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            updateEtaniResult();
            return;
        }
    }

    if (etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const etaniAV = item.parentNode;
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.textContent = item.textContent;
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
        etaniAV.insertBefore(newAVItem, item.nextSibling);

        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                if (itemIndex < values.length) {
                    values.splice(itemIndex + 1, 0, values[itemIndex]);
                    animate.setAttribute('values', values.join(';'));
                    updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex + 1, 0, values[itemIndex]);
                animateOpacity.setAttribute('values', values.join(';'));
                updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
            }
        }

        updateEtaniResult();
        return;
    }

    if (etaniCol.classList.contains(`deleting-mode-${animateType}`)) {
        const deleteButton = etaniItemRight.querySelector(`.etaniAVDelete[data-type="${animateType}"]`);
        const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);
        
        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                
                if (itemIndex < values.length) {
                    values.splice(itemIndex, 1);
                    if (values.length === 0) {
                        // Remove the entire transform animation and its wrappers
                        translateWrapper.replaceWith(cloneUseElement);
                        cloneUseElement.setAttribute('transform', 'translate(0,0) scale(1,1) rotate(0)');
                        etaniAnimate.remove();
                    } else {
                        animate.setAttribute('values', values.join(';'));
                        updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                    }
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex, 1);
                if (values.length === 0) {
                    // Remove the opacity animation
                    animateOpacity.remove();
                    etaniAnimate.remove();
                } else {
                    animateOpacity.setAttribute('values', values.join(';'));
                    updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            }
        }

        item.remove();

        const remainingItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (remainingItems.length === 0) {
            deleteButton.click();
        } else {
            remainingItems.forEach(item => {
                item.classList.add('deleting-target'); 
            });
        }

        updateEtaniResult();
        
    } else if (animateType === 'opacity' && !etaniCol.classList.contains(`deleting-mode-${animateType}`) && !etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const values = animateOpacity.getAttribute('values').split(';');
        if (itemIndex >= values.length) return;

        if (isDoubleClick) {
            const newValue = prompt("Enter opacity value (0 to 1):", values[itemIndex]);
            if (newValue !== null && !isNaN(newValue) && newValue >= 0 && newValue <= 1) {
                values[itemIndex] = newValue;
                item.textContent = newValue;
                animateOpacity.setAttribute('values', values.join(';'));
                updateEtaniResult();
            }
        } else {
            values[itemIndex] = values[itemIndex] === '0' ? '1' : '0';
            item.textContent = values[itemIndex];
            animateOpacity.setAttribute('values', values.join(';'));
            updateEtaniResult();
        }
    }
}

// Handle click event for the 'Transform' button
function handleAllAppendTransformClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="transform"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const baseAnimate = (type, initialValue) => {
            const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
            animate.setAttribute('attributeName', 'transform');
            animate.setAttribute('attributeType', 'XML');
            animate.setAttribute('type', type);
            animate.setAttribute('values', initialValue);
            animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
            if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');
            animate.setAttribute('additive', 'sum');
            return animate;
        };
        
        cloneUseElement.appendChild(baseAnimate('rotate', '0'));
        
        const scaleWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        scaleWrapper.setAttribute('data-use-href-scale', `#${useId}`);
        scaleWrapper.appendChild(baseAnimate('scale', '1'));
        
        const translateWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        translateWrapper.setAttribute('data-use-href', `#${useId}`);
        translateWrapper.appendChild(baseAnimate('translate', '0,0'));
        
        cloneUseElement.parentNode.insertBefore(translateWrapper, cloneUseElement);
        cloneUseElement.parentNode.insertBefore(scaleWrapper, cloneUseElement);
        scaleWrapper.appendChild(cloneUseElement);
        translateWrapper.appendChild(scaleWrapper);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'transform');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'transform';
        etaniAnimate.appendChild(nameSpan);

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        etaniAnimate.appendChild(durSpan);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = document.createElement('div');
        avCtrlDiv.className = 'etaniAVCtrl';

        const avAddSpan = document.createElement('span');
        avAddSpan.className = 'etaniAVAdd';
        avAddSpan.title = 'Add Transform Value';
        avAddSpan.setAttribute('data-type', 'transform');
        avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'transform'));
        
        const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        plusSvg.setAttribute('width', '24');
        plusSvg.setAttribute('height', '24');
        plusSvg.setAttribute('viewBox', '0 0 24 24');
        plusSvg.setAttribute('fill', 'none');
        plusSvg.setAttribute('stroke', 'currentColor');
        plusSvg.setAttribute('stroke-width', '1');
        plusSvg.setAttribute('stroke-linecap', 'round');
        plusSvg.setAttribute('stroke-linejoin', 'round');
        plusSvg.innerHTML = '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>';
        avAddSpan.appendChild(plusSvg);

        const avDeleteSpan = document.createElement('span');
        avDeleteSpan.className = 'etaniAVDelete';
        avDeleteSpan.title = 'Delete Value';
        avDeleteSpan.setAttribute('data-type', 'transform');
        avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'transform'));

        const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        minusSvg.setAttribute('width', '24');
        minusSvg.setAttribute('height', '24');
        minusSvg.setAttribute('viewBox', '0 0 24 24');
        minusSvg.setAttribute('fill', 'none');
        minusSvg.setAttribute('stroke', 'currentColor');
        minusSvg.setAttribute('stroke-width', '1');
        minusSvg.setAttribute('stroke-linecap', 'round');
        minusSvg.setAttribute('stroke-linejoin', 'round');
        minusSvg.innerHTML = '<line x1="5" y1="12" x2="19" y2="12"></line>';
        avDeleteSpan.appendChild(minusSvg);

        const avCopySpan = document.createElement('span');
        avCopySpan.className = 'etaniAVCopy';
        avCopySpan.title = 'Copy Value';
        avCopySpan.setAttribute('data-type', 'transform');
        avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'transform'));

        const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        copySvg.setAttribute('width', '24');
        copySvg.setAttribute('height', '24');
        copySvg.setAttribute('viewBox', '0 0 24 24');
        copySvg.setAttribute('fill', 'none');
        copySvg.setAttribute('stroke', 'currentColor');
        copySvg.setAttribute('stroke-width', '1');
        copySvg.setAttribute('stroke-linecap', 'round');
        copySvg.setAttribute('stroke-linejoin', 'round');
        copySvg.innerHTML = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
        avCopySpan.appendChild(copySvg);

        const avMoveSpan = document.createElement('span');
        avMoveSpan.className = 'etaniAVMove';
        avMoveSpan.title = 'Move Value';
        avMoveSpan.setAttribute('data-type', 'transform');
        avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'transform'));

        const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        moveSvg.setAttribute('width', '24');
        moveSvg.setAttribute('height', '24');
        moveSvg.setAttribute('viewBox', '0 0 24 24');
        moveSvg.setAttribute('fill', 'none');
        moveSvg.setAttribute('stroke', 'currentColor');
        moveSvg.setAttribute('stroke-width', '1');
        moveSvg.setAttribute('stroke-linecap', 'round');
        moveSvg.setAttribute('stroke-linejoin', 'round');
        moveSvg.innerHTML = '<path d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"></path>';
        avMoveSpan.appendChild(moveSvg);
        
        avCtrlDiv.appendChild(avAddSpan);
        avCtrlDiv.appendChild(avDeleteSpan);
        avCtrlDiv.appendChild(avCopySpan);
        avCtrlDiv.appendChild(avMoveSpan);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = 'a';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'transform'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Opacity' button
function handleAllAppendOpacityClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="opacity"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
        animateOpacity.setAttribute('attributeName', 'opacity');
        animateOpacity.setAttribute('values', '1');
        animateOpacity.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animateOpacity.setAttribute('repeatCount', 'indefinite');
        cloneUseElement.appendChild(animateOpacity);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'opacity');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'opacity';
        etaniAnimate.appendChild(nameSpan);

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        etaniAnimate.appendChild(durSpan);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = document.createElement('div');
        avCtrlDiv.className = 'etaniAVCtrl';

        const avAddSpan = document.createElement('span');
        avAddSpan.className = 'etaniAVAdd';
        avAddSpan.title = 'Add Opacity Value';
        avAddSpan.setAttribute('data-type', 'opacity');
        avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'opacity'));
        
        const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        plusSvg.setAttribute('width', '24');
        plusSvg.setAttribute('height', '24');
        plusSvg.setAttribute('viewBox', '0 0 24 24');
        plusSvg.setAttribute('fill', 'none');
        plusSvg.setAttribute('stroke', 'currentColor');
        plusSvg.setAttribute('stroke-width', '1');
        plusSvg.setAttribute('stroke-linecap', 'round');
        plusSvg.setAttribute('stroke-linejoin', 'round');
        plusSvg.innerHTML = '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>';
        avAddSpan.appendChild(plusSvg);

        const avDeleteSpan = document.createElement('span');
        avDeleteSpan.className = 'etaniAVDelete';
        avDeleteSpan.title = 'Delete Value';
        avDeleteSpan.setAttribute('data-type', 'opacity');
        avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'opacity'));

        const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        minusSvg.setAttribute('width', '24');
        minusSvg.setAttribute('height', '24');
        minusSvg.setAttribute('viewBox', '0 0 24 24');
        minusSvg.setAttribute('fill', 'none');
        minusSvg.setAttribute('stroke', 'currentColor');
        minusSvg.setAttribute('stroke-width', '1');
        minusSvg.setAttribute('stroke-linecap', 'round');
        minusSvg.setAttribute('stroke-linejoin', 'round');
        minusSvg.innerHTML = '<line x1="5" y1="12" x2="19" y2="12"></line>';
        avDeleteSpan.appendChild(minusSvg);

        const avCopySpan = document.createElement('span');
        avCopySpan.className = 'etaniAVCopy';
        avCopySpan.title = 'Copy Value';
        avCopySpan.setAttribute('data-type', 'opacity');
        avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'opacity'));

        const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        copySvg.setAttribute('width', '24');
        copySvg.setAttribute('height', '24');
        copySvg.setAttribute('viewBox', '0 0 24 24');
        copySvg.setAttribute('fill', 'none');
        copySvg.setAttribute('stroke', 'currentColor');
        copySvg.setAttribute('stroke-width', '1');
        copySvg.setAttribute('stroke-linecap', 'round');
        copySvg.setAttribute('stroke-linejoin', 'round');
        copySvg.innerHTML = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
        avCopySpan.appendChild(copySvg);

        const avMoveSpan = document.createElement('span');
        avMoveSpan.className = 'etaniAVMove';
        avMoveSpan.title = 'Move Value';
        avMoveSpan.setAttribute('data-type', 'opacity');
        avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'opacity'));

        const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        moveSvg.setAttribute('width', '24');
        moveSvg.setAttribute('height', '24');
        moveSvg.setAttribute('viewBox', '0 0 24 24');
        moveSvg.setAttribute('fill', 'none');
        moveSvg.setAttribute('stroke', 'currentColor');
        moveSvg.setAttribute('stroke-width', '1');
        moveSvg.setAttribute('stroke-linecap', 'round');
        moveSvg.setAttribute('stroke-linejoin', 'round');
        moveSvg.innerHTML = '<path d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"></path>';
        avMoveSpan.appendChild(moveSvg);
        
        avCtrlDiv.appendChild(avAddSpan);
        avCtrlDiv.appendChild(avDeleteSpan);
        avCtrlDiv.appendChild(avCopySpan);
        avCtrlDiv.appendChild(avMoveSpan);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = '1';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'opacity'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Fill up values' button
function handleValueCtrlUpClick() {
    if (!etani_clone) return;

    const allAnimates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    let maxValuesLength = 0;

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        maxValuesLength = Math.max(maxValuesLength, values.length);
    });

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        if (values.length < maxValuesLength && values.length > 0) {
            const lastValue = values[values.length - 1];
            while (values.length < maxValuesLength) {
                values.push(lastValue);
            }
            animate.setAttribute('values', values.join(';'));
            const etaniAnimate = document.querySelector(`.etaniAnimate:has([data-type="${animate.getAttribute('type') || 'opacity'}"])`);
            updateDuration(animate, etaniAnimate, values.length);
        }
    });

    const etaniItemRights = document.querySelectorAll('.etaniItemRight');
    etaniItemRights.forEach(itemRight => {
        const useId = itemRight.closest('.etaniItem').querySelector('.tileid').textContent;
        ['transform', 'opacity'].forEach(animateType => {
            const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
            if (!etaniAV) return;

            const currentItems = etaniAV.querySelectorAll('.etaniAVItem');
            const currentValuesLength = currentItems.length;
            if (currentValuesLength >= maxValuesLength) return;

            const lastItem = currentItems[currentItems.length - 1];
            let lastValue = lastItem.textContent;

            for (let i = currentValuesLength; i < maxValuesLength; i++) {
                const newAVItem = document.createElement('span');
                newAVItem.className = 'etaniAVItem';
                newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
                if (animateType === 'transform') {
                    const existingLetters = Array.from(currentItems).map(item => item.textContent);
                    newAVItem.textContent = findFirstMissingLetter(existingLetters);
                    existingLetters.push(newAVItem.textContent);
                } else if (animateType === 'opacity') {
                    newAVItem.textContent = lastValue;
                }
                etaniAV.appendChild(newAVItem);
            }
        });
    });

    updateEtaniResult();
}

// Handle setting mode switch (Repeat/Freeze)
function handleSettingModeChange(mode) {
    if (!etani_clone) return;

    const animates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    const isRepeat = mode === 'repeat';

    animates.forEach(animate => {
        if (animate.hasAttribute('dur')) {
            if (isRepeat) {
                animate.removeAttribute('fill');
                animate.setAttribute('repeatCount', 'indefinite');
            } else {
                animate.removeAttribute('repeatCount');
                animate.setAttribute('fill', 'freeze');
            }
        }
    });

    document.querySelector('.etaniSettingFreeze').classList.toggle('active', !isRepeat);
    document.querySelector('.etaniSettingRepeat').classList.toggle('active', isRepeat);

    updateEtaniResult();
}

// Create the internal animation control structure
function createEtaniInner(etaniouter) {
    const originalSvg = document.getElementById('etmain');
    if (!originalSvg) {
        console.error('SVG with ID "etmain" not found.');
        return;
    }
    etani_clone = originalSvg.cloneNode(true);
    const etwaitElement = etani_clone.querySelector('.etwait');
    if (etwaitElement) {
        etwaitElement.remove();
    }
    etani_clone.id = 'etmainani';

    const etaniinner = document.createElement('div');
    etaniinner.className = 'etaniinner';
    etaniinner.id = 'etaniinner';

    const etaniCtrl = document.createElement('div');
    etaniCtrl.className = 'etaniCtrl';
    
    const etaniContent = document.createElement('div');
    etaniContent.className = 'etaniContent';
    
    const contentHTMLLink = document.createElement('a');
    contentHTMLLink.className = 'etaniContentHTML';
    contentHTMLLink.textContent = 'Show HTML';
    contentHTMLLink.href = 'javascript:;';
    contentHTMLLink.addEventListener('click', handleContentHTMLClick);
    etaniContent.appendChild(contentHTMLLink);

    const centerLink = document.createElement('a');
    centerLink.className = 'etaniCenter';
    centerLink.textContent = 'Center';
    centerLink.href = 'javascript:;';
    centerLink.addEventListener('click', handleCenterClick);
    etaniContent.appendChild(centerLink);
    
    etaniCtrl.appendChild(etaniContent);

    const etaniSetting = document.createElement('div');
    etaniSetting.className = 'etaniSetting';
    
    const repeatRadio = document.createElement('span');
    repeatRadio.className = 'etaniSettingMode etaniSettingRepeat active';
    repeatRadio.textContent = 'Repeat';
    repeatRadio.setAttribute('data-mode', 'repeat');
    repeatRadio.addEventListener('click', () => handleSettingModeChange('repeat'));
    
    const freezeRadio = document.createElement('span');
    freezeRadio.className = 'etaniSettingMode etaniSettingFreeze';
    freezeRadio.textContent = 'Freeze';
    freezeRadio.setAttribute('data-mode', 'freeze');
    freezeRadio.addEventListener('click', () => handleSettingModeChange('freeze'));

    etaniSetting.appendChild(repeatRadio);
    etaniSetting.appendChild(freezeRadio);
    etaniCtrl.appendChild(etaniSetting);

    const etaniAllAppend = document.createElement('div');
    etaniAllAppend.className = 'etaniAllAppend';

    const transformButton = document.createElement('button');
    transformButton.className = 'etaniAllAppendTransform';
    transformButton.textContent = 'transform';
    transformButton.addEventListener('click', handleAllAppendTransformClick);
    
    const opacityButton = document.createElement('button');
    opacityButton.className = 'etaniAllAppendOpacity';
    opacityButton.textContent = 'opacity';
    opacityButton.addEventListener('click', handleAllAppendOpacityClick);
    
    etaniAllAppend.appendChild(transformButton);
    etaniAllAppend.appendChild(opacityButton);
    etaniCtrl.appendChild(etaniAllAppend);

    const etaniValueCtrl = document.createElement('div');
    etaniValueCtrl.className = 'etaniValueCtrl';

    const valueCtrlUpLink = document.createElement('a');
    valueCtrlUpLink.className = 'etaniValueCtrlUp';
    valueCtrlUpLink.textContent = 'fill up values';
    valueCtrlUpLink.href = 'javascript:;';
    valueCtrlUpLink.addEventListener('click', handleValueCtrlUpClick);
    etaniValueCtrl.appendChild(valueCtrlUpLink);
    etaniCtrl.appendChild(etaniValueCtrl);

    const etaniCol = document.createElement('div');
    etaniCol.className = 'etaniCol';

    const etaniResult = document.createElement('div');
    etaniResult.className = 'etaniResult';
    
    const resultImg = document.createElement('img');
    resultImg.className = 'etaniResultImage';
    resultImg.alt = 'Rendered Ejtile Animation SVG';

    const resultDRDiv = document.createElement('div');
    resultDRDiv.className = 'etaniResultDR';

    const downloadLink = document.createElement('a');
    downloadLink.className = 'etaniResultDownload';
    downloadLink.textContent = 'Download SVG';
    downloadLink.href = 'javascript:;';

    const renameLink = document.createElement('a');
    renameLink.className = 'etaniResultRename';
    renameLink.textContent = 'Rename File';
    renameLink.href = 'javascript:;';
    
    const sizeSpan = document.createElement('span');
    sizeSpan.className = 'etaniResultSize';

    resultDRDiv.appendChild(downloadLink);
    resultDRDiv.appendChild(renameLink);
    
    etaniResult.appendChild(resultImg);
    etaniResult.appendChild(resultDRDiv);
    etaniResult.appendChild(sizeSpan);
    
    etaniinner.appendChild(etaniCtrl);
    etaniinner.appendChild(etaniCol);
    etaniinner.appendChild(etaniResult);

    etaniouter.appendChild(etaniinner);

    const etdropUses = document.querySelectorAll('.etdrop use');
    const etwaitGroups = document.querySelectorAll('.etwait g');

    etdropUses.forEach((useElement, i) => {
        const tileid = useElement.getAttribute('href').substring(1);
        let targetUse = null;
        for (const group of etwaitGroups) {
            const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
            if (useInGroup) {
                targetUse = useInGroup;
                break;
            }
        }
        
        let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
        if (targetUse) {
            etwaittransform = targetUse.getAttribute('transform') || '';
            etwaitfill = targetUse.getAttribute('fill') || '';
            etwaitstroke = targetUse.getAttribute('stroke') || '';
            etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';

            const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
            const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
            const scalePart = scaleMatch ? scaleMatch[0] : '';
            const rotatePart = rotateMatch ? rotateMatch[0] : '';
            etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
        }
        
        const originalTile = document.querySelector(`defs g#${tileid}`);
        
        const etaniItem = document.createElement('div');
        etaniItem.className = 'etaniItem';

        const etaniItemLeft = document.createElement('div');
        etaniItemLeft.className = 'etaniItemLeft';

        const etaniItemRight = document.createElement('div');
        etaniItemRight.className = 'etaniItemRight';
        
        if (originalTile) {
            const tileclone = originalTile.cloneNode(true);
            tileclone.removeAttribute('id');
            if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
            if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
            if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
            if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
            
            const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svgWrapper.setAttribute('width', '40');
            svgWrapper.setAttribute('height', '40');
            svgWrapper.setAttribute('version', '1.1');
            svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
            svgWrapper.className = 'etanitileimg';
            
            svgWrapper.appendChild(tileclone);
            etaniItemLeft.appendChild(svgWrapper);
        }

        const tileidDiv = document.createElement('div');
        tileidDiv.className = 'tileid';
        tileidDiv.textContent = tileid;
        etaniItemLeft.appendChild(tileidDiv);

        etaniItem.appendChild(etaniItemLeft);
        etaniItem.appendChild(etaniItemRight);
        etaniCol.appendChild(etaniItem);
    });
    
    updateEtaniResult();
}

// Toggle the visibility and content of the animation control panel
function toggleAnimation(event) {
    const etanibutton = event.currentTarget;
    const etaniouter = etanibutton.parentNode;
    const etaniinner = document.getElementById('etaniinner');

    if (etanibutton.textContent === 'Animate it') {
        etanibutton.textContent = 'Close Ejtile Ani';
        createEtaniInner(etaniouter);
    } else if (etanibutton.textContent === 'Close Ejtile Ani') {
        etanibutton.textContent = 'Animate it';
        if (etaniinner) {
            etaniinner.remove();
        }
        etani_clone = null;
        isCopyMode = false;
        isMoveMode = false;
        isDeleteMode = false;
        selectedMoveItem = null;
        lastClickTime = 0;
        lastClickedItem = null;
    }
}

// Initialize the animation control panel on window load
window.addEventListener('load', () => {
    addDynamicStyles();

    const etmainouter = document.getElementById('etmainouter');
    if (etmainouter) {
        const etaniouter = document.createElement('div');
        etaniouter.className = 'etaniouter';

        const etanibutton = document.createElement('button');
        etanibutton.id = 'etanibutton';
        etanibutton.textContent = 'Animate it';
        etanibutton.addEventListener('click', toggleAnimation);

        etaniouter.appendChild(etanibutton);
        etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
    } else {
        console.error('Div with ID "etmainouter" not found.');
    }
});

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 18日 15:18
ejsoon
當我按下etaniValueCtrlUp時,出現報錯:「Uncaught TypeError: can't access property "querySelector", etaniAnimate is null」,位置在第568行。請解決這個問題。只需要告知改動的地方。

代码: 全选

// Handle click event for the 'Fill up values' button
function handleValueCtrlUpClick() {
    if (!etani_clone) return;

    const allAnimates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    let maxValuesLength = 0;

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        maxValuesLength = Math.max(maxValuesLength, values.length);
    });

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        if (values.length < maxValuesLength && values.length > 0) {
            const lastValue = values[values.length - 1];
            while (values.length < maxValuesLength) {
                values.push(lastValue);
            }
            animate.setAttribute('values', values.join(';'));
            const useId = animate.closest('use')?.getAttribute('href')?.substring(1) || 
                          animate.closest('g[data-use-href]')?.getAttribute('data-use-href')?.substring(1);
            if (useId) {
                const etaniAnimate = document.querySelector(`.etaniItem:has(.tileid:where(:text("${useId}"))) .etaniAnimate[data-type="${animate.getAttribute('attributeName') === 'opacity' ? 'opacity' : 'transform'}"]`);
                if (etaniAnimate) {
                    updateDuration(animate, etaniAnimate, values.length);
                }
            }
        }
    });

    const etaniItemRights = document.querySelectorAll('.etaniItemRight');
    etaniItemRights.forEach(itemRight => {
        const useId = itemRight.closest('.etaniItem').querySelector('.tileid').textContent;
        ['transform', 'opacity'].forEach(animateType => {
            const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
            if (!etaniAV) return;

            const currentItems = etaniAV.querySelectorAll('.etaniAVItem');
            const currentValuesLength = currentItems.length;
            if (currentValuesLength >= maxValuesLength) return;

            const lastItem = currentItems[currentItems.length - 1];
            let lastValue = lastItem.textContent;

            for (let i = currentValuesLength; i < maxValuesLength; i++) {
                const newAVItem = document.createElement('span');
                newAVItem.className = 'etaniAVItem';
                newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
                if (animateType === 'transform') {
                    const existingLetters = Array.from(currentItems).map(item => item.textContent);
                    newAVItem.textContent = findFirstMissingLetter(existingLetters);
                    existingLetters.push(newAVItem.textContent);
                } else if (animateType === 'opacity') {
                    newAVItem.textContent = lastValue;
                }
                etaniAV.appendChild(newAVItem);
            }
        });
    });

    updateEtaniResult();
}

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 18日 15:31
ejsoon
當按下etaniValueCtrlUp時,如果etaniItem的類型是transform,那麼它所填的value的字母應等同於最後一個,而無需遞增。

例如某etaniItem[transform]的values代表字母為「abc」,需要填充到五個,則結果為「abccc」。

只需要告知改動的地方。

代码: 全选

for (let i = currentValuesLength; i < maxValuesLength; i++) {
    const newAVItem = document.createElement('span');
    newAVItem.className = 'etaniAVItem';
    newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
    if (animateType === 'transform') {
        newAVItem.textContent = lastValue; // Use the last letter instead of incrementing
    } else if (animateType === 'opacity') {
        newAVItem.textContent = lastValue;
    }
    etaniAV.appendChild(newAVItem);
}

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 09:39
ejsoon
改了一些css,微調etaniAVMove。

代码: 全选

// Global variable to hold the SVG clone and ensure state is maintained
let etani_clone = null;

// Track copy, move, and delete mode states, selected item, and last click time for double-click detection
let isCopyMode = false;
let isMoveMode = false;
let isDeleteMode = false;
let selectedMoveItem = null;
let lastClickTime = 0;
let lastClickedItem = null;

// Add dynamic CSS styles to the document
function addDynamicStyles() {
    if (document.getElementById('dynamic-et-styles')) {
        return;
    }

    const styleSheet = document.createElement('style');
    styleSheet.id = 'dynamic-et-styles'; 
    styleSheet.textContent = `
        /* Styles for etaniouter and button */
        .etaniouter {
            margin-top: 10px;
            padding: 10px;
            border: 1px solid #ddd;
            background-color: #f5f5f5;
        }

        /* Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
        .etaniinner {
            margin-top: 10px;
        }
        
        .etaniCtrl {
            margin-bottom: 10px; 
            clear: both;
            padding: 5px;
            border: 1px solid #c0c0c0;
            text-align: center;
        }
        
        /* Styles for etaniContent, etaniSetting, etaniAllAppend, etaniValueCtrl */
        .etaniContent, .etaniSetting, .etaniAllAppend, .etaniValueCtrl {
            display: inline-block;
            vertical-align: top;
            padding: 5px;
            border: 1px solid #ccc;
            margin: 0 5px 5px 5px;
            text-align: left;
        }

        .etaniCol {
            border: 1px solid #aaa;
            padding: 5px;
            margin-bottom: 10px; 
            clear: both;
        }

        /* Styles for etaniResult */
        .etaniResult {
            text-align: center;
            margin-bottom: 10px;
            padding: 10px;
            border: 1px solid #bbb;
            box-sizing: border-box;
        }
        
        /* Control and button styles */
        .etaniContent a, .etaniValueCtrl a {
            display: inline-block;
            margin: 0 5px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 14px;
        }
        
        .etaniCenter {
            border: 1px solid green;
            color: green;
        }
        
        .etaniContentHTML {
            border: 1px solid #0099ff;
            color: #0099ff;
            margin-right: 15px;
        }

        .etaniValueCtrlUp {
            border: 1px solid purple;
            color: purple;
        }

        /* Styles for etaniAllAppend buttons */
        .etaniAllAppend button {
            padding: 5px 10px;
            font-size: 16px;
            margin: 0 5px;
            cursor: pointer;
            border: 1px solid #333;
            background-color: #fff;
        }
        
        /* Custom radio button styles */
        .etaniSettingMode {
            display: inline-block;
            cursor: pointer;
            padding: 4px 8px;
            margin: 0 3px;
            font-size: 14px;
            border: 1px solid #888;
            background-color: #eee;
            color: #333;
            user-select: none;
        }

        .etaniSettingMode.active {
            background-color: #008CBA;
            color: white;
            border-color: #008CBA;
        }

        /* Result and animation item styles */
        .etaniResultDR {
            text-align: center;
            margin-bottom: 10px;
        }

        .etaniResultDownload, .etaniResultRename {
            display: inline-block;
            margin-right: 15px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 16px; 
        }
        
        .etaniResultDownload {
            border: 1px solid blue;
            color: blue;
        }

        .etaniResultRename {
            border: 1px solid brown;
            color: brown;
        }

        .etaniResultImage {
            display: block;
            max-width: 480px;
            width: 100%;
            height: auto;
            margin: 0 auto 10px auto;
            border: 1px solid #000;
            box-sizing: border-box;
        }
        
        .etaniResultSize {
            display: inline-block;
            margin-left: 10px;
            font-size: 12px; 
            color: #555;
        }
        
        /* etaniItem structure */
        .etaniItem {
            min-height: 48px;
            border: 1px solid #ccc;
            box-sizing: border-box;
            width: 100%;
            margin-bottom: -1px;
            background-color: lightyellow;
        }
        
        .etaniItemLeft {
            float: left; 
            width: 60px;
            min-height: 48px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 2px 0;
        }
        
        .etaniItemRight {
            margin-left: 60px; 
            padding: 7px;
            min-height: 48px; 
            background-color: #fff;
        }

        .tileid {
            text-align: center;
            font-size: 12px; 
            word-break: break-all;
            padding-top: 2px;
        }
        
        /* Animation controls */
        .etaniAnimate {
            border: 1px solid #999;
            padding: 5px;
            margin-bottom: 5px;
        }

        .etaniAnimateName {
            display: inline-block;
            padding: 2px 5px;
            background-color: #555;
            color: white;
            margin-right: 10px;
            font-size: 12px;
        }

        .etaniAnimateDur {
            display: inline-block;
            margin-right: 10px;
            font-size: 14px;
        }

        .etaniAnimateValue {
            margin-top: 5px;
        }

        .etaniAVCtrl {
            display: inline-block;
            vertical-align: top;
            margin-right: 5px;
            margin-bottom: 3px;
        }
        
        .etaniAVCtrl svg {
            margin-left: -1px;
            margin-top: -1px;
        }

        .etaniAVCtrl span {
            display: inline-block;
            width: 24px;
            height: 24px;
            line-height: 24px;
            text-align: center;
            cursor: pointer;
            box-sizing: border-box;
            vertical-align: top;
            margin-right: 3px;
        }

        .etaniAVAdd {
            background-color: #a7fca7;
            border: 1px solid #71c371;
        }
        
        .etaniAVDelete {
            background-color: #ffcccc;
            border: 1px solid #cc3333;
        }

        .etaniAVDelete.deleting {
            background-color: #cc3333;
            color: white;
        }

        .etaniAVCopy {
            background-color: #ccccff;
            border: 1px solid #6666cc;
        }

        .etaniAVCopy.copying {
            background-color: #6666cc;
            color: white;
        }

        .etaniAVMove {
            background-color: #ffcc99;
            border: 1px solid #cc9966;
        }

        .etaniAVMove.moving {
            background-color: #cc9966;
            color: white;
        }
        
        .etaniAVLabel {
            font-size: 14px;
            margin-right: 5px;
        }

        .etaniAV {
            display: inline-block;
            vertical-align: top;
        }

        .etaniAVItem {
            display: inline-block;
            height: 24px;
            background-color: #ff9933;
            border: 1px dashed #00bfff;
            margin: 0 5px 3px;
            padding: 0 5px;
            box-sizing: border-box;
            cursor: pointer;
            position: relative;
            text-align: center;
            line-height: 24px;
            font-size: 12px;
            color: #333;
        }

        .etaniAVItem.deleting-target, .etaniAVItem.copying-target, .etaniAVItem.moving-target {
            background-color: #ff4d4d;
            border: 2px solid red;
        }

        .etaniAVItem.selected-move {
            background-color: #66ccff;
            border: 2px solid #0066cc;
        }
        
        /* Modal/popup styles */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }
        .modal-content {
            position: fixed;
            width: 98%;
            height: 48%;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            z-index: 1001;
            box-sizing: border-box;
        }
        .modal-content textarea {
            width: 100%;
            height: calc(100% - 50px);
            resize: none;
            border: 1px solid #ccc;
            font-size: 12px;
            box-sizing: border-box;
        }
        .modal-close {
            position: absolute;
            top: 0;
            right: 10px;
            font-size: 48px;
            line-height: 1; 
            cursor: pointer;
            color: #333;
        }
    `;

    document.head.appendChild(styleSheet);
}

// Find the first missing letter in the sequence starting from 'a'
function findFirstMissingLetter(existingLetters) {
    let letter = 'a';
    while (existingLetters.includes(letter)) {
        const code = letter.charCodeAt(0);
        if (code >= 97 && code < 122) { // lowercase a-y
            letter = String.fromCharCode(code + 1);
        } else if (code === 122) { // z -> A
            letter = 'A';
        } else if (code >= 65 && code < 90) { // uppercase A-Y
            letter = String.fromCharCode(code + 1);
        } else if (code === 90) { // Z -> a (loop back)
            letter = 'a';
        }
    }
    return letter;
}

// Extract a specific transform function and its value from a transform string
function extractTransformPart(transformString, type) {
    const regex = new RegExp(`(${type})\\(([^)]+)\\)`, 'i');
    const match = transformString.match(regex);
    if (match) {
        return { func: match[1], value: match[2].trim() };
    }
    return { func: '', value: '' };
}

// Parse transform string to get individual transform values
function parseTransform(transformString) {
    const defaultTransform = { translate: '0,0', scale: '1,1', rotate: '0' };
    if (!transformString) return defaultTransform;

    const transform = {};

    const getMatch = (type) => {
        const part = extractTransformPart(transformString, type).value;
        if (!part) return null;
        return part.split(/[,\s]+/).join(',');
    };

    transform.translate = getMatch('translate') || defaultTransform.translate;
    transform.scale = getMatch('scale') || defaultTransform.scale;
    if (transform.scale.split(',').length === 1) {
        transform.scale += `,${transform.scale}`;
    }
    transform.rotate = getMatch('rotate') || defaultTransform.rotate;

    return transform;
}

// Convert SVG string to a Base64 data URL
function svgToBase64(svgString) {
    const base64 = btoa(unescape(encodeURIComponent(svgString)));
    return `data:image/svg+xml;base64,${base64}`;
}

// Format byte size into human-readable string
function formatBytes(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

// Update the result section with the current state of etani_clone
function updateEtaniResult() {
    if (!etani_clone) return;

    const svgString = new XMLSerializer().serializeToString(etani_clone);
    const sizeInBytes = new Blob([svgString]).size;
    const base64Url = svgToBase64(svgString);
    
    const now = new Date();
    const yyyy = now.getFullYear();
    const mm = String(now.getMonth() + 1).padStart(2, '0');
    const dd = String(now.getDate()).padStart(2, '0');
    const hh = String(now.getHours()).padStart(2, '0');
    const mi = String(now.getMinutes()).padStart(2, '0');
    const ss = String(now.getSeconds()).padStart(2, '0');
    const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;

    const imgElement = document.querySelector('.etaniResultImage');
    const downloadElement = document.querySelector('.etaniResultDownload');
    const renameElement = document.querySelector('.etaniResultRename');
    const sizeElement = document.querySelector('.etaniResultSize');

    if (imgElement && downloadElement && renameElement && sizeElement) {
        imgElement.src = base64Url;
        sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
        downloadElement.href = base64Url;
        downloadElement.download = defaultFilename;
        
        renameElement.onclick = (e) => {
            e.preventDefault();
            let currentDownloadName = downloadElement.download;
            let newFilename = prompt("Enter new filename:", currentDownloadName);
            if (newFilename) {
                if (!newFilename.toLowerCase().endsWith('.svg')) {
                    newFilename += '.svg';
                }
                downloadElement.download = newFilename;
                alert(`Filename changed to: ${newFilename}`);
            }
        };
    }
}

// Handle the 'Center' button click to reset the .etdrop transform
function handleCenterClick(e) {
    e.preventDefault();
    if (etani_clone) {
        const etdrop = etani_clone.querySelector('.etdrop');
        if (etdrop) {
            etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
            updateEtaniResult();
        }
    }
}

// Handle the HTML popup window
function handleContentHTMLClick(e) {
    e.preventDefault();
    if (!etani_clone) return;

    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    
    const content = document.createElement('div');
    content.className = 'modal-content';
    
    const close = document.createElement('span');
    close.className = 'modal-close';
    close.innerHTML = '&times;';
    
    const textarea = document.createElement('textarea');
    textarea.value = new XMLSerializer().serializeToString(etani_clone);

    const closeModal = () => {
        if (document.body.contains(overlay)) document.body.removeChild(overlay);
        if (document.body.contains(content)) document.body.removeChild(content);
    };
    close.onclick = closeModal;
    overlay.onclick = closeModal;

    content.appendChild(close);
    content.appendChild(textarea);
    
    document.body.appendChild(overlay);
    document.body.appendChild(content);
}

// Calculate additive transform or opacity value for a given type
function calculateAdditiveValue(type, originalValue, currentBaseValue, scaleValue = '1,1') {
    const roundToThreeDecimals = (value) => {
        return Math.round(value * 1000) / 1000;
    };
    
    if (type === 'rotate') {
        const ovAngle = parseFloat(currentBaseValue.trim());
        const cvAngle = parseFloat(originalValue.trim());
        return roundToThreeDecimals(cvAngle - ovAngle);
    } else if (type === 'scale') {
        const ovScales = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvScales = originalValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvScales[0] / ovScales[0]);
        const v2 = roundToThreeDecimals(cvScales.length > 1 ? (cvScales[1] / ovScales[1]) : (cvScales[0] / ovScales[0]));
        return v1 === v2 ? `${v1}` : `${v1},${v2}`;
    } else if (type === 'translate') {
        const ovCoords = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvCoords = originalValue.split(',').map(c => parseFloat(c.trim()));
        const scaleFactors = scaleValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvCoords[0] - (ovCoords[0] * scaleFactors[0]));
        const v2 = roundToThreeDecimals(cvCoords[1] - (ovCoords[1] * (scaleFactors.length > 1 ? scaleFactors[1] : scaleFactors[0])));
        return `${v1},${v2}`;
    } else if (type === 'opacity') {
        return originalValue;
    }
    return '';
}

// Update duration based on the number of values
function updateDuration(animate, etaniAnimate, newValuesLength) {
    const newDur = newValuesLength > 1 ? newValuesLength - 1 : 0;
    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    if (newDur > 0) {
        animate.setAttribute('dur', `${newDur}s`);
        if (isRepeat) {
            animate.removeAttribute('fill');
            animate.setAttribute('repeatCount', 'indefinite');
        } else {
            animate.removeAttribute('repeatCount');
            animate.setAttribute('fill', 'freeze');
        }
    } else {
        animate.removeAttribute('dur');
        animate.removeAttribute('fill');
        animate.removeAttribute('repeatCount');
    }

    etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}

// Handle click event for the '+' button for transform or opacity
function handleAVAddClick(e, useElementId, animateType = 'transform') {
    e.preventDefault();
    if (!etani_clone) return;

    const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
    const etaniAV = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
    const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);

    if (animateType === 'transform') {
        const originalUseElement = document.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!originalUseElement) return;

        const originalTransformString = originalUseElement.getAttribute('transform') || '';
        const originalTransforms = parseTransform(originalTransformString);

        const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useElementId}"]`);
        if (!translateWrapper) return;

        const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useElementId}"]`);
        const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
        
        if (!scaleWrapper || !cloneUseElement) return;

        const cloneBaseTransformString = cloneUseElement.getAttribute('transform') || 'translate(0,0) scale(1,1) rotate(0)'; 
        const currentBaseTransforms = parseTransform(cloneBaseTransformString);
        
        const currentBaseTranslate = currentBaseTransforms.translate;
        const currentBaseScale = currentBaseTransforms.scale;
        const currentBaseRotate = currentBaseTransforms.rotate;

        const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
        const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
        const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
        
        const allAnimates = [...animatesRotate, ...animatesScale, ...animatesTranslate];
        
        const scaleValue = calculateAdditiveValue('scale', originalTransforms.scale, currentBaseScale);
        
        allAnimates.forEach(animate => {
            const type = animate.getAttribute('type').toLowerCase();
            let currentValueString = animate.getAttribute('values') || '';
            let newValue = '';
            
            if (type === 'rotate') {
                newValue = calculateAdditiveValue('rotate', originalTransforms.rotate, currentBaseRotate);
            } else if (type === 'scale') {
                newValue = scaleValue;
            } else if (type === 'translate') {
                newValue = calculateAdditiveValue('translate', originalTransforms.translate, currentBaseTranslate, scaleValue);
            }
            
            const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
            animate.setAttribute('values', newValueString);
            updateDuration(animate, etaniAnimate, newValueString.split(';').length);
        });
    } else if (animateType === 'opacity') {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const currentValueString = animateOpacity.getAttribute('values') || '';
        const values = currentValueString ? currentValueString.split(';') : [];
        const newValue = values.length > 0 ? values[values.length - 1] : '1';
        const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
        animateOpacity.setAttribute('values', newValueString);
        updateDuration(animateOpacity, etaniAnimate, newValueString.split(';').length);
    }

    // Add new etaniAVItem with appropriate label
    if (etaniAV && etaniAnimate) {
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));

        const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
        if (animateType === 'transform') {
            const existingLetters = Array.from(existingItems).map(item => item.textContent);
            newAVItem.textContent = findFirstMissingLetter(existingLetters);
        } else if (animateType === 'opacity') {
            const values = animateType === 'opacity' ? 
                etaniAnimate.querySelector('animate[attributeName="opacity"]')?.getAttribute('values')?.split(';') || ['1'] :
                ['1'];
            newAVItem.textContent = values[values.length - 1];
        }

        etaniAV.appendChild(newAVItem);
    }

    updateEtaniResult();
}

// Reset all mode states for a specific animateType
function resetModes(animateType, excludeMode = null) {
    const etaniCol = document.querySelector('.etaniCol');
    const avItems = document.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);

    if (excludeMode !== 'delete') {
        isDeleteMode = false;
        etaniCol.classList.remove(`deleting-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVDelete[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('deleting');
            btn.title = 'Delete Value';
        });
    }

    if (excludeMode !== 'copy') {
        isCopyMode = false;
        etaniCol.classList.remove(`copying-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVCopy[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('copying');
            btn.title = 'Copy Value';
        });
    }

    if (excludeMode !== 'move') {
        isMoveMode = false;
        selectedMoveItem = null;
        etaniCol.classList.remove(`moving-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVMove[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('moving');
            btn.title = 'Move Value';
        });
    }

    avItems.forEach(item => {
        item.classList.remove('deleting-target', 'copying-target', 'moving-target', 'selected-move');
    });
}

// Handle click event for the '-' button to toggle deletion mode
function handleAVDeleteToggle(e, animateType = 'transform') {
    e.preventDefault();
    const deleteButton = e.currentTarget;
    resetModes(animateType, 'delete');
    isDeleteMode = !isDeleteMode;
    deleteButton.classList.toggle('deleting', isDeleteMode);
    
    const etaniItemRight = deleteButton.closest('.etaniItemRight');
    const etaniCol = deleteButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('deleting-target', isDeleteMode);
    });

    etaniCol.classList.toggle(`deleting-mode-${animateType}`, isDeleteMode);

    if (isDeleteMode) {
        deleteButton.title = "Click value item to delete (Click again to cancel)";
    } else {
        deleteButton.title = "Delete Value";
        
        avItems.forEach(item => {
            item.classList.remove('deleting-target');
        });
    }
}

// Handle click event for the 'Copy' button to toggle copy mode
function handleAVCopyToggle(e, animateType) {
    e.preventDefault();
    const copyButton = e.currentTarget;
    resetModes(animateType, 'copy');
    isCopyMode = !isCopyMode;
    copyButton.classList.toggle('copying', isCopyMode);
    
    const etaniItemRight = copyButton.closest('.etaniItemRight');
    const etaniCol = copyButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('copying-target', isCopyMode);
    });

    etaniCol.classList.toggle(`copying-mode-${animateType}`, isCopyMode);

    if (isCopyMode) {
        copyButton.title = "Click value item to copy (Click again to cancel)";
    } else {
        copyButton.title = "Copy Value";
        
        avItems.forEach(item => {
            item.classList.remove('copying-target');
        });
    }
}

// Handle click event for the 'Move' button to toggle move mode
function handleAVMoveToggle(e, animateType) {
    e.preventDefault();
    const moveButton = e.currentTarget;
    resetModes(animateType, 'move');
    isMoveMode = !isMoveMode;
    moveButton.classList.toggle('moving', isMoveMode);
    
    const etaniItemRight = moveButton.closest('.etaniItemRight');
    const etaniCol = moveButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('moving-target', isMoveMode);
    });

    etaniCol.classList.toggle(`moving-mode-${animateType}`, isMoveMode);

    if (isMoveMode) {
        moveButton.title = "Click value item to select, then click another to move (Click again to cancel)";
    } else {
        moveButton.title = "Move Value";
        selectedMoveItem = null;
        
        avItems.forEach(item => {
            item.classList.remove('moving-target');
            item.classList.remove('selected-move');
        });
    }
}

// Handle click event for an .etaniAVItem in deletion, copy, or move mode
function handleAVItemClick(e, animateType = 'transform') {
    const item = e.currentTarget;
    const itemIndex = Array.from(item.parentNode.children).indexOf(item);
    const etaniItemRight = item.closest('.etaniItemRight');
    const etaniCol = item.closest('.etaniCol');
    const useId = etaniItemRight.closest('.etaniItem').querySelector('.tileid').textContent;

    const currentTime = Date.now();
    const isDoubleClick = (item === lastClickedItem && (currentTime - lastClickTime) < 2000);

    lastClickTime = currentTime;
    lastClickedItem = item;

    if (etaniCol.classList.contains(`moving-mode-${animateType}`)) {
        const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (!selectedMoveItem) {
            selectedMoveItem = item;
            item.classList.add('selected-move');
            return;
        } else if (selectedMoveItem === item) {
            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            return;
        } else {
            const targetIndex = itemIndex;
            const sourceIndex = Array.from(item.parentNode.children).indexOf(selectedMoveItem);
            
            const parent = item.parentNode;
            if (sourceIndex < targetIndex) {
                if (item.nextSibling) {
                    parent.insertBefore(selectedMoveItem, item.nextSibling);
                } else {
                    parent.appendChild(selectedMoveItem);
                }
            } else {
                parent.insertBefore(selectedMoveItem, item);
            }

            if (animateType === 'transform') {
                const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
                if (!translateWrapper) return;
                
                const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
                const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
                if (!scaleWrapper || !cloneUseElement) return;

                const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
                const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
                const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
                
                const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
                
                allAnimates.forEach(animate => {
                    const values = animate.getAttribute('values').split(';');
                    const valueToMove = values[sourceIndex];
                    values.splice(sourceIndex, 1);
                    if (sourceIndex < targetIndex) {
                        values.splice(targetIndex, 0, valueToMove);
                    } else {
                        values.splice(targetIndex, 0, valueToMove);
                    }
                    animate.setAttribute('values', values.join(';'));
                });
            } else if (animateType === 'opacity') {
                const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
                if (!cloneUseElement) return;

                const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
                if (!animateOpacity) return;

                const values = animateOpacity.getAttribute('values').split(';');
                const valueToMove = values[sourceIndex];
                values.splice(sourceIndex, 1);
                if (sourceIndex < targetIndex) {
                    values.splice(targetIndex, 0, valueToMove);
                } else {
                    values.splice(targetIndex, 0, valueToMove);
                }
                animateOpacity.setAttribute('values', values.join(';'));
            }

            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            updateEtaniResult();
            return;
        }
    }

    if (etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const etaniAV = item.parentNode;
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.textContent = item.textContent;
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
        etaniAV.insertBefore(newAVItem, item.nextSibling);

        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                if (itemIndex < values.length) {
                    values.splice(itemIndex + 1, 0, values[itemIndex]);
                    animate.setAttribute('values', values.join(';'));
                    updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex + 1, 0, values[itemIndex]);
                animateOpacity.setAttribute('values', values.join(';'));
                updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
            }
        }

        updateEtaniResult();
        return;
    }

    if (etaniCol.classList.contains(`deleting-mode-${animateType}`)) {
        const deleteButton = etaniItemRight.querySelector(`.etaniAVDelete[data-type="${animateType}"]`);
        const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);
        
        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                
                if (itemIndex < values.length) {
                    values.splice(itemIndex, 1);
                    if (values.length === 0) {
                        // Remove the entire transform animation and its wrappers
                        translateWrapper.replaceWith(cloneUseElement);
                        cloneUseElement.setAttribute('transform', 'translate(0,0) scale(1,1) rotate(0)');
                        etaniAnimate.remove();
                    } else {
                        animate.setAttribute('values', values.join(';'));
                        updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                    }
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex, 1);
                if (values.length === 0) {
                    // Remove the opacity animation
                    animateOpacity.remove();
                    etaniAnimate.remove();
                } else {
                    animateOpacity.setAttribute('values', values.join(';'));
                    updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            }
        }

        item.remove();

        const remainingItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (remainingItems.length === 0) {
            deleteButton.click();
        } else {
            remainingItems.forEach(item => {
                item.classList.add('deleting-target'); 
            });
        }

        updateEtaniResult();
        
    } else if (animateType === 'opacity' && !etaniCol.classList.contains(`deleting-mode-${animateType}`) && !etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const values = animateOpacity.getAttribute('values').split(';');
        if (itemIndex >= values.length) return;

        if (isDoubleClick) {
            const newValue = prompt("Enter opacity value (0 to 1):", values[itemIndex]);
            if (newValue !== null && !isNaN(newValue) && newValue >= 0 && newValue <= 1) {
                values[itemIndex] = newValue;
                item.textContent = newValue;
                animateOpacity.setAttribute('values', values.join(';'));
                updateEtaniResult();
            }
        } else {
            values[itemIndex] = values[itemIndex] === '0' ? '1' : '0';
            item.textContent = values[itemIndex];
            animateOpacity.setAttribute('values', values.join(';'));
            updateEtaniResult();
        }
    }
}

// Handle click event for the 'Transform' button
function handleAllAppendTransformClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="transform"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const baseAnimate = (type, initialValue) => {
            const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
            animate.setAttribute('attributeName', 'transform');
            animate.setAttribute('attributeType', 'XML');
            animate.setAttribute('type', type);
            animate.setAttribute('values', initialValue);
            animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
            if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');
            animate.setAttribute('additive', 'sum');
            return animate;
        };
        
        cloneUseElement.appendChild(baseAnimate('rotate', '0'));
        
        const scaleWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        scaleWrapper.setAttribute('data-use-href-scale', `#${useId}`);
        scaleWrapper.appendChild(baseAnimate('scale', '1'));
        
        const translateWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        translateWrapper.setAttribute('data-use-href', `#${useId}`);
        translateWrapper.appendChild(baseAnimate('translate', '0,0'));
        
        cloneUseElement.parentNode.insertBefore(translateWrapper, cloneUseElement);
        cloneUseElement.parentNode.insertBefore(scaleWrapper, cloneUseElement);
        scaleWrapper.appendChild(cloneUseElement);
        translateWrapper.appendChild(scaleWrapper);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'transform');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'transform';
        etaniAnimate.appendChild(nameSpan);

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        etaniAnimate.appendChild(durSpan);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = document.createElement('div');
        avCtrlDiv.className = 'etaniAVCtrl';

        const avAddSpan = document.createElement('span');
        avAddSpan.className = 'etaniAVAdd';
        avAddSpan.title = 'Add Transform Value';
        avAddSpan.setAttribute('data-type', 'transform');
        avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'transform'));
        
        const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        plusSvg.setAttribute('width', '24');
        plusSvg.setAttribute('height', '24');
        plusSvg.setAttribute('viewBox', '0 0 24 24');
        plusSvg.setAttribute('fill', 'none');
        plusSvg.setAttribute('stroke', 'currentColor');
        plusSvg.setAttribute('stroke-width', '1');
        plusSvg.setAttribute('stroke-linecap', 'round');
        plusSvg.setAttribute('stroke-linejoin', 'round');
        plusSvg.innerHTML = '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>';
        avAddSpan.appendChild(plusSvg);

        const avDeleteSpan = document.createElement('span');
        avDeleteSpan.className = 'etaniAVDelete';
        avDeleteSpan.title = 'Delete Value';
        avDeleteSpan.setAttribute('data-type', 'transform');
        avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'transform'));

        const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        minusSvg.setAttribute('width', '24');
        minusSvg.setAttribute('height', '24');
        minusSvg.setAttribute('viewBox', '0 0 24 24');
        minusSvg.setAttribute('fill', 'none');
        minusSvg.setAttribute('stroke', 'currentColor');
        minusSvg.setAttribute('stroke-width', '1');
        minusSvg.setAttribute('stroke-linecap', 'round');
        minusSvg.setAttribute('stroke-linejoin', 'round');
        minusSvg.innerHTML = '<line x1="5" y1="12" x2="19" y2="12"></line>';
        avDeleteSpan.appendChild(minusSvg);

        const avCopySpan = document.createElement('span');
        avCopySpan.className = 'etaniAVCopy';
        avCopySpan.title = 'Copy Value';
        avCopySpan.setAttribute('data-type', 'transform');
        avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'transform'));

        const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        copySvg.setAttribute('width', '24');
        copySvg.setAttribute('height', '24');
        copySvg.setAttribute('viewBox', '0 0 24 24');
        copySvg.setAttribute('fill', 'none');
        copySvg.setAttribute('stroke', 'currentColor');
        copySvg.setAttribute('stroke-width', '1');
        copySvg.setAttribute('stroke-linecap', 'round');
        copySvg.setAttribute('stroke-linejoin', 'round');
        copySvg.innerHTML = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
        avCopySpan.appendChild(copySvg);

        const avMoveSpan = document.createElement('span');
        avMoveSpan.className = 'etaniAVMove';
        avMoveSpan.title = 'Move Value';
        avMoveSpan.setAttribute('data-type', 'transform');
        avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'transform'));

        const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        moveSvg.setAttribute('width', '24');
        moveSvg.setAttribute('height', '24');
        moveSvg.setAttribute('viewBox', '0 0 24 24');
        moveSvg.setAttribute('fill', 'none');
        moveSvg.setAttribute('stroke', 'currentColor');
        moveSvg.setAttribute('stroke-width', '1');
        moveSvg.setAttribute('stroke-linecap', 'round');
        moveSvg.setAttribute('stroke-linejoin', 'round');
        moveSvg.innerHTML = '<path d="M 6,4 L 3,8 L 6,12 M 3,8 L 18,8 M 18,12 L 21,16 L 18,20 M 21,16 L 6,16"></path>';
        avMoveSpan.appendChild(moveSvg);
        
        avCtrlDiv.appendChild(avAddSpan);
        avCtrlDiv.appendChild(avDeleteSpan);
        avCtrlDiv.appendChild(avCopySpan);
        avCtrlDiv.appendChild(avMoveSpan);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = 'a';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'transform'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Opacity' button
function handleAllAppendOpacityClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="opacity"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
        animateOpacity.setAttribute('attributeName', 'opacity');
        animateOpacity.setAttribute('values', '1');
        animateOpacity.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animateOpacity.setAttribute('repeatCount', 'indefinite');
        cloneUseElement.appendChild(animateOpacity);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'opacity');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'opacity';
        etaniAnimate.appendChild(nameSpan);

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        etaniAnimate.appendChild(durSpan);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = document.createElement('div');
        avCtrlDiv.className = 'etaniAVCtrl';

        const avAddSpan = document.createElement('span');
        avAddSpan.className = 'etaniAVAdd';
        avAddSpan.title = 'Add Opacity Value';
        avAddSpan.setAttribute('data-type', 'opacity');
        avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'opacity'));
        
        const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        plusSvg.setAttribute('width', '24');
        plusSvg.setAttribute('height', '24');
        plusSvg.setAttribute('viewBox', '0 0 24 24');
        plusSvg.setAttribute('fill', 'none');
        plusSvg.setAttribute('stroke', 'currentColor');
        plusSvg.setAttribute('stroke-width', '1');
        plusSvg.setAttribute('stroke-linecap', 'round');
        plusSvg.setAttribute('stroke-linejoin', 'round');
        plusSvg.innerHTML = '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>';
        avAddSpan.appendChild(plusSvg);

        const avDeleteSpan = document.createElement('span');
        avDeleteSpan.className = 'etaniAVDelete';
        avDeleteSpan.title = 'Delete Value';
        avDeleteSpan.setAttribute('data-type', 'opacity');
        avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'opacity'));

        const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        minusSvg.setAttribute('width', '24');
        minusSvg.setAttribute('height', '24');
        minusSvg.setAttribute('viewBox', '0 0 24 24');
        minusSvg.setAttribute('fill', 'none');
        minusSvg.setAttribute('stroke', 'currentColor');
        minusSvg.setAttribute('stroke-width', '1');
        minusSvg.setAttribute('stroke-linecap', 'round');
        minusSvg.setAttribute('stroke-linejoin', 'round');
        minusSvg.innerHTML = '<line x1="5" y1="12" x2="19" y2="12"></line>';
        avDeleteSpan.appendChild(minusSvg);

        const avCopySpan = document.createElement('span');
        avCopySpan.className = 'etaniAVCopy';
        avCopySpan.title = 'Copy Value';
        avCopySpan.setAttribute('data-type', 'opacity');
        avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'opacity'));

        const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        copySvg.setAttribute('width', '24');
        copySvg.setAttribute('height', '24');
        copySvg.setAttribute('viewBox', '0 0 24 24');
        copySvg.setAttribute('fill', 'none');
        copySvg.setAttribute('stroke', 'currentColor');
        copySvg.setAttribute('stroke-width', '1');
        copySvg.setAttribute('stroke-linecap', 'round');
        copySvg.setAttribute('stroke-linejoin', 'round');
        copySvg.innerHTML = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
        avCopySpan.appendChild(copySvg);

        const avMoveSpan = document.createElement('span');
        avMoveSpan.className = 'etaniAVMove';
        avMoveSpan.title = 'Move Value';
        avMoveSpan.setAttribute('data-type', 'opacity');
        avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'opacity'));

        const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        moveSvg.setAttribute('width', '24');
        moveSvg.setAttribute('height', '24');
        moveSvg.setAttribute('viewBox', '0 0 24 24');
        moveSvg.setAttribute('fill', 'none');
        moveSvg.setAttribute('stroke', 'currentColor');
        moveSvg.setAttribute('stroke-width', '1');
        moveSvg.setAttribute('stroke-linecap', 'round');
        moveSvg.setAttribute('stroke-linejoin', 'round');
        moveSvg.innerHTML = '<path d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"></path>';
        avMoveSpan.appendChild(moveSvg);
        
        avCtrlDiv.appendChild(avAddSpan);
        avCtrlDiv.appendChild(avDeleteSpan);
        avCtrlDiv.appendChild(avCopySpan);
        avCtrlDiv.appendChild(avMoveSpan);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = '1';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'opacity'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Fill up values' button
function handleValueCtrlUpClick() {
    if (!etani_clone) return;

    const allAnimates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    let maxValuesLength = 0;

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        maxValuesLength = Math.max(maxValuesLength, values.length);
    });

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        if (values.length < maxValuesLength && values.length > 0) {
            const lastValue = values[values.length - 1];
            while (values.length < maxValuesLength) {
                values.push(lastValue);
            }
            animate.setAttribute('values', values.join(';'));
            const useId = animate.closest('use')?.getAttribute('href')?.substring(1) || 
                          animate.closest('g[data-use-href]')?.getAttribute('data-use-href')?.substring(1);
            if (useId) {
                const etaniAnimate = document.querySelector(`.etaniItem:has(.tileid:where(:text("${useId}"))) .etaniAnimate[data-type="${animate.getAttribute('attributeName') === 'opacity' ? 'opacity' : 'transform'}"]`);
                if (etaniAnimate) {
                    updateDuration(animate, etaniAnimate, values.length);
                }
            }
        }
    });

    const etaniItemRights = document.querySelectorAll('.etaniItemRight');
    etaniItemRights.forEach(itemRight => {
        const useId = itemRight.closest('.etaniItem').querySelector('.tileid').textContent;
        ['transform', 'opacity'].forEach(animateType => {
            const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
            if (!etaniAV) return;

            const currentItems = etaniAV.querySelectorAll('.etaniAVItem');
            const currentValuesLength = currentItems.length;
            if (currentValuesLength >= maxValuesLength) return;

            const lastItem = currentItems[currentItems.length - 1];
            let lastValue = lastItem.textContent;

            for (let i = currentValuesLength; i < maxValuesLength; i++) {
                const newAVItem = document.createElement('span');
                newAVItem.className = 'etaniAVItem';
                newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
                if (animateType === 'transform') {
                    newAVItem.textContent = lastValue; // Use the last letter instead of incrementing
                } else if (animateType === 'opacity') {
                    newAVItem.textContent = lastValue;
                }
                etaniAV.appendChild(newAVItem);
            }
        });
    });

    updateEtaniResult();
}

// Handle setting mode switch (Repeat/Freeze)
function handleSettingModeChange(mode) {
    if (!etani_clone) return;

    const animates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    const isRepeat = mode === 'repeat';

    animates.forEach(animate => {
        if (animate.hasAttribute('dur')) {
            if (isRepeat) {
                animate.removeAttribute('fill');
                animate.setAttribute('repeatCount', 'indefinite');
            } else {
                animate.removeAttribute('repeatCount');
                animate.setAttribute('fill', 'freeze');
            }
        }
    });

    document.querySelector('.etaniSettingFreeze').classList.toggle('active', !isRepeat);
    document.querySelector('.etaniSettingRepeat').classList.toggle('active', isRepeat);

    updateEtaniResult();
}

// Create the internal animation control structure
function createEtaniInner(etaniouter) {
    const originalSvg = document.getElementById('etmain');
    if (!originalSvg) {
        console.error('SVG with ID "etmain" not found.');
        return;
    }
    etani_clone = originalSvg.cloneNode(true);
    const etwaitElement = etani_clone.querySelector('.etwait');
    if (etwaitElement) {
        etwaitElement.remove();
    }
    etani_clone.id = 'etmainani';

    const etaniinner = document.createElement('div');
    etaniinner.className = 'etaniinner';
    etaniinner.id = 'etaniinner';

    const etaniCtrl = document.createElement('div');
    etaniCtrl.className = 'etaniCtrl';
    
    const etaniContent = document.createElement('div');
    etaniContent.className = 'etaniContent';
    
    const contentHTMLLink = document.createElement('a');
    contentHTMLLink.className = 'etaniContentHTML';
    contentHTMLLink.textContent = 'Show HTML';
    contentHTMLLink.href = 'javascript:;';
    contentHTMLLink.addEventListener('click', handleContentHTMLClick);
    etaniContent.appendChild(contentHTMLLink);

    const centerLink = document.createElement('a');
    centerLink.className = 'etaniCenter';
    centerLink.textContent = 'Center';
    centerLink.href = 'javascript:;';
    centerLink.addEventListener('click', handleCenterClick);
    etaniContent.appendChild(centerLink);
    
    etaniCtrl.appendChild(etaniContent);

    const etaniSetting = document.createElement('div');
    etaniSetting.className = 'etaniSetting';
    
    const repeatRadio = document.createElement('span');
    repeatRadio.className = 'etaniSettingMode etaniSettingRepeat active';
    repeatRadio.textContent = 'Repeat';
    repeatRadio.setAttribute('data-mode', 'repeat');
    repeatRadio.addEventListener('click', () => handleSettingModeChange('repeat'));
    
    const freezeRadio = document.createElement('span');
    freezeRadio.className = 'etaniSettingMode etaniSettingFreeze';
    freezeRadio.textContent = 'Freeze';
    freezeRadio.setAttribute('data-mode', 'freeze');
    freezeRadio.addEventListener('click', () => handleSettingModeChange('freeze'));

    etaniSetting.appendChild(repeatRadio);
    etaniSetting.appendChild(freezeRadio);
    etaniCtrl.appendChild(etaniSetting);

    const etaniAllAppend = document.createElement('div');
    etaniAllAppend.className = 'etaniAllAppend';

    const transformButton = document.createElement('button');
    transformButton.className = 'etaniAllAppendTransform';
    transformButton.textContent = 'transform';
    transformButton.addEventListener('click', handleAllAppendTransformClick);
    
    const opacityButton = document.createElement('button');
    opacityButton.className = 'etaniAllAppendOpacity';
    opacityButton.textContent = 'opacity';
    opacityButton.addEventListener('click', handleAllAppendOpacityClick);
    
    etaniAllAppend.appendChild(transformButton);
    etaniAllAppend.appendChild(opacityButton);
    etaniCtrl.appendChild(etaniAllAppend);

    const etaniValueCtrl = document.createElement('div');
    etaniValueCtrl.className = 'etaniValueCtrl';

    const valueCtrlUpLink = document.createElement('a');
    valueCtrlUpLink.className = 'etaniValueCtrlUp';
    valueCtrlUpLink.textContent = 'fill up values';
    valueCtrlUpLink.href = 'javascript:;';
    valueCtrlUpLink.addEventListener('click', handleValueCtrlUpClick);
    etaniValueCtrl.appendChild(valueCtrlUpLink);
    etaniCtrl.appendChild(etaniValueCtrl);

    const etaniCol = document.createElement('div');
    etaniCol.className = 'etaniCol';

    const etaniResult = document.createElement('div');
    etaniResult.className = 'etaniResult';
    
    const resultImg = document.createElement('img');
    resultImg.className = 'etaniResultImage';
    resultImg.alt = 'Rendered Ejtile Animation SVG';

    const resultDRDiv = document.createElement('div');
    resultDRDiv.className = 'etaniResultDR';

    const downloadLink = document.createElement('a');
    downloadLink.className = 'etaniResultDownload';
    downloadLink.textContent = 'Download SVG';
    downloadLink.href = 'javascript:;';

    const renameLink = document.createElement('a');
    renameLink.className = 'etaniResultRename';
    renameLink.textContent = 'Rename File';
    renameLink.href = 'javascript:;';
    
    const sizeSpan = document.createElement('span');
    sizeSpan.className = 'etaniResultSize';

    resultDRDiv.appendChild(downloadLink);
    resultDRDiv.appendChild(renameLink);
    
    etaniResult.appendChild(resultImg);
    etaniResult.appendChild(resultDRDiv);
    etaniResult.appendChild(sizeSpan);
    
    etaniinner.appendChild(etaniCtrl);
    etaniinner.appendChild(etaniCol);
    etaniinner.appendChild(etaniResult);

    etaniouter.appendChild(etaniinner);

    const etdropUses = document.querySelectorAll('.etdrop use');
    const etwaitGroups = document.querySelectorAll('.etwait g');

    etdropUses.forEach((useElement, i) => {
        const tileid = useElement.getAttribute('href').substring(1);
        let targetUse = null;
        for (const group of etwaitGroups) {
            const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
            if (useInGroup) {
                targetUse = useInGroup;
                break;
            }
        }
        
        let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
        if (targetUse) {
            etwaittransform = targetUse.getAttribute('transform') || '';
            etwaitfill = targetUse.getAttribute('fill') || '';
            etwaitstroke = targetUse.getAttribute('stroke') || '';
            etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';

            const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
            const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
            const scalePart = scaleMatch ? scaleMatch[0] : '';
            const rotatePart = rotateMatch ? rotateMatch[0] : '';
            etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
        }
        
        const originalTile = document.querySelector(`defs g#${tileid}`);
        
        const etaniItem = document.createElement('div');
        etaniItem.className = 'etaniItem';

        const etaniItemLeft = document.createElement('div');
        etaniItemLeft.className = 'etaniItemLeft';

        const etaniItemRight = document.createElement('div');
        etaniItemRight.className = 'etaniItemRight';
        
        if (originalTile) {
            const tileclone = originalTile.cloneNode(true);
            tileclone.removeAttribute('id');
            if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
            if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
            if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
            if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
            
            const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svgWrapper.setAttribute('width', '40');
            svgWrapper.setAttribute('height', '40');
            svgWrapper.setAttribute('version', '1.1');
            svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
            svgWrapper.className = 'etanitileimg';
            
            svgWrapper.appendChild(tileclone);
            etaniItemLeft.appendChild(svgWrapper);
        }

        const tileidDiv = document.createElement('div');
        tileidDiv.className = 'tileid';
        tileidDiv.textContent = tileid;
        etaniItemLeft.appendChild(tileidDiv);

        etaniItem.appendChild(etaniItemLeft);
        etaniItem.appendChild(etaniItemRight);
        etaniCol.appendChild(etaniItem);
    });
    
    updateEtaniResult();
}

// Toggle the visibility and content of the animation control panel
function toggleAnimation(event) {
    const etanibutton = event.currentTarget;
    const etaniouter = etanibutton.parentNode;
    const etaniinner = document.getElementById('etaniinner');

    if (etanibutton.textContent === 'Animate it') {
        etanibutton.textContent = 'Close Ejtile Ani';
        createEtaniInner(etaniouter);
    } else if (etanibutton.textContent === 'Close Ejtile Ani') {
        etanibutton.textContent = 'Animate it';
        if (etaniinner) {
            etaniinner.remove();
        }
        etani_clone = null;
        isCopyMode = false;
        isMoveMode = false;
        isDeleteMode = false;
        selectedMoveItem = null;
        lastClickTime = 0;
        lastClickedItem = null;
    }
}

// Initialize the animation control panel on window load
window.addEventListener('load', () => {
    addDynamicStyles();

    const etmainouter = document.getElementById('etmainouter');
    if (etmainouter) {
        const etaniouter = document.createElement('div');
        etaniouter.className = 'etaniouter';

        const etanibutton = document.createElement('button');
        etanibutton.id = 'etanibutton';
        etanibutton.textContent = 'Animate it';
        etanibutton.addEventListener('click', toggleAnimation);

        etaniouter.appendChild(etanibutton);
        etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
    } else {
        console.error('Div with ID "etmainouter" not found.');
    }
});

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 10:09
ejsoon
為etaniAttr加父元素。

代码: 全选

將.etaniAnimateName和.etaniAnimateDur放進一個父元素div.etaniAnimateAttr中。

之後所有的div.etaniAnimateAttr內的span,除了span.etaniAnimateName之外,它們的css都為「display:inline-block; padding: 2px 5px; font-size: 12px; margin-right: 7px; box-sizing: border-box;border-width: 1px; border-style: solid;」,它們的border-color和color需要單獨指定,span.etaniAnimateDur是blue。

代碼中所有的注釋都要使用英文。

僅需告知改動之處,不用提供完整代碼。

代码: 全选

// Modifications to wrap .etaniAnimateName and .etaniAnimateDur in a new .etaniAnimateAttr div
// In handleAllAppendTransformClick and handleAllAppendOpacityClick functions:
// 1. Replace the direct appending of nameSpan and durSpan to etaniAnimate
// 2. Create a new div.etaniAnimateAttr to wrap nameSpan and durSpan
// 3. Append the new div.etaniAnimateAttr to etaniAnimate

// In handleAllAppendTransformClick function:
const etaniAnimateAttr = document.createElement('div');
etaniAnimateAttr.className = 'etaniAnimateAttr';
etaniAnimateAttr.appendChild(nameSpan);
etaniAnimateAttr.appendChild(durSpan);
etaniAnimate.appendChild(etaniAnimateAttr);

// In handleAllAppendOpacityClick function:
const etaniAnimateAttr = document.createElement('div');
etaniAnimateAttr.className = 'etaniAnimateAttr';
etaniAnimateAttr.appendChild(nameSpan);
etaniAnimateAttr.appendChild(durSpan);
etaniAnimate.appendChild(etaniAnimateAttr);

// Update CSS in addDynamicStyles function to include styles for .etaniAnimateAttr and its child spans
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles';
styleSheet.textContent += `
    .etaniAnimateAttr {
        margin-bottom: 5px;
    }
    .etaniAnimateAttr > span:not(.etaniAnimateName) {
        display: inline-block;
        padding: 2px 5px;
        font-size: 12px;
        margin-right: 7px;
        box-sizing: border-box;
        border-width: 1px;
        border-style: solid;
    }
    .etaniAnimateDur {
        border-color: blue;
        color: blue;
    }
`;

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 10:17
ejsoon
改動:

為dur增加點擊事件,當點擊它時,將彈出輸入框對其更改。

當點擊etaniAVCtrl內的幾個功能按鈕使values的數量變化時,如果dur不是整數,則它將不再更改。

代碼中所有的注釋都要使用英文。

僅需告知改動之處,不用提供完整代碼。

代码: 全选

// Add click event handler for .etaniAnimateDur in handleAllAppendTransformClick and handleAllAppendOpacityClick
// In handleAllAppendTransformClick function, modify durSpan creation:
durSpan.addEventListener('click', () => {
    const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
    const newDur = prompt('Enter duration in seconds:', currentDur);
    if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
        const animates = etani_clone.querySelectorAll(`.etdrop > g[data-use-href="#${useId}"] animateTransform, .etdrop > g[data-use-href="#${useId}"] > g[data-use-href-scale="#${useId}"] animateTransform, .etdrop use[href="#${useId}"] animateTransform`);
        animates.forEach(animate => {
            if (newDur > 0) {
                animate.setAttribute('dur', `${newDur}s`);
                if (isRepeat) {
                    animate.removeAttribute('fill');
                    animate.setAttribute('repeatCount', 'indefinite');
                } else {
                    animate.removeAttribute('repeatCount');
                    animate.setAttribute('fill', 'freeze');
                }
            } else {
                animate.removeAttribute('dur');
                animate.removeAttribute('fill');
                animate.removeAttribute('repeatCount');
            }
        });
        durSpan.textContent = `dur: ${newDur}s`;
        updateEtaniResult();
    }
});

// In handleAllAppendOpacityClick function, modify durSpan creation:
durSpan.addEventListener('click', () => {
    const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
    const newDur = prompt('Enter duration in seconds:', currentDur);
    if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
        const animateOpacity = etani_clone.querySelector(`.etdrop use[href="#${useId}"] animate[attributeName="opacity"]`);
        if (animateOpacity) {
            if (newDur > 0) {
                animateOpacity.setAttribute('dur', `${newDur}s`);
                if (isRepeat) {
                    animateOpacity.removeAttribute('fill');
                    animateOpacity.setAttribute('repeatCount', 'indefinite');
                } else {
                    animateOpacity.removeAttribute('repeatCount');
                    animateOpacity.setAttribute('fill', 'freeze');
                }
            } else {
                animateOpacity.removeAttribute('dur');
                animateOpacity.removeAttribute('fill');
                animateOpacity.removeAttribute('repeatCount');
            }
        }
        durSpan.textContent = `dur: ${newDur}s`;
        updateEtaniResult();
    }
});

// Modify updateDuration function to check if duration is an integer
function updateDuration(animate, etaniAnimate, newValuesLength) {
    const currentDur = parseFloat(etaniAnimate.querySelector('.etaniAnimateDur').textContent.replace('dur: ', '').replace('s', ''));
    const isIntegerDur = Number.isInteger(currentDur);
    const newDur = isIntegerDur ? (newValuesLength > 1 ? newValuesLength - 1 : 0) : currentDur;
    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    if (newDur > 0) {
        animate.setAttribute('dur', `${newDur}s`);
        if (isRepeat) {
            animate.removeAttribute('fill');
            animate.setAttribute('repeatCount', 'indefinite');
        } else {
            animate.removeAttribute('repeatCount');
            animate.setAttribute('fill', 'freeze');
        }
    } else {
        animate.removeAttribute('dur');
        animate.removeAttribute('fill');
        animate.removeAttribute('repeatCount');
    }

    etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 10:28
ejsoon
改動:

當點擊etaniAVDelete、etaniAVCopy、etaniAVMove而使其處於刪除、複製、移動狀態時,這時點擊etaniAVAdd,將先消除這個狀態,之後再增加value。

代碼中所有的注釋都要使用英文。

只需要告知代碼改動之處。

代码: 全选

// Modify handleAVAddClick function to reset modes before adding a new value
function handleAVAddClick(e, useElementId, animateType = 'transform') {
    e.preventDefault();
    if (!etani_clone) return;

    // Reset all modes before adding a new value
    resetModes(animateType);

    const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
    const etaniAV = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
    const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);

    // ... (rest of the existing handleAVAddClick function remains unchanged)
}

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 10:34
ejsoon
改動:

所有的「innerHTML=」都改成createElement,appendChild方式插入。

只需告知代碼改動之處。

代码: 全选

// In handleContentHTMLClick function, modify close button creation:
const close = document.createElement('span');
close.className = 'modal-close';
const closeText = document.createTextNode('×');
close.appendChild(closeText);

// In handleAllAppendTransformClick function, modify SVG creation for plusSvg:
const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
plusSvg.setAttribute('width', '24');
plusSvg.setAttribute('height', '24');
plusSvg.setAttribute('viewBox', '0 0 24 24');
plusSvg.setAttribute('fill', 'none');
plusSvg.setAttribute('stroke', 'currentColor');
plusSvg.setAttribute('stroke-width', '1');
plusSvg.setAttribute('stroke-linecap', 'round');
plusSvg.setAttribute('stroke-linejoin', 'round');
const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line1.setAttribute('x1', '12');
line1.setAttribute('y1', '5');
line1.setAttribute('x2', '12');
line1.setAttribute('y2', '19');
const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line2.setAttribute('x1', '5');
line2.setAttribute('y1', '12');
line2.setAttribute('x2', '19');
line2.setAttribute('y2', '12');
plusSvg.appendChild(line1);
plusSvg.appendChild(line2);

// In handleAllAppendTransformClick function, modify SVG creation for minusSvg:
const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
minusSvg.setAttribute('width', '24');
minusSvg.setAttribute('height', '24');
minusSvg.setAttribute('viewBox', '0 0 24 24');
minusSvg.setAttribute('fill', 'none');
minusSvg.setAttribute('stroke', 'currentColor');
minusSvg.setAttribute('stroke-width', '1');
minusSvg.setAttribute('stroke-linecap', 'round');
minusSvg.setAttribute('stroke-linejoin', 'round');
const minusLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
minusLine.setAttribute('x1', '5');
minusLine.setAttribute('y1', '12');
minusLine.setAttribute('x2', '19');
minusLine.setAttribute('y2', '12');
minusSvg.appendChild(minusLine);

// In handleAllAppendTransformClick function, modify SVG creation for copySvg:
const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
copySvg.setAttribute('width', '24');
copySvg.setAttribute('height', '24');
copySvg.setAttribute('viewBox', '0 0 24 24');
copySvg.setAttribute('fill', 'none');
copySvg.setAttribute('stroke', 'currentColor');
copySvg.setAttribute('stroke-width', '1');
copySvg.setAttribute('stroke-linecap', 'round');
copySvg.setAttribute('stroke-linejoin', 'round');
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', '9');
rect.setAttribute('y', '9');
rect.setAttribute('width', '13');
rect.setAttribute('height', '13');
rect.setAttribute('rx', '2');
rect.setAttribute('ry', '2');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
copySvg.appendChild(rect);
copySvg.appendChild(path);

// In handleAllAppendTransformClick function, modify SVG creation for moveSvg:
const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
moveSvg.setAttribute('width', '24');
moveSvg.setAttribute('height', '24');
moveSvg.setAttribute('viewBox', '0 0 24 24');
moveSvg.setAttribute('fill', 'none');
moveSvg.setAttribute('stroke', 'currentColor');
moveSvg.setAttribute('stroke-width', '1');
moveSvg.setAttribute('stroke-linecap', 'round');
moveSvg.setAttribute('stroke-linejoin', 'round');
const movePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
movePath.setAttribute('d', 'M 6,4 L 3,8 L 6,12 M 3,8 L 18,8 M 18,12 L 21,16 L 18,20 M 21,16 L 6,16');
moveSvg.appendChild(movePath);

// In handleAllAppendOpacityClick function, modify SVG creation for plusSvg:
const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
plusSvg.setAttribute('width', '24');
plusSvg.setAttribute('height', '24');
plusSvg.setAttribute('viewBox', '0 0 24 24');
plusSvg.setAttribute('fill', 'none');
plusSvg.setAttribute('stroke', 'currentColor');
plusSvg.setAttribute('stroke-width', '1');
plusSvg.setAttribute('stroke-linecap', 'round');
plusSvg.setAttribute('stroke-linejoin', 'round');
const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line1.setAttribute('x1', '12');
line1.setAttribute('y1', '5');
line1.setAttribute('x2', '12');
line1.setAttribute('y2', '19');
const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line2.setAttribute('x1', '5');
line2.setAttribute('y1', '12');
line2.setAttribute('x2', '19');
line2.setAttribute('y2', '12');
plusSvg.appendChild(line1);
plusSvg.appendChild(line2);

// In handleAllAppendOpacityClick function, modify SVG creation for minusSvg:
const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
minusSvg.setAttribute('width', '24');
minusSvg.setAttribute('height', '24');
minusSvg.setAttribute('viewBox', '0 0 24 24');
minusSvg.setAttribute('fill', 'none');
minusSvg.setAttribute('stroke', 'currentColor');
minusSvg.setAttribute('stroke-width', '1');
minusSvg.setAttribute('stroke-linecap', 'round');
minusSvg.setAttribute('stroke-linejoin', 'round');
const minusLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
minusLine.setAttribute('x1', '5');
minusLine.setAttribute('y1', '12');
minusLine.setAttribute('x2', '19');
minusLine.setAttribute('y2', '12');
minusSvg.appendChild(minusLine);

// In handleAllAppendOpacityClick function, modify SVG creation for copySvg:
const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
copySvg.setAttribute('width', '24');
copySvg.setAttribute('height', '24');
copySvg.setAttribute('viewBox', '0 0 24 24');
copySvg.setAttribute('fill', 'none');
copySvg.setAttribute('stroke', 'currentColor');
copySvg.setAttribute('stroke-width', '1');
copySvg.setAttribute('stroke-linecap', 'round');
copySvg.setAttribute('stroke-linejoin', 'round');
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', '9');
rect.setAttribute('y', '9');
rect.setAttribute('width', '13');
rect.setAttribute('height', '13');
rect.setAttribute('rx', '2');
rect.setAttribute('ry', '2');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
copySvg.appendChild(rect);
copySvg.appendChild(path);

// In handleAllAppendOpacityClick function, modify SVG creation for moveSvg:
const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
moveSvg.setAttribute('width', '24');
moveSvg.setAttribute('height', '24');
moveSvg.setAttribute('viewBox', '0 0 24 24');
moveSvg.setAttribute('fill', 'none');
moveSvg.setAttribute('stroke', 'currentColor');
moveSvg.setAttribute('stroke-width', '1');
moveSvg.setAttribute('stroke-linecap', 'round');
moveSvg.setAttribute('stroke-linejoin', 'round');
const movePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
movePath.setAttribute('d', 'M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16');
moveSvg.appendChild(movePath);

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 10:58
ejsoon
當前結果:

代码: 全选

// Global variable to hold the SVG clone and ensure state is maintained
let etani_clone = null;

// Track copy, move, and delete mode states, selected item, and last click time for double-click detection
let isCopyMode = false;
let isMoveMode = false;
let isDeleteMode = false;
let selectedMoveItem = null;
let lastClickTime = 0;
let lastClickedItem = null;

// Add dynamic CSS styles to the document
function addDynamicStyles() {
    if (document.getElementById('dynamic-et-styles')) {
        return;
    }

    const styleSheet = document.createElement('style');
    styleSheet.id = 'dynamic-et-styles'; 
    styleSheet.textContent = `
        /* Styles for etaniouter and button */
        .etaniouter {
            margin-top: 10px;
            padding: 10px;
            border: 1px solid #ddd;
            background-color: #f5f5f5;
        }

        /* Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
        .etaniinner {
            margin-top: 10px;
        }
        
        .etaniCtrl {
            margin-bottom: 10px; 
            clear: both;
            padding: 5px;
            border: 1px solid #c0c0c0;
            text-align: center;
        }
        
        /* Styles for etaniContent, etaniSetting, etaniAllAppend, etaniValueCtrl */
        .etaniContent, .etaniSetting, .etaniAllAppend, .etaniValueCtrl {
            display: inline-block;
            vertical-align: top;
            padding: 5px;
            border: 1px solid #ccc;
            margin: 0 5px 5px 5px;
            text-align: left;
        }

        .etaniCol {
            border: 1px solid #aaa;
            padding: 5px;
            margin-bottom: 10px; 
            clear: both;
        }

        /* Styles for etaniResult */
        .etaniResult {
            text-align: center;
            margin-bottom: 10px;
            padding: 10px;
            border: 1px solid #bbb;
            box-sizing: border-box;
        }
        
        /* Control and button styles */
        .etaniContent a, .etaniValueCtrl a {
            display: inline-block;
            margin: 0 5px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 14px;
        }
        
        .etaniCenter {
            border: 1px solid green;
            color: green;
        }
        
        .etaniContentHTML {
            border: 1px solid #0099ff;
            color: #0099ff;
            margin-right: 15px;
        }

        .etaniValueCtrlUp {
            border: 1px solid purple;
            color: purple;
        }

        /* Styles for etaniAllAppend buttons */
        .etaniAllAppend button {
            padding: 5px 10px;
            font-size: 16px;
            margin: 0 5px;
            cursor: pointer;
            border: 1px solid #333;
            background-color: #fff;
        }
        
        /* Custom radio button styles */
        .etaniSettingMode {
            display: inline-block;
            cursor: pointer;
            padding: 4px 8px;
            margin: 0 3px;
            font-size: 14px;
            border: 1px solid #888;
            background-color: #eee;
            color: #333;
            user-select: none;
        }

        .etaniSettingMode.active {
            background-color: #008CBA;
            color: white;
            border-color: #008CBA;
        }

        /* Result and animation item styles */
        .etaniResultDR {
            text-align: center;
            margin-bottom: 10px;
        }

        .etaniResultDownload, .etaniResultRename {
            display: inline-block;
            margin-right: 15px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 16px; 
        }
        
        .etaniResultDownload {
            border: 1px solid blue;
            color: blue;
        }

        .etaniResultRename {
            border: 1px solid brown;
            color: brown;
        }

        .etaniResultImage {
            display: block;
            max-width: 480px;
            width: 100%;
            height: auto;
            margin: 0 auto 10px auto;
            border: 1px solid #000;
            box-sizing: border-box;
        }
        
        .etaniResultSize {
            display: inline-block;
            margin-left: 10px;
            font-size: 12px; 
            color: #555;
        }
        
        /* etaniItem structure */
        .etaniItem {
            min-height: 48px;
            border: 1px solid #ccc;
            box-sizing: border-box;
            width: 100%;
            margin-bottom: -1px;
            background-color: lightyellow;
        }
        
        .etaniItemLeft {
            float: left; 
            width: 60px;
            min-height: 48px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 2px 0;
        }
        
        .etaniItemRight {
            margin-left: 60px; 
            padding: 7px;
            min-height: 48px; 
            background-color: #fff;
        }

        .tileid {
            text-align: center;
            font-size: 12px; 
            word-break: break-all;
            padding-top: 2px;
        }
        
        /* Animation controls */
        .etaniAnimate {
            border: 1px solid #999;
            padding: 5px;
            margin-bottom: 5px;
        }

        .etaniAnimateAttr {
            margin-bottom: 5px;
        }

        .etaniAnimateName {
            display: inline-block;
            padding: 2px 5px;
            background-color: #555;
            color: white;
            margin-right: 10px;
            font-size: 12px;
        }

        .etaniAnimateAttr > span:not(.etaniAnimateName) {
            display: inline-block;
            padding: 2px 5px;
            font-size: 12px;
            margin-right: 7px;
            box-sizing: border-box;
            border-width: 1px;
            border-style: solid;
        }

        .etaniAnimateDur {
            border-color: blue;
            color: blue;
        }

        .etaniAnimateValue {
            margin-top: 5px;
        }

        .etaniAVCtrl {
            display: inline-block;
            vertical-align: top;
            margin-right: 5px;
            margin-bottom: 3px;
        }
        
        .etaniAVCtrl svg {
            margin-left: -1px;
            margin-top: -1px;
        }

        .etaniAVCtrl span {
            display: inline-block;
            width: 24px;
            height: 24px;
            line-height: 24px;
            text-align: center;
            cursor: pointer;
            box-sizing: border-box;
            vertical-align: top;
            margin-right: 3px;
        }

        .etaniAVAdd {
            background-color: #a7fca7;
            border: 1px solid #71c371;
        }
        
        .etaniAVDelete {
            background-color: #ffcccc;
            border: 1px solid #cc3333;
        }

        .etaniAVDelete.deleting {
            background-color: #cc3333;
            color: white;
        }

        .etaniAVCopy {
            background-color: #ccccff;
            border: 1px solid #6666cc;
        }

        .etaniAVCopy.copying {
            background-color: #6666cc;
            color: white;
        }

        .etaniAVMove {
            background-color: #ffcc99;
            border: 1px solid #cc9966;
        }

        .etaniAVMove.moving {
            background-color: #cc9966;
            color: white;
        }
        
        .etaniAVLabel {
            font-size: 14px;
            margin-right: 5px;
        }

        .etaniAV {
            display: inline-block;
            vertical-align: top;
        }

        .etaniAVItem {
            display: inline-block;
            height: 24px;
            background-color: #ff9933;
            border: 1px dashed #00bfff;
            margin: 0 5px 3px;
            padding: 0 5px;
            box-sizing: border-box;
            cursor: pointer;
            position: relative;
            text-align: center;
            line-height: 24px;
            font-size: 12px;
            color: #333;
        }

        .etaniAVItem.deleting-target, .etaniAVItem.copying-target, .etaniAVItem.moving-target {
            background-color: #ff4d4d;
            border: 2px solid red;
        }

        .etaniAVItem.selected-move {
            background-color: #66ccff;
            border: 2px solid #0066cc;
        }
        
        /* Modal/popup styles */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }
        .modal-content {
            position: fixed;
            width: 98%;
            height: 48%;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            z-index: 1001;
            box-sizing: border-box;
        }
        .modal-content textarea {
            width: 100%;
            height: calc(100% - 50px);
            resize: none;
            border: 1px solid #ccc;
            font-size: 12px;
            box-sizing: border-box;
        }
        .modal-close {
            position: absolute;
            top: 0;
            right: 10px;
            font-size: 48px;
            line-height: 1; 
            cursor: pointer;
            color: #333;
        }
    `;

    document.head.appendChild(styleSheet);
}

// Find the first missing letter in the sequence starting from 'a'
function findFirstMissingLetter(existingLetters) {
    let letter = 'a';
    while (existingLetters.includes(letter)) {
        const code = letter.charCodeAt(0);
        if (code >= 97 && code < 122) { // lowercase a-y
            letter = String.fromCharCode(code + 1);
        } else if (code === 122) { // z -> A
            letter = 'A';
        } else if (code >= 65 && code < 90) { // uppercase A-Y
            letter = String.fromCharCode(code + 1);
        } else if (code === 90) { // Z -> a (loop back)
            letter = 'a';
        }
    }
    return letter;
}

// Extract a specific transform function and its value from a transform string
function extractTransformPart(transformString, type) {
    const regex = new RegExp(`(${type})\\(([^)]+)\\)`, 'i');
    const match = transformString.match(regex);
    if (match) {
        return { func: match[1], value: match[2].trim() };
    }
    return { func: '', value: '' };
}

// Parse transform string to get individual transform values
function parseTransform(transformString) {
    const defaultTransform = { translate: '0,0', scale: '1,1', rotate: '0' };
    if (!transformString) return defaultTransform;

    const transform = {};

    const getMatch = (type) => {
        const part = extractTransformPart(transformString, type).value;
        if (!part) return null;
        return part.split(/[,\s]+/).join(',');
    };

    transform.translate = getMatch('translate') || defaultTransform.translate;
    transform.scale = getMatch('scale') || defaultTransform.scale;
    if (transform.scale.split(',').length === 1) {
        transform.scale += `,${transform.scale}`;
    }
    transform.rotate = getMatch('rotate') || defaultTransform.rotate;

    return transform;
}

// Convert SVG string to a Base64 data URL
function svgToBase64(svgString) {
    const base64 = btoa(unescape(encodeURIComponent(svgString)));
    return `data:image/svg+xml;base64,${base64}`;
}

// Format byte size into human-readable string
function formatBytes(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

// Update the result section with the current state of etani_clone
function updateEtaniResult() {
    if (!etani_clone) return;

    const svgString = new XMLSerializer().serializeToString(etani_clone);
    const sizeInBytes = new Blob([svgString]).size;
    const base64Url = svgToBase64(svgString);
    
    const now = new Date();
    const yyyy = now.getFullYear();
    const mm = String(now.getMonth() + 1).padStart(2, '0');
    const dd = String(now.getDate()).padStart(2, '0');
    const hh = String(now.getHours()).padStart(2, '0');
    const mi = String(now.getMinutes()).padStart(2, '0');
    const ss = String(now.getSeconds()).padStart(2, '0');
    const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;

    const imgElement = document.querySelector('.etaniResultImage');
    const downloadElement = document.querySelector('.etaniResultDownload');
    const renameElement = document.querySelector('.etaniResultRename');
    const sizeElement = document.querySelector('.etaniResultSize');

    if (imgElement && downloadElement && renameElement && sizeElement) {
        imgElement.src = base64Url;
        sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
        downloadElement.href = base64Url;
        downloadElement.download = defaultFilename;
        
        renameElement.onclick = (e) => {
            e.preventDefault();
            let currentDownloadName = downloadElement.download;
            let newFilename = prompt("Enter new filename:", currentDownloadName);
            if (newFilename) {
                if (!newFilename.toLowerCase().endsWith('.svg')) {
                    newFilename += '.svg';
                }
                downloadElement.download = newFilename;
                alert(`Filename changed to: ${newFilename}`);
            }
        };
    }
}

// Handle the 'Center' button click to reset the .etdrop transform
function handleCenterClick(e) {
    e.preventDefault();
    if (etani_clone) {
        const etdrop = etani_clone.querySelector('.etdrop');
        if (etdrop) {
            etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
            updateEtaniResult();
        }
    }
}

// Handle the HTML popup window
function handleContentHTMLClick(e) {
    e.preventDefault();
    if (!etani_clone) return;

    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    
    const content = document.createElement('div');
    content.className = 'modal-content';
    
    const close = document.createElement('span');
    close.className = 'modal-close';
    close.innerHTML = '&times;';
    
    const textarea = document.createElement('textarea');
    textarea.value = new XMLSerializer().serializeToString(etani_clone);

    const closeModal = () => {
        if (document.body.contains(overlay)) document.body.removeChild(overlay);
        if (document.body.contains(content)) document.body.removeChild(content);
    };
    close.onclick = closeModal;
    overlay.onclick = closeModal;

    content.appendChild(close);
    content.appendChild(textarea);
    
    document.body.appendChild(overlay);
    document.body.appendChild(content);
}

// Calculate additive transform or opacity value for a given type
function calculateAdditiveValue(type, originalValue, currentBaseValue, scaleValue = '1,1') {
    const roundToThreeDecimals = (value) => {
        return Math.round(value * 1000) / 1000;
    };
    
    if (type === 'rotate') {
        const ovAngle = parseFloat(currentBaseValue.trim());
        const cvAngle = parseFloat(originalValue.trim());
        return roundToThreeDecimals(cvAngle - ovAngle);
    } else if (type === 'scale') {
        const ovScales = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvScales = originalValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvScales[0] / ovScales[0]);
        const v2 = roundToThreeDecimals(cvScales.length > 1 ? (cvScales[1] / ovScales[1]) : (cvScales[0] / ovScales[0]));
        return v1 === v2 ? `${v1}` : `${v1},${v2}`;
    } else if (type === 'translate') {
        const ovCoords = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvCoords = originalValue.split(',').map(c => parseFloat(c.trim()));
        const scaleFactors = scaleValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvCoords[0] - (ovCoords[0] * scaleFactors[0]));
        const v2 = roundToThreeDecimals(cvCoords[1] - (ovCoords[1] * (scaleFactors.length > 1 ? scaleFactors[1] : scaleFactors[0])));
        return `${v1},${v2}`;
    } else if (type === 'opacity') {
        return originalValue;
    }
    return '';
}

// Update duration based on the number of values
function updateDuration(animate, etaniAnimate, newValuesLength) {
    const currentDur = parseFloat(etaniAnimate.querySelector('.etaniAnimateDur').textContent.replace('dur: ', '').replace('s', ''));
    const isIntegerDur = Number.isInteger(currentDur);
    const newDur = isIntegerDur ? (newValuesLength > 1 ? newValuesLength - 1 : 0) : currentDur;
    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    if (newDur > 0) {
        animate.setAttribute('dur', `${newDur}s`);
        if (isRepeat) {
            animate.removeAttribute('fill');
            animate.setAttribute('repeatCount', 'indefinite');
        } else {
            animate.removeAttribute('repeatCount');
            animate.setAttribute('fill', 'freeze');
        }
    } else {
        animate.removeAttribute('dur');
        animate.removeAttribute('fill');
        animate.removeAttribute('repeatCount');
    }

    etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}

// Handle click event for the '+' button for transform or opacity
function handleAVAddClick(e, useElementId, animateType = 'transform') {
    e.preventDefault();
    if (!etani_clone) return;

    // Reset all modes before adding a new value
    resetModes(animateType);

    const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
    const etaniAV = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
    const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);

    if (animateType === 'transform') {
        const originalUseElement = document.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!originalUseElement) return;

        const originalTransformString = originalUseElement.getAttribute('transform') || '';
        const originalTransforms = parseTransform(originalTransformString);

        const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useElementId}"]`);
        if (!translateWrapper) return;

        const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useElementId}"]`);
        const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
        
        if (!scaleWrapper || !cloneUseElement) return;

        const cloneBaseTransformString = cloneUseElement.getAttribute('transform') || 'translate(0,0) scale(1,1) rotate(0)'; 
        const currentBaseTransforms = parseTransform(cloneBaseTransformString);
        
        const currentBaseTranslate = currentBaseTransforms.translate;
        const currentBaseScale = currentBaseTransforms.scale;
        const currentBaseRotate = currentBaseTransforms.rotate;

        const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
        const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
        const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
        
        const allAnimates = [...animatesRotate, ...animatesScale, ...animatesTranslate];
        
        const scaleValue = calculateAdditiveValue('scale', originalTransforms.scale, currentBaseScale);
        
        allAnimates.forEach(animate => {
            const type = animate.getAttribute('type').toLowerCase();
            let currentValueString = animate.getAttribute('values') || '';
            let newValue = '';
            
            if (type === 'rotate') {
                newValue = calculateAdditiveValue('rotate', originalTransforms.rotate, currentBaseRotate);
            } else if (type === 'scale') {
                newValue = scaleValue;
            } else if (type === 'translate') {
                newValue = calculateAdditiveValue('translate', originalTransforms.translate, currentBaseTranslate, scaleValue);
            }
            
            const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
            animate.setAttribute('values', newValueString);
            updateDuration(animate, etaniAnimate, newValueString.split(';').length);
        });
    } else if (animateType === 'opacity') {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const currentValueString = animateOpacity.getAttribute('values') || '';
        const values = currentValueString ? currentValueString.split(';') : [];
        const newValue = values.length > 0 ? values[values.length - 1] : '1';
        const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
        animateOpacity.setAttribute('values', newValueString);
        updateDuration(animateOpacity, etaniAnimate, newValueString.split(';').length);
    }

    // Add new etaniAVItem with appropriate label
    if (etaniAV && etaniAnimate) {
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));

        const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
        if (animateType === 'transform') {
            const existingLetters = Array.from(existingItems).map(item => item.textContent);
            newAVItem.textContent = findFirstMissingLetter(existingLetters);
        } else if (animateType === 'opacity') {
            const values = animateType === 'opacity' ? 
                etaniAnimate.querySelector('animate[attributeName="opacity"]')?.getAttribute('values')?.split(';') || ['1'] :
                ['1'];
            newAVItem.textContent = values[values.length - 1];
        }

        etaniAV.appendChild(newAVItem);
    }

    updateEtaniResult();
}

// Reset all mode states for a specific animateType
function resetModes(animateType, excludeMode = null) {
    const etaniCol = document.querySelector('.etaniCol');
    const avItems = document.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);

    if (excludeMode !== 'delete') {
        isDeleteMode = false;
        etaniCol.classList.remove(`deleting-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVDelete[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('deleting');
            btn.title = 'Delete Value';
        });
    }

    if (excludeMode !== 'copy') {
        isCopyMode = false;
        etaniCol.classList.remove(`copying-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVCopy[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('copying');
            btn.title = 'Copy Value';
        });
    }

    if (excludeMode !== 'move') {
        isMoveMode = false;
        selectedMoveItem = null;
        etaniCol.classList.remove(`moving-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVMove[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('moving');
            btn.title = 'Move Value';
        });
    }

    avItems.forEach(item => {
        item.classList.remove('deleting-target', 'copying-target', 'moving-target', 'selected-move');
    });
}

// Handle click event for the '-' button to toggle deletion mode
function handleAVDeleteToggle(e, animateType = 'transform') {
    e.preventDefault();
    const deleteButton = e.currentTarget;
    resetModes(animateType, 'delete');
    isDeleteMode = !isDeleteMode;
    deleteButton.classList.toggle('deleting', isDeleteMode);
    
    const etaniItemRight = deleteButton.closest('.etaniItemRight');
    const etaniCol = deleteButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('deleting-target', isDeleteMode);
    });

    etaniCol.classList.toggle(`deleting-mode-${animateType}`, isDeleteMode);

    if (isDeleteMode) {
        deleteButton.title = "Click value item to delete (Click again to cancel)";
    } else {
        deleteButton.title = "Delete Value";
        
        avItems.forEach(item => {
            item.classList.remove('deleting-target');
        });
    }
}

// Handle click event for the 'Copy' button to toggle copy mode
function handleAVCopyToggle(e, animateType) {
    e.preventDefault();
    const copyButton = e.currentTarget;
    resetModes(animateType, 'copy');
    isCopyMode = !isCopyMode;
    copyButton.classList.toggle('copying', isCopyMode);
    
    const etaniItemRight = copyButton.closest('.etaniItemRight');
    const etaniCol = copyButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('copying-target', isCopyMode);
    });

    etaniCol.classList.toggle(`copying-mode-${animateType}`, isCopyMode);

    if (isCopyMode) {
        copyButton.title = "Click value item to copy (Click again to cancel)";
    } else {
        copyButton.title = "Copy Value";
        
        avItems.forEach(item => {
            item.classList.remove('copying-target');
        });
    }
}

// Handle click event for the 'Move' button to toggle move mode
function handleAVMoveToggle(e, animateType) {
    e.preventDefault();
    const moveButton = e.currentTarget;
    resetModes(animateType, 'move');
    isMoveMode = !isMoveMode;
    moveButton.classList.toggle('moving', isMoveMode);
    
    const etaniItemRight = moveButton.closest('.etaniItemRight');
    const etaniCol = moveButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('moving-target', isMoveMode);
    });

    etaniCol.classList.toggle(`moving-mode-${animateType}`, isMoveMode);

    if (isMoveMode) {
        moveButton.title = "Click value item to select, then click another to move (Click again to cancel)";
    } else {
        moveButton.title = "Move Value";
        selectedMoveItem = null;
        
        avItems.forEach(item => {
            item.classList.remove('moving-target');
            item.classList.remove('selected-move');
        });
    }
}

// Handle click event for an .etaniAVItem in deletion, copy, or move mode
function handleAVItemClick(e, animateType = 'transform') {
    const item = e.currentTarget;
    const itemIndex = Array.from(item.parentNode.children).indexOf(item);
    const etaniItemRight = item.closest('.etaniItemRight');
    const etaniCol = item.closest('.etaniCol');
    const useId = etaniItemRight.closest('.etaniItem').querySelector('.tileid').textContent;

    const currentTime = Date.now();
    const isDoubleClick = (item === lastClickedItem && (currentTime - lastClickTime) < 2000);

    lastClickTime = currentTime;
    lastClickedItem = item;

    if (etaniCol.classList.contains(`moving-mode-${animateType}`)) {
        const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (!selectedMoveItem) {
            selectedMoveItem = item;
            item.classList.add('selected-move');
            return;
        } else if (selectedMoveItem === item) {
            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            return;
        } else {
            const targetIndex = itemIndex;
            const sourceIndex = Array.from(item.parentNode.children).indexOf(selectedMoveItem);
            
            const parent = item.parentNode;
            if (sourceIndex < targetIndex) {
                if (item.nextSibling) {
                    parent.insertBefore(selectedMoveItem, item.nextSibling);
                } else {
                    parent.appendChild(selectedMoveItem);
                }
            } else {
                parent.insertBefore(selectedMoveItem, item);
            }

            if (animateType === 'transform') {
                const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
                if (!translateWrapper) return;
                
                const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
                const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
                if (!scaleWrapper || !cloneUseElement) return;

                const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
                const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
                const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
                
                const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
                
                allAnimates.forEach(animate => {
                    const values = animate.getAttribute('values').split(';');
                    const valueToMove = values[sourceIndex];
                    values.splice(sourceIndex, 1);
                    if (sourceIndex < targetIndex) {
                        values.splice(targetIndex, 0, valueToMove);
                    } else {
                        values.splice(targetIndex, 0, valueToMove);
                    }
                    animate.setAttribute('values', values.join(';'));
                });
            } else if (animateType === 'opacity') {
                const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
                if (!cloneUseElement) return;

                const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
                if (!animateOpacity) return;

                const values = animateOpacity.getAttribute('values').split(';');
                const valueToMove = values[sourceIndex];
                values.splice(sourceIndex, 1);
                if (sourceIndex < targetIndex) {
                    values.splice(targetIndex, 0, valueToMove);
                } else {
                    values.splice(targetIndex, 0, valueToMove);
                }
                animateOpacity.setAttribute('values', values.join(';'));
            }

            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            updateEtaniResult();
            return;
        }
    }

    if (etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const etaniAV = item.parentNode;
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.textContent = item.textContent;
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
        etaniAV.insertBefore(newAVItem, item.nextSibling);

        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                if (itemIndex < values.length) {
                    values.splice(itemIndex + 1, 0, values[itemIndex]);
                    animate.setAttribute('values', values.join(';'));
                    updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex + 1, 0, values[itemIndex]);
                animateOpacity.setAttribute('values', values.join(';'));
                updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
            }
        }

        updateEtaniResult();
        return;
    }

    if (etaniCol.classList.contains(`deleting-mode-${animateType}`)) {
        const deleteButton = etaniItemRight.querySelector(`.etaniAVDelete[data-type="${animateType}"]`);
        const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);
        
        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                
                if (itemIndex < values.length) {
                    values.splice(itemIndex, 1);
                    if (values.length === 0) {
                        // Remove the entire transform animation and its wrappers
                        translateWrapper.replaceWith(cloneUseElement);
                        cloneUseElement.setAttribute('transform', 'translate(0,0) scale(1,1) rotate(0)');
                        etaniAnimate.remove();
                    } else {
                        animate.setAttribute('values', values.join(';'));
                        updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                    }
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex, 1);
                if (values.length === 0) {
                    // Remove the opacity animation
                    animateOpacity.remove();
                    etaniAnimate.remove();
                } else {
                    animateOpacity.setAttribute('values', values.join(';'));
                    updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            }
        }

        item.remove();

        const remainingItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (remainingItems.length === 0) {
            deleteButton.click();
        } else {
            remainingItems.forEach(item => {
                item.classList.add('deleting-target'); 
            });
        }

        updateEtaniResult();
        
    } else if (animateType === 'opacity' && !etaniCol.classList.contains(`deleting-mode-${animateType}`) && !etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const values = animateOpacity.getAttribute('values').split(';');
        if (itemIndex >= values.length) return;

        if (isDoubleClick) {
            const newValue = prompt("Enter opacity value (0 to 1):", values[itemIndex]);
            if (newValue !== null && !isNaN(newValue) && newValue >= 0 && newValue <= 1) {
                values[itemIndex] = newValue;
                item.textContent = newValue;
                animateOpacity.setAttribute('values', values.join(';'));
                updateEtaniResult();
            }
        } else {
            values[itemIndex] = values[itemIndex] === '0' ? '1' : '0';
            item.textContent = values[itemIndex];
            animateOpacity.setAttribute('values', values.join(';'));
            updateEtaniResult();
        }
    }
}

// Handle click event for the 'Transform' button
function handleAllAppendTransformClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="transform"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const baseAnimate = (type, initialValue) => {
            const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
            animate.setAttribute('attributeName', 'transform');
            animate.setAttribute('attributeType', 'XML');
            animate.setAttribute('type', type);
            animate.setAttribute('values', initialValue);
            animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
            if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');
            animate.setAttribute('additive', 'sum');
            return animate;
        };
        
        cloneUseElement.appendChild(baseAnimate('rotate', '0'));
        
        const scaleWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        scaleWrapper.setAttribute('data-use-href-scale', `#${useId}`);
        scaleWrapper.appendChild(baseAnimate('scale', '1'));
        
        const translateWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        translateWrapper.setAttribute('data-use-href', `#${useId}`);
        translateWrapper.appendChild(baseAnimate('translate', '0,0'));
        
        cloneUseElement.parentNode.insertBefore(translateWrapper, cloneUseElement);
        cloneUseElement.parentNode.insertBefore(scaleWrapper, cloneUseElement);
        scaleWrapper.appendChild(cloneUseElement);
        translateWrapper.appendChild(scaleWrapper);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'transform');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'transform';

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        durSpan.addEventListener('click', () => {
            const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
            const newDur = prompt('Enter duration in seconds:', currentDur);
            if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
                const animates = etani_clone.querySelectorAll(`.etdrop > g[data-use-href="#${useId}"] animateTransform, .etdrop > g[data-use-href="#${useId}"] > g[data-use-href-scale="#${useId}"] animateTransform, .etdrop use[href="#${useId}"] animateTransform`);
                animates.forEach(animate => {
                    if (newDur > 0) {
                        animate.setAttribute('dur', `${newDur}s`);
                        if (isRepeat) {
                            animate.removeAttribute('fill');
                            animate.setAttribute('repeatCount', 'indefinite');
                        } else {
                            animate.removeAttribute('repeatCount');
                            animate.setAttribute('fill', 'freeze');
                        }
                    } else {
                        animate.removeAttribute('dur');
                        animate.removeAttribute('fill');
                        animate.removeAttribute('repeatCount');
                    }
                });
                durSpan.textContent = `dur: ${newDur}s`;
                updateEtaniResult();
            }
        });

        const etaniAnimateAttr = document.createElement('div');
        etaniAnimateAttr.className = 'etaniAnimateAttr';
        etaniAnimateAttr.appendChild(nameSpan);
        etaniAnimateAttr.appendChild(durSpan);
        etaniAnimate.appendChild(etaniAnimateAttr);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = document.createElement('div');
        avCtrlDiv.className = 'etaniAVCtrl';

        const avAddSpan = document.createElement('span');
        avAddSpan.className = 'etaniAVAdd';
        avAddSpan.title = 'Add Transform Value';
        avAddSpan.setAttribute('data-type', 'transform');
        avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'transform'));
        
        const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        plusSvg.setAttribute('width', '24');
        plusSvg.setAttribute('height', '24');
        plusSvg.setAttribute('viewBox', '0 0 24 24');
        plusSvg.setAttribute('fill', 'none');
        plusSvg.setAttribute('stroke', 'currentColor');
        plusSvg.setAttribute('stroke-width', '1');
        plusSvg.setAttribute('stroke-linecap', 'round');
        plusSvg.setAttribute('stroke-linejoin', 'round');
        const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line1.setAttribute('x1', '12');
        line1.setAttribute('y1', '5');
        line1.setAttribute('x2', '12');
        line1.setAttribute('y2', '19');
        const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line2.setAttribute('x1', '5');
        line2.setAttribute('y1', '12');
        line2.setAttribute('x2', '19');
        line2.setAttribute('y2', '12');
        plusSvg.appendChild(line1);
        plusSvg.appendChild(line2);
        avAddSpan.appendChild(plusSvg);

        const avDeleteSpan = document.createElement('span');
        avDeleteSpan.className = 'etaniAVDelete';
        avDeleteSpan.title = 'Delete Value';
        avDeleteSpan.setAttribute('data-type', 'transform');
        avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'transform'));

        const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        minusSvg.setAttribute('width', '24');
        minusSvg.setAttribute('height', '24');
        minusSvg.setAttribute('viewBox', '0 0 24 24');
        minusSvg.setAttribute('fill', 'none');
        minusSvg.setAttribute('stroke', 'currentColor');
        minusSvg.setAttribute('stroke-width', '1');
        minusSvg.setAttribute('stroke-linecap', 'round');
        minusSvg.setAttribute('stroke-linejoin', 'round');
        const minusLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        minusLine.setAttribute('x1', '5');
        minusLine.setAttribute('y1', '12');
        minusLine.setAttribute('x2', '19');
        minusLine.setAttribute('y2', '12');
        minusSvg.appendChild(minusLine);
        avDeleteSpan.appendChild(minusSvg);

        const avCopySpan = document.createElement('span');
        avCopySpan.className = 'etaniAVCopy';
        avCopySpan.title = 'Copy Value';
        avCopySpan.setAttribute('data-type', 'transform');
        avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'transform'));

        const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        copySvg.setAttribute('width', '24');
        copySvg.setAttribute('height', '24');
        copySvg.setAttribute('viewBox', '0 0 24 24');
        copySvg.setAttribute('fill', 'none');
        copySvg.setAttribute('stroke', 'currentColor');
        copySvg.setAttribute('stroke-width', '1');
        copySvg.setAttribute('stroke-linecap', 'round');
        copySvg.setAttribute('stroke-linejoin', 'round');
        const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rect.setAttribute('x', '9');
        rect.setAttribute('y', '9');
        rect.setAttribute('width', '13');
        rect.setAttribute('height', '13');
        rect.setAttribute('rx', '2');
        rect.setAttribute('ry', '2');
        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
        copySvg.appendChild(rect);
        copySvg.appendChild(path);
        avCopySpan.appendChild(copySvg);

        const avMoveSpan = document.createElement('span');
        avMoveSpan.className = 'etaniAVMove';
        avMoveSpan.title = 'Move Value';
        avMoveSpan.setAttribute('data-type', 'transform');
        avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'transform'));

        const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        moveSvg.setAttribute('width', '24');
        moveSvg.setAttribute('height', '24');
        moveSvg.setAttribute('viewBox', '0 0 24 24');
        moveSvg.setAttribute('fill', 'none');
        moveSvg.setAttribute('stroke', 'currentColor');
        moveSvg.setAttribute('stroke-width', '1');
        moveSvg.setAttribute('stroke-linecap', 'round');
        moveSvg.setAttribute('stroke-linejoin', 'round');
        const movePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        movePath.setAttribute('d', 'M 6,4 L 3,8 L 6,12 M 3,8 L 18,8 M 18,12 L 21,16 L 18,20 M 21,16 L 6,16');
        moveSvg.appendChild(movePath);
        avMoveSpan.appendChild(moveSvg);
        
        avCtrlDiv.appendChild(avAddSpan);
        avCtrlDiv.appendChild(avDeleteSpan);
        avCtrlDiv.appendChild(avCopySpan);
        avCtrlDiv.appendChild(avMoveSpan);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = 'a';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'transform'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Opacity' button
function handleAllAppendOpacityClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="opacity"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
        animateOpacity.setAttribute('attributeName', 'opacity');
        animateOpacity.setAttribute('values', '1');
        animateOpacity.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animateOpacity.setAttribute('repeatCount', 'indefinite');
        cloneUseElement.appendChild(animateOpacity);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'opacity');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'opacity';

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        durSpan.addEventListener('click', () => {
            const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
            const newDur = prompt('Enter duration in seconds:', currentDur);
            if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
                const animateOpacity = etani_clone.querySelector(`.etdrop use[href="#${useId}"] animate[attributeName="opacity"]`);
                if (animateOpacity) {
                    if (newDur > 0) {
                        animateOpacity.setAttribute('dur', `${newDur}s`);
                        if (isRepeat) {
                            animateOpacity.removeAttribute('fill');
                            animateOpacity.setAttribute('repeatCount', 'indefinite');
                        } else {
                            animateOpacity.removeAttribute('repeatCount');
                            animateOpacity.setAttribute('fill', 'freeze');
                        }
                    } else {
                        animateOpacity.removeAttribute('dur');
                        animateOpacity.removeAttribute('fill');
                        animateOpacity.removeAttribute('repeatCount');
                    }
                }
                durSpan.textContent = `dur: ${newDur}s`;
                updateEtaniResult();
            }
        });

        const etaniAnimateAttr = document.createElement('div');
        etaniAnimateAttr.className = 'etaniAnimateAttr';
        etaniAnimateAttr.appendChild(nameSpan);
        etaniAnimateAttr.appendChild(durSpan);
        etaniAnimate.appendChild(etaniAnimateAttr);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = document.createElement('div');
        avCtrlDiv.className = 'etaniAVCtrl';

        const avAddSpan = document.createElement('span');
        avAddSpan.className = 'etaniAVAdd';
        avAddSpan.title = 'Add Opacity Value';
        avAddSpan.setAttribute('data-type', 'opacity');
        avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'opacity'));
        
        const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        plusSvg.setAttribute('width', '24');
        plusSvg.setAttribute('height', '24');
        plusSvg.setAttribute('viewBox', '0 0 24 24');
        plusSvg.setAttribute('fill', 'none');
        plusSvg.setAttribute('stroke', 'currentColor');
        plusSvg.setAttribute('stroke-width', '1');
        plusSvg.setAttribute('stroke-linecap', 'round');
        plusSvg.setAttribute('stroke-linejoin', 'round');
        const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line1.setAttribute('x1', '12');
        line1.setAttribute('y1', '5');
        line1.setAttribute('x2', '12');
        line1.setAttribute('y2', '19');
        const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line2.setAttribute('x1', '5');
        line2.setAttribute('y1', '12');
        line2.setAttribute('x2', '19');
        line2.setAttribute('y2', '12');
        plusSvg.appendChild(line1);
        plusSvg.appendChild(line2);
        avAddSpan.appendChild(plusSvg);

        const avDeleteSpan = document.createElement('span');
        avDeleteSpan.className = 'etaniAVDelete';
        avDeleteSpan.title = 'Delete Value';
        avDeleteSpan.setAttribute('data-type', 'opacity');
        avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'opacity'));

        const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        minusSvg.setAttribute('width', '24');
        minusSvg.setAttribute('height', '24');
        minusSvg.setAttribute('viewBox', '0 0 24 24');
        minusSvg.setAttribute('fill', 'none');
        minusSvg.setAttribute('stroke', 'currentColor');
        minusSvg.setAttribute('stroke-width', '1');
        minusSvg.setAttribute('stroke-linecap', 'round');
        minusSvg.setAttribute('stroke-linejoin', 'round');
        const minusLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        minusLine.setAttribute('x1', '5');
        minusLine.setAttribute('y1', '12');
        minusLine.setAttribute('x2', '19');
        minusLine.setAttribute('y2', '12');
        minusSvg.appendChild(minusLine);
        avDeleteSpan.appendChild(minusSvg);

        const avCopySpan = document.createElement('span');
        avCopySpan.className = 'etaniAVCopy';
        avCopySpan.title = 'Copy Value';
        avCopySpan.setAttribute('data-type', 'opacity');
        avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'opacity'));

        const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        copySvg.setAttribute('width', '24');
        copySvg.setAttribute('height', '24');
        copySvg.setAttribute('viewBox', '0 0 24 24');
        copySvg.setAttribute('fill', 'none');
        copySvg.setAttribute('stroke', 'currentColor');
        copySvg.setAttribute('stroke-width', '1');
        copySvg.setAttribute('stroke-linecap', 'round');
        copySvg.setAttribute('stroke-linejoin', 'round');
        const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rect.setAttribute('x', '9');
        rect.setAttribute('y', '9');
        rect.setAttribute('width', '13');
        rect.setAttribute('height', '13');
        rect.setAttribute('rx', '2');
        rect.setAttribute('ry', '2');
        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
        copySvg.appendChild(rect);
        copySvg.appendChild(path);
        avCopySpan.appendChild(copySvg);

        const avMoveSpan = document.createElement('span');
        avMoveSpan.className = 'etaniAVMove';
        avMoveSpan.title = 'Move Value';
        avMoveSpan.setAttribute('data-type', 'opacity');
        avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'opacity'));

        const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        moveSvg.setAttribute('width', '24');
        moveSvg.setAttribute('height', '24');
        moveSvg.setAttribute('viewBox', '0 0 24 24');
        moveSvg.setAttribute('fill', 'none');
        moveSvg.setAttribute('stroke', 'currentColor');
        moveSvg.setAttribute('stroke-width', '1');
        moveSvg.setAttribute('stroke-linecap', 'round');
        moveSvg.setAttribute('stroke-linejoin', 'round');
        const movePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        movePath.setAttribute('d', 'M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16');
        moveSvg.appendChild(movePath);
        avMoveSpan.appendChild(moveSvg);
        
        avCtrlDiv.appendChild(avAddSpan);
        avCtrlDiv.appendChild(avDeleteSpan);
        avCtrlDiv.appendChild(avCopySpan);
        avCtrlDiv.appendChild(avMoveSpan);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = '1';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'opacity'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Fill up values' button
function handleValueCtrlUpClick() {
    if (!etani_clone) return;

    const allAnimates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    let maxValuesLength = 0;

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        maxValuesLength = Math.max(maxValuesLength, values.length);
    });

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        if (values.length < maxValuesLength && values.length > 0) {
            const lastValue = values[values.length - 1];
            while (values.length < maxValuesLength) {
                values.push(lastValue);
            }
            animate.setAttribute('values', values.join(';'));
            const useId = animate.closest('use')?.getAttribute('href')?.substring(1) || 
                          animate.closest('g[data-use-href]')?.getAttribute('data-use-href')?.substring(1);
            if (useId) {
                const etaniAnimate = document.querySelector(`.etaniItem:has(.tileid:where(:text("${useId}"))) .etaniAnimate[data-type="${animate.getAttribute('attributeName') === 'opacity' ? 'opacity' : 'transform'}"]`);
                if (etaniAnimate) {
                    updateDuration(animate, etaniAnimate, values.length);
                }
            }
        }
    });

    const etaniItemRights = document.querySelectorAll('.etaniItemRight');
    etaniItemRights.forEach(itemRight => {
        const useId = itemRight.closest('.etaniItem').querySelector('.tileid').textContent;
        ['transform', 'opacity'].forEach(animateType => {
            const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
            if (!etaniAV) return;

            const currentItems = etaniAV.querySelectorAll('.etaniAVItem');
            const currentValuesLength = currentItems.length;
            if (currentValuesLength >= maxValuesLength) return;

            const lastItem = currentItems[currentItems.length - 1];
            let lastValue = lastItem.textContent;

            for (let i = currentValuesLength; i < maxValuesLength; i++) {
                const newAVItem = document.createElement('span');
                newAVItem.className = 'etaniAVItem';
                newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
                if (animateType === 'transform') {
                    newAVItem.textContent = lastValue; // Use the last letter instead of incrementing
                } else if (animateType === 'opacity') {
                    newAVItem.textContent = lastValue;
                }
                etaniAV.appendChild(newAVItem);
            }
        });
    });

    updateEtaniResult();
}

// Handle setting mode switch (Repeat/Freeze)
function handleSettingModeChange(mode) {
    if (!etani_clone) return;

    const animates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    const isRepeat = mode === 'repeat';

    animates.forEach(animate => {
        if (animate.hasAttribute('dur')) {
            if (isRepeat) {
                animate.removeAttribute('fill');
                animate.setAttribute('repeatCount', 'indefinite');
            } else {
                animate.removeAttribute('repeatCount');
                animate.setAttribute('fill', 'freeze');
            }
        }
    });

    document.querySelector('.etaniSettingFreeze').classList.toggle('active', !isRepeat);
    document.querySelector('.etaniSettingRepeat').classList.toggle('active', isRepeat);

    updateEtaniResult();
}

// Create the internal animation control structure
function createEtaniInner(etaniouter) {
    const originalSvg = document.getElementById('etmain');
    if (!originalSvg) {
        console.error('SVG with ID "etmain" not found.');
        return;
    }
    etani_clone = originalSvg.cloneNode(true);
    const etwaitElement = etani_clone.querySelector('.etwait');
    if (etwaitElement) {
        etwaitElement.remove();
    }
    etani_clone.id = 'etmainani';

    const etaniinner = document.createElement('div');
    etaniinner.className = 'etaniinner';
    etaniinner.id = 'etaniinner';

    const etaniCtrl = document.createElement('div');
    etaniCtrl.className = 'etaniCtrl';
    
    const etaniContent = document.createElement('div');
    etaniContent.className = 'etaniContent';
    
    const contentHTMLLink = document.createElement('a');
    contentHTMLLink.className = 'etaniContentHTML';
    contentHTMLLink.textContent = 'Show HTML';
    contentHTMLLink.href = 'javascript:;';
    contentHTMLLink.addEventListener('click', handleContentHTMLClick);
    etaniContent.appendChild(contentHTMLLink);

    const centerLink = document.createElement('a');
    centerLink.className = 'etaniCenter';
    centerLink.textContent = 'Center';
    centerLink.href = 'javascript:;';
    centerLink.addEventListener('click', handleCenterClick);
    etaniContent.appendChild(centerLink);
    
    etaniCtrl.appendChild(etaniContent);

    const etaniSetting = document.createElement('div');
    etaniSetting.className = 'etaniSetting';
    
    const repeatRadio = document.createElement('span');
    repeatRadio.className = 'etaniSettingMode etaniSettingRepeat active';
    repeatRadio.textContent = 'Repeat';
    repeatRadio.setAttribute('data-mode', 'repeat');
    repeatRadio.addEventListener('click', () => handleSettingModeChange('repeat'));
    
    const freezeRadio = document.createElement('span');
    freezeRadio.className = 'etaniSettingMode etaniSettingFreeze';
    freezeRadio.textContent = 'Freeze';
    freezeRadio.setAttribute('data-mode', 'freeze');
    freezeRadio.addEventListener('click', () => handleSettingModeChange('freeze'));

    etaniSetting.appendChild(repeatRadio);
    etaniSetting.appendChild(freezeRadio);
    etaniCtrl.appendChild(etaniSetting);

    const etaniAllAppend = document.createElement('div');
    etaniAllAppend.className = 'etaniAllAppend';

    const transformButton = document.createElement('button');
    transformButton.className = 'etaniAllAppendTransform';
    transformButton.textContent = 'transform';
    transformButton.addEventListener('click', handleAllAppendTransformClick);
    
    const opacityButton = document.createElement('button');
    opacityButton.className = 'etaniAllAppendOpacity';
    opacityButton.textContent = 'opacity';
    opacityButton.addEventListener('click', handleAllAppendOpacityClick);
    
    etaniAllAppend.appendChild(transformButton);
    etaniAllAppend.appendChild(opacityButton);
    etaniCtrl.appendChild(etaniAllAppend);

    const etaniValueCtrl = document.createElement('div');
    etaniValueCtrl.className = 'etaniValueCtrl';

    const valueCtrlUpLink = document.createElement('a');
    valueCtrlUpLink.className = 'etaniValueCtrlUp';
    valueCtrlUpLink.textContent = 'fill up values';
    valueCtrlUpLink.href = 'javascript:;';
    valueCtrlUpLink.addEventListener('click', handleValueCtrlUpClick);
    etaniValueCtrl.appendChild(valueCtrlUpLink);
    etaniCtrl.appendChild(etaniValueCtrl);

    const etaniCol = document.createElement('div');
    etaniCol.className = 'etaniCol';

    const etaniResult = document.createElement('div');
    etaniResult.className = 'etaniResult';
    
    const resultImg = document.createElement('img');
    resultImg.className = 'etaniResultImage';
    resultImg.alt = 'Rendered Ejtile Animation SVG';

    const resultDRDiv = document.createElement('div');
    resultDRDiv.className = 'etaniResultDR';

    const downloadLink = document.createElement('a');
    downloadLink.className = 'etaniResultDownload';
    downloadLink.textContent = 'Download SVG';
    downloadLink.href = 'javascript:;';

    const renameLink = document.createElement('a');
    renameLink.className = 'etaniResultRename';
    renameLink.textContent = 'Rename File';
    renameLink.href = 'javascript:;';
    
    const sizeSpan = document.createElement('span');
    sizeSpan.className = 'etaniResultSize';

    resultDRDiv.appendChild(downloadLink);
    resultDRDiv.appendChild(renameLink);
    
    etaniResult.appendChild(resultImg);
    etaniResult.appendChild(resultDRDiv);
    etaniResult.appendChild(sizeSpan);
    
    etaniinner.appendChild(etaniCtrl);
    etaniinner.appendChild(etaniCol);
    etaniinner.appendChild(etaniResult);

    etaniouter.appendChild(etaniinner);

    const etdropUses = document.querySelectorAll('.etdrop use');
    const etwaitGroups = document.querySelectorAll('.etwait g');

    etdropUses.forEach((useElement, i) => {
        const tileid = useElement.getAttribute('href').substring(1);
        let targetUse = null;
        for (const group of etwaitGroups) {
            const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
            if (useInGroup) {
                targetUse = useInGroup;
                break;
            }
        }
        
        let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
        if (targetUse) {
            etwaittransform = targetUse.getAttribute('transform') || '';
            etwaitfill = targetUse.getAttribute('fill') || '';
            etwaitstroke = targetUse.getAttribute('stroke') || '';
            etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';

            const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
            const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
            const scalePart = scaleMatch ? scaleMatch[0] : '';
            const rotatePart = rotateMatch ? rotateMatch[0] : '';
            etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
        }
        
        const originalTile = document.querySelector(`defs g#${tileid}`);
        
        const etaniItem = document.createElement('div');
        etaniItem.className = 'etaniItem';

        const etaniItemLeft = document.createElement('div');
        etaniItemLeft.className = 'etaniItemLeft';

        const etaniItemRight = document.createElement('div');
        etaniItemRight.className = 'etaniItemRight';
        
        if (originalTile) {
            const tileclone = originalTile.cloneNode(true);
            tileclone.removeAttribute('id');
            if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
            if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
            if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
            if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
            
            const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svgWrapper.setAttribute('width', '40');
            svgWrapper.setAttribute('height', '40');
            svgWrapper.setAttribute('version', '1.1');
            svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
            svgWrapper.className = 'etanitileimg';
            
            svgWrapper.appendChild(tileclone);
            etaniItemLeft.appendChild(svgWrapper);
        }

        const tileidDiv = document.createElement('div');
        tileidDiv.className = 'tileid';
        tileidDiv.textContent = tileid;
        etaniItemLeft.appendChild(tileidDiv);

        etaniItem.appendChild(etaniItemLeft);
        etaniItem.appendChild(etaniItemRight);
        etaniCol.appendChild(etaniItem);
    });
    
    updateEtaniResult();
}

// Toggle the visibility and content of the animation control panel
function toggleAnimation(event) {
    const etanibutton = event.currentTarget;
    const etaniouter = etanibutton.parentNode;
    const etaniinner = document.getElementById('etaniinner');

    if (etanibutton.textContent === 'Animate it') {
        etanibutton.textContent = 'Close Ejtile Ani';
        createEtaniInner(etaniouter);
    } else if (etanibutton.textContent === 'Close Ejtile Ani') {
        etanibutton.textContent = 'Animate it';
        if (etaniinner) {
            etaniinner.remove();
        }
        etani_clone = null;
        isCopyMode = false;
        isMoveMode = false;
        isDeleteMode = false;
        selectedMoveItem = null;
        lastClickTime = 0;
        lastClickedItem = null;
    }
}

// Initialize the animation control panel on window load
window.addEventListener('load', () => {
    addDynamicStyles();

    const etmainouter = document.getElementById('etmainouter');
    if (etmainouter) {
        const etaniouter = document.createElement('div');
        etaniouter.className = 'etaniouter';

        const etanibutton = document.createElement('button');
        etanibutton.id = 'etanibutton';
        etanibutton.textContent = 'Animate it';
        etanibutton.addEventListener('click', toggleAnimation);

        etaniouter.appendChild(etanibutton);
        etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
    } else {
        console.error('Div with ID "etmainouter" not found.');
    }
});

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 12:11
ejsoon
找到並整合功能重覆的代碼,只需給出需要替換的代碼和位置,以及所替換的內容。

代码: 全选

// SEARCH/REPLACE block 1
// SEARCH for the following code in handleAllAppendTransformClick and handleAllAppendOpacityClick functions
// (This is the repeated code for creating control buttons in both functions)

const avAddSpan = document.createElement('span');
avAddSpan.className = 'etaniAVAdd';
avAddSpan.title = 'Add Transform Value';
avAddSpan.setAttribute('data-type', 'transform');
avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'transform'));

const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
plusSvg.setAttribute('width', '24');
plusSvg.setAttribute('height', '24');
plusSvg.setAttribute('viewBox', '0 0 24 24');
plusSvg.setAttribute('fill', 'none');
plusSvg.setAttribute('stroke', 'currentColor');
plusSvg.setAttribute('stroke-width', '1');
plusSvg.setAttribute('stroke-linecap', 'round');
plusSvg.setAttribute('stroke-linejoin', 'round');
const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line1.setAttribute('x1', '12');
line1.setAttribute('y1', '5');
line1.setAttribute('x2', '12');
line1.setAttribute('y2', '19');
const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line2.setAttribute('x1', '5');
line2.setAttribute('y1', '12');
line2.setAttribute('x2', '19');
line2.setAttribute('y2', '12');
plusSvg.appendChild(line1);
plusSvg.appendChild(line2);
avAddSpan.appendChild(plusSvg);

const avDeleteSpan = document.createElement('span');
avDeleteSpan.className = 'etaniAVDelete';
avDeleteSpan.title = 'Delete Value';
avDeleteSpan.setAttribute('data-type', 'transform');
avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'transform'));

const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
minusSvg.setAttribute('width', '24');
minusSvg.setAttribute('height', '24');
minusSvg.setAttribute('viewBox', '0 0 24 24');
minusSvg.setAttribute('fill', 'none');
minusSvg.setAttribute('stroke', 'currentColor');
minusSvg.setAttribute('stroke-width', '1');
minusSvg.setAttribute('stroke-linecap', 'round');
minusSvg.setAttribute('stroke-linejoin', 'round');
const minusLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
minusLine.setAttribute('x1', '5');
minusLine.setAttribute('y1', '12');
minusLine.setAttribute('x2', '19');
minusLine.setAttribute('y2', '12');
minusSvg.appendChild(minusLine);
avDeleteSpan.appendChild(minusSvg);

const avCopySpan = document.createElement('span');
avCopySpan.className = 'etaniAVCopy';
avCopySpan.title = 'Copy Value';
avCopySpan.setAttribute('data-type', 'transform');
avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'transform'));

const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
copySvg.setAttribute('width', '24');
copySvg.setAttribute('height', '24');
copySvg.setAttribute('viewBox', '0 0 24 24');
copySvg.setAttribute('fill', 'none');
copySvg.setAttribute('stroke', 'currentColor');
copySvg.setAttribute('stroke-width', '1');
copySvg.setAttribute('stroke-linecap', 'round');
copySvg.setAttribute('stroke-linejoin', 'round');
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', '9');
rect.setAttribute('y', '9');
rect.setAttribute('width', '13');
rect.setAttribute('height', '13');
rect.setAttribute('rx', '2');
rect.setAttribute('ry', '2');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
copySvg.appendChild(rect);
copySvg.appendChild(path);
avCopySpan.appendChild(copySvg);

const avMoveSpan = document.createElement('span');
avMoveSpan.className = 'etaniAVMove';
avMoveSpan.title = 'Move Value';
avMoveSpan.setAttribute('data-type', 'transform');
avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'transform'));

const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
moveSvg.setAttribute('width', '24');
moveSvg.setAttribute('height', '24');
moveSvg.setAttribute('viewBox', '0 0 24 24');
moveSvg.setAttribute('fill', 'none');
moveSvg.setAttribute('stroke', 'currentColor');
moveSvg.setAttribute('stroke-width', '1');
moveSvg.setAttribute('stroke-linecap', 'round');
moveSvg.setAttribute('stroke-linejoin', 'round');
const movePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
movePath.setAttribute('d', 'M 6,4 L 3,8 L 6,12 M 3,8 L 18,8 M 18,12 L 21,16 L 18,20 M 21,16 L 6,16');
moveSvg.appendChild(movePath);
avMoveSpan.appendChild(moveSvg);

// REPLACE with the following code in both handleAllAppendTransformClick and handleAllAppendOpacityClick functions
function createControlButtons(animateType, useId) {
    const avCtrlDiv = document.createElement('div');
    avCtrlDiv.className = 'etaniAVCtrl';

    const buttons = [
        {
            className: 'etaniAVAdd',
            title: `Add ${animateType.charAt(0).toUpperCase() + animateType.slice(1)} Value`,
            svg: {
                lines: [
                    { x1: '12', y1: '5', x2: '12', y2: '19' },
                    { x1: '5', y1: '12', x2: '19', y2: '12' }
                ]
            },
            handler: (e) => handleAVAddClick(e, useId, animateType)
        },
        {
            className: 'etaniAVDelete',
            title: 'Delete Value',
            svg: {
                lines: [
                    { x1: '5', y1: '12', x2: '19', y2: '12' }
                ]
            },
            handler: (e) => handleAVDeleteToggle(e, animateType)
        },
        {
            className: 'etaniAVCopy',
            title: 'Copy Value',
            svg: {
                rect: { x: '9', y: '9', width: '13', height: '13', rx: '2', ry: '2' },
                path: { d: 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' }
            },
            handler: (e) => handleAVCopyToggle(e, animateType)
        },
        {
            className: 'etaniAVMove',
            title: 'Move Value',
            svg: {
                path: { d: 'M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16' }
            },
            handler: (e) => handleAVMoveToggle(e, animateType)
        }
    ];

    buttons.forEach(button => {
        const span = document.createElement('span');
        span.className = button.className;
        span.title = button.title;
        span.setAttribute('data-type', animateType);
        span.addEventListener('click', button.handler);

        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', '24');
        svg.setAttribute('height', '24');
        svg.setAttribute('viewBox', '0 0 24 24');
        svg.setAttribute('fill', 'none');
        svg.setAttribute('stroke', 'currentColor');
        svg.setAttribute('stroke-width', '1');
        svg.setAttribute('stroke-linecap', 'round');
        svg.setAttribute('stroke-linejoin', 'round');

        if (button.svg.lines) {
            button.svg.lines.forEach(line => {
                const lineElement = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                Object.entries(line).forEach(([key, value]) => lineElement.setAttribute(key, value));
                svg.appendChild(lineElement);
            });
        }
        if (button.svg.rect) {
            const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
            Object.entries(button.svg.rect).forEach(([key, value]) => rect.setAttribute(key, value));
            svg.appendChild(rect);
        }
        if (button.svg.path) {
            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            Object.entries(button.svg.path).forEach(([key, value]) => path.setAttribute(key, value));
            svg.appendChild(path);
        }

        span.appendChild(svg);
        avCtrlDiv.appendChild(span);
    });

    return avCtrlDiv;
}

// In handleAllAppendTransformClick, REPLACE the control button creation code with:
const avCtrlDiv = createControlButtons('transform', useId);

// In handleAllAppendOpacityClick, REPLACE the control button creation code with:
const avCtrlDiv = createControlButtons('opacity', useId);

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 12:56
ejsoon
當前結果,省了兩百行。

代码: 全选

// Global variable to hold the SVG clone and ensure state is maintained
let etani_clone = null;

// Track copy, move, and delete mode states, selected item, and last click time for double-click detection
let isCopyMode = false;
let isMoveMode = false;
let isDeleteMode = false;
let selectedMoveItem = null;
let lastClickTime = 0;
let lastClickedItem = null;

// Add dynamic CSS styles to the document
function addDynamicStyles() {
    if (document.getElementById('dynamic-et-styles')) {
        return;
    }

    const styleSheet = document.createElement('style');
    styleSheet.id = 'dynamic-et-styles'; 
    styleSheet.textContent = `
        /* Styles for etaniouter and button */
        .etaniouter {
            margin-top: 10px;
            padding: 10px;
            border: 1px solid #ddd;
            background-color: #f5f5f5;
        }

        /* Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
        .etaniinner {
            margin-top: 10px;
        }
        
        .etaniCtrl {
            margin-bottom: 10px; 
            clear: both;
            padding: 5px;
            border: 1px solid #c0c0c0;
            text-align: center;
        }
        
        /* Styles for etaniContent, etaniSetting, etaniAllAppend, etaniValueCtrl */
        .etaniContent, .etaniSetting, .etaniAllAppend, .etaniValueCtrl {
            display: inline-block;
            vertical-align: top;
            padding: 5px;
            border: 1px solid #ccc;
            margin: 0 5px 5px 5px;
            text-align: left;
        }

        .etaniCol {
            border: 1px solid #aaa;
            padding: 5px;
            margin-bottom: 10px; 
            clear: both;
        }

        /* Styles for etaniResult */
        .etaniResult {
            text-align: center;
            margin-bottom: 10px;
            padding: 10px;
            border: 1px solid #bbb;
            box-sizing: border-box;
        }
        
        /* Control and button styles */
        .etaniContent a, .etaniValueCtrl a {
            display: inline-block;
            margin: 0 5px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 14px;
        }
        
        .etaniCenter {
            border: 1px solid green;
            color: green;
        }
        
        .etaniContentHTML {
            border: 1px solid #0099ff;
            color: #0099ff;
            margin-right: 15px;
        }

        .etaniValueCtrlUp {
            border: 1px solid purple;
            color: purple;
        }

        /* Styles for etaniAllAppend buttons */
        .etaniAllAppend button {
            padding: 5px 10px;
            font-size: 16px;
            margin: 0 5px;
            cursor: pointer;
            border: 1px solid #333;
            background-color: #fff;
        }
        
        /* Custom radio button styles */
        .etaniSettingMode {
            display: inline-block;
            cursor: pointer;
            padding: 4px 8px;
            margin: 0 3px;
            font-size: 14px;
            border: 1px solid #888;
            background-color: #eee;
            color: #333;
            user-select: none;
        }

        .etaniSettingMode.active {
            background-color: #008CBA;
            color: white;
            border-color: #008CBA;
        }

        /* Result and animation item styles */
        .etaniResultDR {
            text-align: center;
            margin-bottom: 10px;
        }

        .etaniResultDownload, .etaniResultRename {
            display: inline-block;
            margin-right: 15px;
            text-decoration: none;
            padding: 5px 10px;
            font-size: 16px; 
        }
        
        .etaniResultDownload {
            border: 1px solid blue;
            color: blue;
        }

        .etaniResultRename {
            border: 1px solid brown;
            color: brown;
        }

        .etaniResultImage {
            display: block;
            max-width: 480px;
            width: 100%;
            height: auto;
            margin: 0 auto 10px auto;
            border: 1px solid #000;
            box-sizing: border-box;
        }
        
        .etaniResultSize {
            display: inline-block;
            margin-left: 10px;
            font-size: 12px; 
            color: #555;
        }
        
        /* etaniItem structure */
        .etaniItem {
            min-height: 48px;
            border: 1px solid #ccc;
            box-sizing: border-box;
            width: 100%;
            margin-bottom: -1px;
            background-color: lightyellow;
        }
        
        .etaniItemLeft {
            float: left; 
            width: 60px;
            min-height: 48px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 2px 0;
        }
        
        .etaniItemRight {
            margin-left: 60px; 
            padding: 7px;
            min-height: 48px; 
            background-color: #fff;
        }

        .tileid {
            text-align: center;
            font-size: 12px; 
            word-break: break-all;
            padding-top: 2px;
        }
        
        /* Animation controls */
        .etaniAnimate {
            border: 1px solid #999;
            padding: 5px;
            margin-bottom: 5px;
        }

        .etaniAnimateAttr {
            margin-bottom: 5px;
        }

        .etaniAnimateName {
            display: inline-block;
            padding: 2px 5px;
            background-color: #555;
            color: white;
            margin-right: 10px;
            font-size: 12px;
        }

        .etaniAnimateAttr > span:not(.etaniAnimateName) {
            display: inline-block;
            padding: 2px 5px;
            font-size: 12px;
            margin-right: 7px;
            box-sizing: border-box;
            border-width: 1px;
            border-style: solid;
        }

        .etaniAnimateDur {
            border-color: blue;
            color: blue;
        }

        .etaniAnimateValue {
            margin-top: 5px;
        }

        .etaniAVCtrl {
            display: inline-block;
            vertical-align: top;
            margin-right: 5px;
            margin-bottom: 3px;
        }
        
        .etaniAVCtrl svg {
            margin-left: -1px;
            margin-top: -1px;
        }

        .etaniAVCtrl span {
            display: inline-block;
            width: 24px;
            height: 24px;
            line-height: 24px;
            text-align: center;
            cursor: pointer;
            box-sizing: border-box;
            vertical-align: top;
            margin-right: 3px;
        }

        .etaniAVAdd {
            background-color: #a7fca7;
            border: 1px solid #71c371;
        }
        
        .etaniAVDelete {
            background-color: #ffcccc;
            border: 1px solid #cc3333;
        }

        .etaniAVDelete.deleting {
            background-color: #cc3333;
            color: white;
        }

        .etaniAVCopy {
            background-color: #ccccff;
            border: 1px solid #6666cc;
        }

        .etaniAVCopy.copying {
            background-color: #6666cc;
            color: white;
        }

        .etaniAVMove {
            background-color: #ffcc99;
            border: 1px solid #cc9966;
        }

        .etaniAVMove.moving {
            background-color: #cc9966;
            color: white;
        }
        
        .etaniAVLabel {
            font-size: 14px;
            margin-right: 5px;
        }

        .etaniAV {
            display: inline-block;
            vertical-align: top;
        }

        .etaniAVItem {
            display: inline-block;
            height: 24px;
            background-color: #ff9933;
            border: 1px dashed #00bfff;
            margin: 0 5px 3px;
            padding: 0 5px;
            box-sizing: border-box;
            cursor: pointer;
            position: relative;
            text-align: center;
            line-height: 24px;
            font-size: 12px;
            color: #333;
        }

        .etaniAVItem.deleting-target, .etaniAVItem.copying-target, .etaniAVItem.moving-target {
            background-color: #ff4d4d;
            border: 2px solid red;
        }

        .etaniAVItem.selected-move {
            background-color: #66ccff;
            border: 2px solid #0066cc;
        }
        
        /* Modal/popup styles */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }
        .modal-content {
            position: fixed;
            width: 98%;
            height: 48%;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            z-index: 1001;
            box-sizing: border-box;
        }
        .modal-content textarea {
            width: 100%;
            height: calc(100% - 50px);
            resize: none;
            border: 1px solid #ccc;
            font-size: 12px;
            box-sizing: border-box;
        }
        .modal-close {
            position: absolute;
            top: 0;
            right: 10px;
            font-size: 48px;
            line-height: 1; 
            cursor: pointer;
            color: #333;
        }
    `;

    document.head.appendChild(styleSheet);
}

// Find the first missing letter in the sequence starting from 'a'
function findFirstMissingLetter(existingLetters) {
    let letter = 'a';
    while (existingLetters.includes(letter)) {
        const code = letter.charCodeAt(0);
        if (code >= 97 && code < 122) { // lowercase a-y
            letter = String.fromCharCode(code + 1);
        } else if (code === 122) { // z -> A
            letter = 'A';
        } else if (code >= 65 && code < 90) { // uppercase A-Y
            letter = String.fromCharCode(code + 1);
        } else if (code === 90) { // Z -> a (loop back)
            letter = 'a';
        }
    }
    return letter;
}

// Extract a specific transform function and its value from a transform string
function extractTransformPart(transformString, type) {
    const regex = new RegExp(`(${type})\\(([^)]+)\\)`, 'i');
    const match = transformString.match(regex);
    if (match) {
        return { func: match[1], value: match[2].trim() };
    }
    return { func: '', value: '' };
}

// Parse transform string to get individual transform values
function parseTransform(transformString) {
    const defaultTransform = { translate: '0,0', scale: '1,1', rotate: '0' };
    if (!transformString) return defaultTransform;

    const transform = {};

    const getMatch = (type) => {
        const part = extractTransformPart(transformString, type).value;
        if (!part) return null;
        return part.split(/[,\s]+/).join(',');
    };

    transform.translate = getMatch('translate') || defaultTransform.translate;
    transform.scale = getMatch('scale') || defaultTransform.scale;
    if (transform.scale.split(',').length === 1) {
        transform.scale += `,${transform.scale}`;
    }
    transform.rotate = getMatch('rotate') || defaultTransform.rotate;

    return transform;
}

// Convert SVG string to a Base64 data URL
function svgToBase64(svgString) {
    const base64 = btoa(unescape(encodeURIComponent(svgString)));
    return `data:image/svg+xml;base64,${base64}`;
}

// Format byte size into human-readable string
function formatBytes(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

// Update the result section with the current state of etani_clone
function updateEtaniResult() {
    if (!etani_clone) return;

    const svgString = new XMLSerializer().serializeToString(etani_clone);
    const sizeInBytes = new Blob([svgString]).size;
    const base64Url = svgToBase64(svgString);
    
    const now = new Date();
    const yyyy = now.getFullYear();
    const mm = String(now.getMonth() + 1).padStart(2, '0');
    const dd = String(now.getDate()).padStart(2, '0');
    const hh = String(now.getHours()).padStart(2, '0');
    const mi = String(now.getMinutes()).padStart(2, '0');
    const ss = String(now.getSeconds()).padStart(2, '0');
    const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;

    const imgElement = document.querySelector('.etaniResultImage');
    const downloadElement = document.querySelector('.etaniResultDownload');
    const renameElement = document.querySelector('.etaniResultRename');
    const sizeElement = document.querySelector('.etaniResultSize');

    if (imgElement && downloadElement && renameElement && sizeElement) {
        imgElement.src = base64Url;
        sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
        downloadElement.href = base64Url;
        downloadElement.download = defaultFilename;
        
        renameElement.onclick = (e) => {
            e.preventDefault();
            let currentDownloadName = downloadElement.download;
            let newFilename = prompt("Enter new filename:", currentDownloadName);
            if (newFilename) {
                if (!newFilename.toLowerCase().endsWith('.svg')) {
                    newFilename += '.svg';
                }
                downloadElement.download = newFilename;
                alert(`Filename changed to: ${newFilename}`);
            }
        };
    }
}

// Handle the 'Center' button click to reset the .etdrop transform
function handleCenterClick(e) {
    e.preventDefault();
    if (etani_clone) {
        const etdrop = etani_clone.querySelector('.etdrop');
        if (etdrop) {
            etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
            updateEtaniResult();
        }
    }
}

// Handle the HTML popup window
function handleContentHTMLClick(e) {
    e.preventDefault();
    if (!etani_clone) return;

    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    
    const content = document.createElement('div');
    content.className = 'modal-content';
    
    const close = document.createElement('span');
    close.className = 'modal-close';
    close.innerHTML = '&times;';
    
    const textarea = document.createElement('textarea');
    textarea.value = new XMLSerializer().serializeToString(etani_clone);

    const closeModal = () => {
        if (document.body.contains(overlay)) document.body.removeChild(overlay);
        if (document.body.contains(content)) document.body.removeChild(content);
    };
    close.onclick = closeModal;
    overlay.onclick = closeModal;

    content.appendChild(close);
    content.appendChild(textarea);
    
    document.body.appendChild(overlay);
    document.body.appendChild(content);
}

// Calculate additive transform or opacity value for a given type
function calculateAdditiveValue(type, originalValue, currentBaseValue, scaleValue = '1,1') {
    const roundToThreeDecimals = (value) => {
        return Math.round(value * 1000) / 1000;
    };
    
    if (type === 'rotate') {
        const ovAngle = parseFloat(currentBaseValue.trim());
        const cvAngle = parseFloat(originalValue.trim());
        return roundToThreeDecimals(cvAngle - ovAngle);
    } else if (type === 'scale') {
        const ovScales = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvScales = originalValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvScales[0] / ovScales[0]);
        const v2 = roundToThreeDecimals(cvScales.length > 1 ? (cvScales[1] / ovScales[1]) : (cvScales[0] / ovScales[0]));
        return v1 === v2 ? `${v1}` : `${v1},${v2}`;
    } else if (type === 'translate') {
        const ovCoords = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
        const cvCoords = originalValue.split(',').map(c => parseFloat(c.trim()));
        const scaleFactors = scaleValue.split(',').map(c => parseFloat(c.trim()));
        const v1 = roundToThreeDecimals(cvCoords[0] - (ovCoords[0] * scaleFactors[0]));
        const v2 = roundToThreeDecimals(cvCoords[1] - (ovCoords[1] * (scaleFactors.length > 1 ? scaleFactors[1] : scaleFactors[0])));
        return `${v1},${v2}`;
    } else if (type === 'opacity') {
        return originalValue;
    }
    return '';
}

// Update duration based on the number of values
function updateDuration(animate, etaniAnimate, newValuesLength) {
    const currentDur = parseFloat(etaniAnimate.querySelector('.etaniAnimateDur').textContent.replace('dur: ', '').replace('s', ''));
    const isIntegerDur = Number.isInteger(currentDur);
    const newDur = isIntegerDur ? (newValuesLength > 1 ? newValuesLength - 1 : 0) : currentDur;
    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    if (newDur > 0) {
        animate.setAttribute('dur', `${newDur}s`);
        if (isRepeat) {
            animate.removeAttribute('fill');
            animate.setAttribute('repeatCount', 'indefinite');
        } else {
            animate.removeAttribute('repeatCount');
            animate.setAttribute('fill', 'freeze');
        }
    } else {
        animate.removeAttribute('dur');
        animate.removeAttribute('fill');
        animate.removeAttribute('repeatCount');
    }

    etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}

// Handle click event for the '+' button for transform or opacity
function handleAVAddClick(e, useElementId, animateType = 'transform') {
    e.preventDefault();
    if (!etani_clone) return;

    // Reset all modes before adding a new value
    resetModes(animateType);

    const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
    const etaniAV = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
    const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);

    if (animateType === 'transform') {
        const originalUseElement = document.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!originalUseElement) return;

        const originalTransformString = originalUseElement.getAttribute('transform') || '';
        const originalTransforms = parseTransform(originalTransformString);

        const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useElementId}"]`);
        if (!translateWrapper) return;

        const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useElementId}"]`);
        const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
        
        if (!scaleWrapper || !cloneUseElement) return;

        const cloneBaseTransformString = cloneUseElement.getAttribute('transform') || 'translate(0,0) scale(1,1) rotate(0)'; 
        const currentBaseTransforms = parseTransform(cloneBaseTransformString);
        
        const currentBaseTranslate = currentBaseTransforms.translate;
        const currentBaseScale = currentBaseTransforms.scale;
        const currentBaseRotate = currentBaseTransforms.rotate;

        const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
        const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
        const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
        
        const allAnimates = [...animatesRotate, ...animatesScale, ...animatesTranslate];
        
        const scaleValue = calculateAdditiveValue('scale', originalTransforms.scale, currentBaseScale);
        
        allAnimates.forEach(animate => {
            const type = animate.getAttribute('type').toLowerCase();
            let currentValueString = animate.getAttribute('values') || '';
            let newValue = '';
            
            if (type === 'rotate') {
                newValue = calculateAdditiveValue('rotate', originalTransforms.rotate, currentBaseRotate);
            } else if (type === 'scale') {
                newValue = scaleValue;
            } else if (type === 'translate') {
                newValue = calculateAdditiveValue('translate', originalTransforms.translate, currentBaseTranslate, scaleValue);
            }
            
            const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
            animate.setAttribute('values', newValueString);
            updateDuration(animate, etaniAnimate, newValueString.split(';').length);
        });
    } else if (animateType === 'opacity') {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useElementId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const currentValueString = animateOpacity.getAttribute('values') || '';
        const values = currentValueString ? currentValueString.split(';') : [];
        const newValue = values.length > 0 ? values[values.length - 1] : '1';
        const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
        animateOpacity.setAttribute('values', newValueString);
        updateDuration(animateOpacity, etaniAnimate, newValueString.split(';').length);
    }

    // Add new etaniAVItem with appropriate label
    if (etaniAV && etaniAnimate) {
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));

        const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
        if (animateType === 'transform') {
            const existingLetters = Array.from(existingItems).map(item => item.textContent);
            newAVItem.textContent = findFirstMissingLetter(existingLetters);
        } else if (animateType === 'opacity') {
            const values = animateType === 'opacity' ? 
                etaniAnimate.querySelector('animate[attributeName="opacity"]')?.getAttribute('values')?.split(';') || ['1'] :
                ['1'];
            newAVItem.textContent = values[values.length - 1];
        }

        etaniAV.appendChild(newAVItem);
    }

    updateEtaniResult();
}

// Reset all mode states for a specific animateType
function resetModes(animateType, excludeMode = null) {
    const etaniCol = document.querySelector('.etaniCol');
    const avItems = document.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);

    if (excludeMode !== 'delete') {
        isDeleteMode = false;
        etaniCol.classList.remove(`deleting-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVDelete[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('deleting');
            btn.title = 'Delete Value';
        });
    }

    if (excludeMode !== 'copy') {
        isCopyMode = false;
        etaniCol.classList.remove(`copying-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVCopy[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('copying');
            btn.title = 'Copy Value';
        });
    }

    if (excludeMode !== 'move') {
        isMoveMode = false;
        selectedMoveItem = null;
        etaniCol.classList.remove(`moving-mode-${animateType}`);
        document.querySelectorAll(`.etaniAVMove[data-type="${animateType}"]`).forEach(btn => {
            btn.classList.remove('moving');
            btn.title = 'Move Value';
        });
    }

    avItems.forEach(item => {
        item.classList.remove('deleting-target', 'copying-target', 'moving-target', 'selected-move');
    });
}

// Handle click event for the '-' button to toggle deletion mode
function handleAVDeleteToggle(e, animateType = 'transform') {
    e.preventDefault();
    const deleteButton = e.currentTarget;
    resetModes(animateType, 'delete');
    isDeleteMode = !isDeleteMode;
    deleteButton.classList.toggle('deleting', isDeleteMode);
    
    const etaniItemRight = deleteButton.closest('.etaniItemRight');
    const etaniCol = deleteButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('deleting-target', isDeleteMode);
    });

    etaniCol.classList.toggle(`deleting-mode-${animateType}`, isDeleteMode);

    if (isDeleteMode) {
        deleteButton.title = "Click value item to delete (Click again to cancel)";
    } else {
        deleteButton.title = "Delete Value";
        
        avItems.forEach(item => {
            item.classList.remove('deleting-target');
        });
    }
}

// Handle click event for the 'Copy' button to toggle copy mode
function handleAVCopyToggle(e, animateType) {
    e.preventDefault();
    const copyButton = e.currentTarget;
    resetModes(animateType, 'copy');
    isCopyMode = !isCopyMode;
    copyButton.classList.toggle('copying', isCopyMode);
    
    const etaniItemRight = copyButton.closest('.etaniItemRight');
    const etaniCol = copyButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('copying-target', isCopyMode);
    });

    etaniCol.classList.toggle(`copying-mode-${animateType}`, isCopyMode);

    if (isCopyMode) {
        copyButton.title = "Click value item to copy (Click again to cancel)";
    } else {
        copyButton.title = "Copy Value";
        
        avItems.forEach(item => {
            item.classList.remove('copying-target');
        });
    }
}

// Handle click event for the 'Move' button to toggle move mode
function handleAVMoveToggle(e, animateType) {
    e.preventDefault();
    const moveButton = e.currentTarget;
    resetModes(animateType, 'move');
    isMoveMode = !isMoveMode;
    moveButton.classList.toggle('moving', isMoveMode);
    
    const etaniItemRight = moveButton.closest('.etaniItemRight');
    const etaniCol = moveButton.closest('.etaniCol');
    
    const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
    
    avItems.forEach(item => {
        item.classList.toggle('moving-target', isMoveMode);
    });

    etaniCol.classList.toggle(`moving-mode-${animateType}`, isMoveMode);

    if (isMoveMode) {
        moveButton.title = "Click value item to select, then click another to move (Click again to cancel)";
    } else {
        moveButton.title = "Move Value";
        selectedMoveItem = null;
        
        avItems.forEach(item => {
            item.classList.remove('moving-target');
            item.classList.remove('selected-move');
        });
    }
}

// Handle click event for an .etaniAVItem in deletion, copy, or move mode
function handleAVItemClick(e, animateType = 'transform') {
    const item = e.currentTarget;
    const itemIndex = Array.from(item.parentNode.children).indexOf(item);
    const etaniItemRight = item.closest('.etaniItemRight');
    const etaniCol = item.closest('.etaniCol');
    const useId = etaniItemRight.closest('.etaniItem').querySelector('.tileid').textContent;

    const currentTime = Date.now();
    const isDoubleClick = (item === lastClickedItem && (currentTime - lastClickTime) < 2000);

    lastClickTime = currentTime;
    lastClickedItem = item;

    if (etaniCol.classList.contains(`moving-mode-${animateType}`)) {
        const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (!selectedMoveItem) {
            selectedMoveItem = item;
            item.classList.add('selected-move');
            return;
        } else if (selectedMoveItem === item) {
            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            return;
        } else {
            const targetIndex = itemIndex;
            const sourceIndex = Array.from(item.parentNode.children).indexOf(selectedMoveItem);
            
            const parent = item.parentNode;
            if (sourceIndex < targetIndex) {
                if (item.nextSibling) {
                    parent.insertBefore(selectedMoveItem, item.nextSibling);
                } else {
                    parent.appendChild(selectedMoveItem);
                }
            } else {
                parent.insertBefore(selectedMoveItem, item);
            }

            if (animateType === 'transform') {
                const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
                if (!translateWrapper) return;
                
                const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
                const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
                if (!scaleWrapper || !cloneUseElement) return;

                const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
                const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
                const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
                
                const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
                
                allAnimates.forEach(animate => {
                    const values = animate.getAttribute('values').split(';');
                    const valueToMove = values[sourceIndex];
                    values.splice(sourceIndex, 1);
                    if (sourceIndex < targetIndex) {
                        values.splice(targetIndex, 0, valueToMove);
                    } else {
                        values.splice(targetIndex, 0, valueToMove);
                    }
                    animate.setAttribute('values', values.join(';'));
                });
            } else if (animateType === 'opacity') {
                const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
                if (!cloneUseElement) return;

                const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
                if (!animateOpacity) return;

                const values = animateOpacity.getAttribute('values').split(';');
                const valueToMove = values[sourceIndex];
                values.splice(sourceIndex, 1);
                if (sourceIndex < targetIndex) {
                    values.splice(targetIndex, 0, valueToMove);
                } else {
                    values.splice(targetIndex, 0, valueToMove);
                }
                animateOpacity.setAttribute('values', values.join(';'));
            }

            selectedMoveItem.classList.remove('selected-move');
            selectedMoveItem = null;
            updateEtaniResult();
            return;
        }
    }

    if (etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const etaniAV = item.parentNode;
        const newAVItem = document.createElement('span');
        newAVItem.className = 'etaniAVItem';
        newAVItem.textContent = item.textContent;
        newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
        etaniAV.insertBefore(newAVItem, item.nextSibling);

        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                if (itemIndex < values.length) {
                    values.splice(itemIndex + 1, 0, values[itemIndex]);
                    animate.setAttribute('values', values.join(';'));
                    updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex + 1, 0, values[itemIndex]);
                animateOpacity.setAttribute('values', values.join(';'));
                updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
            }
        }

        updateEtaniResult();
        return;
    }

    if (etaniCol.classList.contains(`deleting-mode-${animateType}`)) {
        const deleteButton = etaniItemRight.querySelector(`.etaniAVDelete[data-type="${animateType}"]`);
        const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);
        
        if (animateType === 'transform') {
            const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
            if (!translateWrapper) return;
            
            const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
            const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
            if (!scaleWrapper || !cloneUseElement) return;

            const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
            const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
            const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
            
            const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
            
            allAnimates.forEach(animate => {
                const values = animate.getAttribute('values').split(';');
                
                if (itemIndex < values.length) {
                    values.splice(itemIndex, 1);
                    if (values.length === 0) {
                        // Remove the entire transform animation and its wrappers
                        translateWrapper.replaceWith(cloneUseElement);
                        cloneUseElement.setAttribute('transform', 'translate(0,0) scale(1,1) rotate(0)');
                        etaniAnimate.remove();
                    } else {
                        animate.setAttribute('values', values.join(';'));
                        updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                    }
                }
            });
        } else if (animateType === 'opacity') {
            const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
            if (!cloneUseElement) return;

            const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
            if (!animateOpacity) return;

            const values = animateOpacity.getAttribute('values').split(';');
            if (itemIndex < values.length) {
                values.splice(itemIndex, 1);
                if (values.length === 0) {
                    // Remove the opacity animation
                    animateOpacity.remove();
                    etaniAnimate.remove();
                } else {
                    animateOpacity.setAttribute('values', values.join(';'));
                    updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
                }
            }
        }

        item.remove();

        const remainingItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
        
        if (remainingItems.length === 0) {
            deleteButton.click();
        } else {
            remainingItems.forEach(item => {
                item.classList.add('deleting-target'); 
            });
        }

        updateEtaniResult();
        
    } else if (animateType === 'opacity' && !etaniCol.classList.contains(`deleting-mode-${animateType}`) && !etaniCol.classList.contains(`copying-mode-${animateType}`)) {
        const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
        if (!animateOpacity) return;

        const values = animateOpacity.getAttribute('values').split(';');
        if (itemIndex >= values.length) return;

        if (isDoubleClick) {
            const newValue = prompt("Enter opacity value (0 to 1):", values[itemIndex]);
            if (newValue !== null && !isNaN(newValue) && newValue >= 0 && newValue <= 1) {
                values[itemIndex] = newValue;
                item.textContent = newValue;
                animateOpacity.setAttribute('values', values.join(';'));
                updateEtaniResult();
            }
        } else {
            values[itemIndex] = values[itemIndex] === '0' ? '1' : '0';
            item.textContent = values[itemIndex];
            animateOpacity.setAttribute('values', values.join(';'));
            updateEtaniResult();
        }
    }
}

// Handle click event for the 'Transform' button
function handleAllAppendTransformClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="transform"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const baseAnimate = (type, initialValue) => {
            const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
            animate.setAttribute('attributeName', 'transform');
            animate.setAttribute('attributeType', 'XML');
            animate.setAttribute('type', type);
            animate.setAttribute('values', initialValue);
            animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
            if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');
            animate.setAttribute('additive', 'sum');
            return animate;
        };
        
        cloneUseElement.appendChild(baseAnimate('rotate', '0'));
        
        const scaleWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        scaleWrapper.setAttribute('data-use-href-scale', `#${useId}`);
        scaleWrapper.appendChild(baseAnimate('scale', '1'));
        
        const translateWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        translateWrapper.setAttribute('data-use-href', `#${useId}`);
        translateWrapper.appendChild(baseAnimate('translate', '0,0'));
        
        cloneUseElement.parentNode.insertBefore(translateWrapper, cloneUseElement);
        cloneUseElement.parentNode.insertBefore(scaleWrapper, cloneUseElement);
        scaleWrapper.appendChild(cloneUseElement);
        translateWrapper.appendChild(scaleWrapper);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'transform');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'transform';

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        durSpan.addEventListener('click', () => {
            const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
            const newDur = prompt('Enter duration in seconds:', currentDur);
            if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
                const animates = etani_clone.querySelectorAll(`.etdrop > g[data-use-href="#${useId}"] animateTransform, .etdrop > g[data-use-href="#${useId}"] > g[data-use-href-scale="#${useId}"] animateTransform, .etdrop use[href="#${useId}"] animateTransform`);
                animates.forEach(animate => {
                    if (newDur > 0) {
                        animate.setAttribute('dur', `${newDur}s`);
                        if (isRepeat) {
                            animate.removeAttribute('fill');
                            animate.setAttribute('repeatCount', 'indefinite');
                        } else {
                            animate.removeAttribute('repeatCount');
                            animate.setAttribute('fill', 'freeze');
                        }
                    } else {
                        animate.removeAttribute('dur');
                        animate.removeAttribute('fill');
                        animate.removeAttribute('repeatCount');
                    }
                });
                durSpan.textContent = `dur: ${newDur}s`;
                updateEtaniResult();
            }
        });

        const etaniAnimateAttr = document.createElement('div');
        etaniAnimateAttr.className = 'etaniAnimateAttr';
        etaniAnimateAttr.appendChild(nameSpan);
        etaniAnimateAttr.appendChild(durSpan);
        etaniAnimate.appendChild(etaniAnimateAttr);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = createControlButtons('transform', useId);
        
        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = 'a';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'transform'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Opacity' button
function handleAllAppendOpacityClick() {
    if (!etani_clone) return;

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    const isRepeat = document.querySelector('.etaniSettingRepeat.active');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];
        
        if (!itemRight) return;

        if (itemRight.querySelector('.etaniAnimate[data-type="opacity"]')) return;

        let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
        if (!cloneUseElement) return;

        const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
        animateOpacity.setAttribute('attributeName', 'opacity');
        animateOpacity.setAttribute('values', '1');
        animateOpacity.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animateOpacity.setAttribute('repeatCount', 'indefinite');
        cloneUseElement.appendChild(animateOpacity);

        const etaniAnimate = document.createElement('div');
        etaniAnimate.className = 'etaniAnimate';
        etaniAnimate.setAttribute('data-type', 'opacity');

        const nameSpan = document.createElement('span');
        nameSpan.className = 'etaniAnimateName';
        nameSpan.textContent = 'opacity';

        const durSpan = document.createElement('span');
        durSpan.className = 'etaniAnimateDur';
        durSpan.textContent = 'dur: 0s';
        durSpan.addEventListener('click', () => {
            const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
            const newDur = prompt('Enter duration in seconds:', currentDur);
            if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
                const animateOpacity = etani_clone.querySelector(`.etdrop use[href="#${useId}"] animate[attributeName="opacity"]`);
                if (animateOpacity) {
                    if (newDur > 0) {
                        animateOpacity.setAttribute('dur', `${newDur}s`);
                        if (isRepeat) {
                            animateOpacity.removeAttribute('fill');
                            animateOpacity.setAttribute('repeatCount', 'indefinite');
                        } else {
                            animateOpacity.removeAttribute('repeatCount');
                            animateOpacity.setAttribute('fill', 'freeze');
                        }
                    } else {
                        animateOpacity.removeAttribute('dur');
                        animateOpacity.removeAttribute('fill');
                        animateOpacity.removeAttribute('repeatCount');
                    }
                }
                durSpan.textContent = `dur: ${newDur}s`;
                updateEtaniResult();
            }
        });

        const etaniAnimateAttr = document.createElement('div');
        etaniAnimateAttr.className = 'etaniAnimateAttr';
        etaniAnimateAttr.appendChild(nameSpan);
        etaniAnimateAttr.appendChild(durSpan);
        etaniAnimate.appendChild(etaniAnimateAttr);

        const valueDiv = document.createElement('div');
        valueDiv.className = 'etaniAnimateValue';
        
        const avCtrlDiv = createControlButtons('opacity', useId);

        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'values : ';

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';
        
        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = '1';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'opacity'));
        
        avDiv.appendChild(avItemSpan);
        
        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);

        etaniAnimate.appendChild(valueDiv);
        itemRight.appendChild(etaniAnimate);
    });

    updateEtaniResult();
}

// Handle click event for the 'Fill up values' button
function handleValueCtrlUpClick() {
    if (!etani_clone) return;

    const allAnimates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    let maxValuesLength = 0;

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        maxValuesLength = Math.max(maxValuesLength, values.length);
    });

    allAnimates.forEach(animate => {
        const values = animate.getAttribute('values')?.split(';') || [];
        if (values.length < maxValuesLength && values.length > 0) {
            const lastValue = values[values.length - 1];
            while (values.length < maxValuesLength) {
                values.push(lastValue);
            }
            animate.setAttribute('values', values.join(';'));
            const useId = animate.closest('use')?.getAttribute('href')?.substring(1) || 
                          animate.closest('g[data-use-href]')?.getAttribute('data-use-href')?.substring(1);
            if (useId) {
                const etaniAnimate = document.querySelector(`.etaniItem:has(.tileid:where(:text("${useId}"))) .etaniAnimate[data-type="${animate.getAttribute('attributeName') === 'opacity' ? 'opacity' : 'transform'}"]`);
                if (etaniAnimate) {
                    updateDuration(animate, etaniAnimate, values.length);
                }
            }
        }
    });

    const etaniItemRights = document.querySelectorAll('.etaniItemRight');
    etaniItemRights.forEach(itemRight => {
        const useId = itemRight.closest('.etaniItem').querySelector('.tileid').textContent;
        ['transform', 'opacity'].forEach(animateType => {
            const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
            if (!etaniAV) return;

            const currentItems = etaniAV.querySelectorAll('.etaniAVItem');
            const currentValuesLength = currentItems.length;
            if (currentValuesLength >= maxValuesLength) return;

            const lastItem = currentItems[currentItems.length - 1];
            let lastValue = lastItem.textContent;

            for (let i = currentValuesLength; i < maxValuesLength; i++) {
                const newAVItem = document.createElement('span');
                newAVItem.className = 'etaniAVItem';
                newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
                if (animateType === 'transform') {
                    newAVItem.textContent = lastValue; // Use the last letter instead of incrementing
                } else if (animateType === 'opacity') {
                    newAVItem.textContent = lastValue;
                }
                etaniAV.appendChild(newAVItem);
            }
        });
    });

    updateEtaniResult();
}

// Handle setting mode switch (Repeat/Freeze)
function handleSettingModeChange(mode) {
    if (!etani_clone) return;

    const animates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
    const isRepeat = mode === 'repeat';

    animates.forEach(animate => {
        if (animate.hasAttribute('dur')) {
            if (isRepeat) {
                animate.removeAttribute('fill');
                animate.setAttribute('repeatCount', 'indefinite');
            } else {
                animate.removeAttribute('repeatCount');
                animate.setAttribute('fill', 'freeze');
            }
        }
    });

    document.querySelector('.etaniSettingFreeze').classList.toggle('active', !isRepeat);
    document.querySelector('.etaniSettingRepeat').classList.toggle('active', isRepeat);

    updateEtaniResult();
}

// Create the internal animation control structure
function createEtaniInner(etaniouter) {
    const originalSvg = document.getElementById('etmain');
    if (!originalSvg) {
        console.error('SVG with ID "etmain" not found.');
        return;
    }
    etani_clone = originalSvg.cloneNode(true);
    const etwaitElement = etani_clone.querySelector('.etwait');
    if (etwaitElement) {
        etwaitElement.remove();
    }
    etani_clone.id = 'etmainani';

    const etaniinner = document.createElement('div');
    etaniinner.className = 'etaniinner';
    etaniinner.id = 'etaniinner';

    const etaniCtrl = document.createElement('div');
    etaniCtrl.className = 'etaniCtrl';
    
    const etaniContent = document.createElement('div');
    etaniContent.className = 'etaniContent';
    
    const contentHTMLLink = document.createElement('a');
    contentHTMLLink.className = 'etaniContentHTML';
    contentHTMLLink.textContent = 'Show HTML';
    contentHTMLLink.href = 'javascript:;';
    contentHTMLLink.addEventListener('click', handleContentHTMLClick);
    etaniContent.appendChild(contentHTMLLink);

    const centerLink = document.createElement('a');
    centerLink.className = 'etaniCenter';
    centerLink.textContent = 'Center';
    centerLink.href = 'javascript:;';
    centerLink.addEventListener('click', handleCenterClick);
    etaniContent.appendChild(centerLink);
    
    etaniCtrl.appendChild(etaniContent);

    const etaniSetting = document.createElement('div');
    etaniSetting.className = 'etaniSetting';
    
    const repeatRadio = document.createElement('span');
    repeatRadio.className = 'etaniSettingMode etaniSettingRepeat active';
    repeatRadio.textContent = 'Repeat';
    repeatRadio.setAttribute('data-mode', 'repeat');
    repeatRadio.addEventListener('click', () => handleSettingModeChange('repeat'));
    
    const freezeRadio = document.createElement('span');
    freezeRadio.className = 'etaniSettingMode etaniSettingFreeze';
    freezeRadio.textContent = 'Freeze';
    freezeRadio.setAttribute('data-mode', 'freeze');
    freezeRadio.addEventListener('click', () => handleSettingModeChange('freeze'));

    etaniSetting.appendChild(repeatRadio);
    etaniSetting.appendChild(freezeRadio);
    etaniCtrl.appendChild(etaniSetting);

    const etaniAllAppend = document.createElement('div');
    etaniAllAppend.className = 'etaniAllAppend';

    const transformButton = document.createElement('button');
    transformButton.className = 'etaniAllAppendTransform';
    transformButton.textContent = 'transform';
    transformButton.addEventListener('click', handleAllAppendTransformClick);
    
    const opacityButton = document.createElement('button');
    opacityButton.className = 'etaniAllAppendOpacity';
    opacityButton.textContent = 'opacity';
    opacityButton.addEventListener('click', handleAllAppendOpacityClick);
    
    etaniAllAppend.appendChild(transformButton);
    etaniAllAppend.appendChild(opacityButton);
    etaniCtrl.appendChild(etaniAllAppend);

    const etaniValueCtrl = document.createElement('div');
    etaniValueCtrl.className = 'etaniValueCtrl';

    const valueCtrlUpLink = document.createElement('a');
    valueCtrlUpLink.className = 'etaniValueCtrlUp';
    valueCtrlUpLink.textContent = 'fill up values';
    valueCtrlUpLink.href = 'javascript:;';
    valueCtrlUpLink.addEventListener('click', handleValueCtrlUpClick);
    etaniValueCtrl.appendChild(valueCtrlUpLink);
    etaniCtrl.appendChild(etaniValueCtrl);

    const etaniCol = document.createElement('div');
    etaniCol.className = 'etaniCol';

    const etaniResult = document.createElement('div');
    etaniResult.className = 'etaniResult';
    
    const resultImg = document.createElement('img');
    resultImg.className = 'etaniResultImage';
    resultImg.alt = 'Rendered Ejtile Animation SVG';

    const resultDRDiv = document.createElement('div');
    resultDRDiv.className = 'etaniResultDR';

    const downloadLink = document.createElement('a');
    downloadLink.className = 'etaniResultDownload';
    downloadLink.textContent = 'Download SVG';
    downloadLink.href = 'javascript:;';

    const renameLink = document.createElement('a');
    renameLink.className = 'etaniResultRename';
    renameLink.textContent = 'Rename File';
    renameLink.href = 'javascript:;';
    
    const sizeSpan = document.createElement('span');
    sizeSpan.className = 'etaniResultSize';

    resultDRDiv.appendChild(downloadLink);
    resultDRDiv.appendChild(renameLink);
    
    etaniResult.appendChild(resultImg);
    etaniResult.appendChild(resultDRDiv);
    etaniResult.appendChild(sizeSpan);
    
    etaniinner.appendChild(etaniCtrl);
    etaniinner.appendChild(etaniCol);
    etaniinner.appendChild(etaniResult);

    etaniouter.appendChild(etaniinner);

    const etdropUses = document.querySelectorAll('.etdrop use');
    const etwaitGroups = document.querySelectorAll('.etwait g');

    etdropUses.forEach((useElement, i) => {
        const tileid = useElement.getAttribute('href').substring(1);
        let targetUse = null;
        for (const group of etwaitGroups) {
            const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
            if (useInGroup) {
                targetUse = useInGroup;
                break;
            }
        }
        
        let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
        if (targetUse) {
            etwaittransform = targetUse.getAttribute('transform') || '';
            etwaitfill = targetUse.getAttribute('fill') || '';
            etwaitstroke = targetUse.getAttribute('stroke') || '';
            etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';

            const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
            const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
            const scalePart = scaleMatch ? scaleMatch[0] : '';
            const rotatePart = rotateMatch ? rotateMatch[0] : '';
            etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
        }
        
        const originalTile = document.querySelector(`defs g#${tileid}`);
        
        const etaniItem = document.createElement('div');
        etaniItem.className = 'etaniItem';

        const etaniItemLeft = document.createElement('div');
        etaniItemLeft.className = 'etaniItemLeft';

        const etaniItemRight = document.createElement('div');
        etaniItemRight.className = 'etaniItemRight';
        
        if (originalTile) {
            const tileclone = originalTile.cloneNode(true);
            tileclone.removeAttribute('id');
            if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
            if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
            if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
            if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
            
            const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svgWrapper.setAttribute('width', '40');
            svgWrapper.setAttribute('height', '40');
            svgWrapper.setAttribute('version', '1.1');
            svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
            svgWrapper.className = 'etanitileimg';
            
            svgWrapper.appendChild(tileclone);
            etaniItemLeft.appendChild(svgWrapper);
        }

        const tileidDiv = document.createElement('div');
        tileidDiv.className = 'tileid';
        tileidDiv.textContent = tileid;
        etaniItemLeft.appendChild(tileidDiv);

        etaniItem.appendChild(etaniItemLeft);
        etaniItem.appendChild(etaniItemRight);
        etaniCol.appendChild(etaniItem);
    });
    
    updateEtaniResult();
}

// Toggle the visibility and content of the animation control panel
function toggleAnimation(event) {
    const etanibutton = event.currentTarget;
    const etaniouter = etanibutton.parentNode;
    const etaniinner = document.getElementById('etaniinner');

    if (etanibutton.textContent === 'Animate it') {
        etanibutton.textContent = 'Close Ejtile Ani';
        createEtaniInner(etaniouter);
    } else if (etanibutton.textContent === 'Close Ejtile Ani') {
        etanibutton.textContent = 'Animate it';
        if (etaniinner) {
            etaniinner.remove();
        }
        etani_clone = null;
        isCopyMode = false;
        isMoveMode = false;
        isDeleteMode = false;
        selectedMoveItem = null;
        lastClickTime = 0;
        lastClickedItem = null;
    }
}

// Create control buttons
function createControlButtons(animateType, useId) {
    const avCtrlDiv = document.createElement('div');
    avCtrlDiv.className = 'etaniAVCtrl';

    const buttons = [
        {
            className: 'etaniAVAdd',
            title: `Add ${animateType.charAt(0).toUpperCase() + animateType.slice(1)} Value`,
            svg: {
                lines: [
                    { x1: '12', y1: '5', x2: '12', y2: '19' },
                    { x1: '5', y1: '12', x2: '19', y2: '12' }
                ]
            },
            handler: (e) => handleAVAddClick(e, useId, animateType)
        },
        {
            className: 'etaniAVDelete',
            title: 'Delete Value',
            svg: {
                lines: [
                    { x1: '5', y1: '12', x2: '19', y2: '12' }
                ]
            },
            handler: (e) => handleAVDeleteToggle(e, animateType)
        },
        {
            className: 'etaniAVCopy',
            title: 'Copy Value',
            svg: {
                rect: { x: '9', y: '9', width: '13', height: '13', rx: '2', ry: '2' },
                path: { d: 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' }
            },
            handler: (e) => handleAVCopyToggle(e, animateType)
        },
        {
            className: 'etaniAVMove',
            title: 'Move Value',
            svg: {
                path: { d: 'M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16' }
            },
            handler: (e) => handleAVMoveToggle(e, animateType)
        }
    ];

    buttons.forEach(button => {
        const span = document.createElement('span');
        span.className = button.className;
        span.title = button.title;
        span.setAttribute('data-type', animateType);
        span.addEventListener('click', button.handler);

        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', '24');
        svg.setAttribute('height', '24');
        svg.setAttribute('viewBox', '0 0 24 24');
        svg.setAttribute('fill', 'none');
        svg.setAttribute('stroke', 'currentColor');
        svg.setAttribute('stroke-width', '1');
        svg.setAttribute('stroke-linecap', 'round');
        svg.setAttribute('stroke-linejoin', 'round');

        if (button.svg.lines) {
            button.svg.lines.forEach(line => {
                const lineElement = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                Object.entries(line).forEach(([key, value]) => lineElement.setAttribute(key, value));
                svg.appendChild(lineElement);
            });
        }
        if (button.svg.rect) {
            const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
            Object.entries(button.svg.rect).forEach(([key, value]) => rect.setAttribute(key, value));
            svg.appendChild(rect);
        }
        if (button.svg.path) {
            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            Object.entries(button.svg.path).forEach(([key, value]) => path.setAttribute(key, value));
            svg.appendChild(path);
        }

        span.appendChild(svg);
        avCtrlDiv.appendChild(span);
    });

    return avCtrlDiv;
}

// Initialize the animation control panel on window load
window.addEventListener('load', () => {
    addDynamicStyles();

    const etmainouter = document.getElementById('etmainouter');
    if (etmainouter) {
        const etaniouter = document.createElement('div');
        etaniouter.className = 'etaniouter';

        const etanibutton = document.createElement('button');
        etanibutton.id = 'etanibutton';
        etanibutton.textContent = 'Animate it';
        etanibutton.addEventListener('click', toggleAnimation);

        etaniouter.appendChild(etanibutton);
        etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
    } else {
        console.error('Div with ID "etmainouter" not found.');
    }
});

Re: 正在做etani,ejtile的動畫套件

发表于 : 2025年 10月 19日 16:59
ejsoon
改動:

一,把etaniSetting改為etaniMode。去掉etaniSettingMode這個類名。

二,在css中,把.etaniSettingMode改為.etaniMode span,把.etaniSettingMode.active改為.etaniMode span.active。

三,把etaniSettingRepeat類名改為etaniModeRepeat,把etaniSettingFreeze類名改為etaniModeFreeze。

四,在etaniMode內新增span.etaniModeFixed{Fixed}。

五,之前當etani_clone中的.etdrop中的<use>的<animate>或<animateTransform>含有dur這個屬性時,在.etaniAVItem中就會用.etaniAnimateDur代表和控制。而「fill=freeze」或「repeatCount=indefinite」這兩個屬性沒有元素來代表和控置。

現在要加入一個.etaniAnimateFR元素來代表和控制這兩個屬性。因為這兩個屬性必然有且僅有一個,所以當<use>的動畫中含有「fill=freeze」時.etaniAnimateFR顯示「freeze」,當含有「repeatCount=indefinite」時顯示「repeat」。

六,當etaniMode選中的是freeze或repeat時,.etaniAnimateFR隱藏。當etaniMode選中的是mixed時,.etaniAnimateFR顯示。

當.etaniAnimateFR顯示時,點擊它,它將在「freeze」和「repeat」之間來回切換,並改變etani_clone中的.etdrop中的<use>對應動畫標籤的值。

七,當etaniMode的Mixed選中時,將處於Mixed狀態,這時如果etani_clone的<use>新增動畫,.etaniAnimateFR代表的值默認是「freeze」。

八,在css中,.etaniAnimateFR的border-color和color都為#78229f。放在.etaniAnimateDur的下面。

只需告知被替換的代碼和位置,以及替換的內容。所有注釋都要是英文。