UNPKG

attractive-alert

Version:

一个美观易用的弹框库,提供丰富的图标和动画效果

17 lines (16 loc) 24.4 kB
/** * Copyright 2025 王小玗 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function(){"use strict";class Atal{constructor(executor){this.state="pending";this.value=null;this.okCallbacks=[];this.cancelCallbacks=[];this.customCallbacks=[];this.createdCallback=()=>{};this.closeCallback=()=>{};const executeCallbacks=(callbacks,value=null)=>{const copies=callbacks.slice();callbacks.length=0;copies.forEach(cb=>{try{value!==null?cb(value):cb()}catch(error){console.error("Atal callback error:",error)}})};const resolve=(type,value=null)=>{if(this.state!=="pending")return;this.state=type;this.value=value;if(type==="ok"){executeCallbacks(this.okCallbacks)}else if(type==="cancel"){executeCallbacks(this.cancelCallbacks)}else if(type==="custom"){executeCallbacks(this.customCallbacks,value)}try{this.closeCallback()}catch(error){console.error("Atal close callback error:",error)}};const ok=()=>resolve("ok");const cancel=()=>resolve("cancel");const custom=value=>resolve("custom",value);const close=fn=>{this.closeCallback=fn};const created=value=>{this.createdCallback(value)};try{executor(ok,cancel,custom,created,close)}catch(error){cancel()}this._executeCallbacks=executeCallbacks}ok(callback){if(this.state==="ok"){callback()}else if(this.state==="pending"){this.okCallbacks.push(callback)}return this}cancel(callback){if(this.state==="cancel"){callback()}else if(this.state==="pending"){this.cancelCallbacks.push(callback)}return this}custom(callback){if(this.state==="custom"){callback(this.value)}else if(this.state==="pending"){this.customCallbacks.push(callback)}return this}created(callback){this.createdCallback=callback;return this}close(type="cancel",value=null){if(this.state!=="pending")return this;try{this.closeCallback()}catch(error){console.error("Atal close callback error:",error)}if(type==="ok"){this.state="ok";this._executeCallbacks(this.okCallbacks)}else if(type==="cancel"){this.state="cancel";this._executeCallbacks(this.cancelCallbacks)}else if(type==="custom"){this.state="custom";this.value=value;this._executeCallbacks(this.customCallbacks,value)}return this}static alert(){return new Atal((ok,cancel,custom,created,close)=>{let options;if(arguments.length===1&&typeof arguments[0]==="object"){options={...Atal.defaults,...arguments[0]}}else{options={...Atal.defaults};if(arguments.length>=1)options.title=arguments[0];if(arguments.length>=2)options.content=arguments[1];if(arguments.length>=3)options.icon=arguments[2]}options.buttons=Atal.processButtons(options.buttons);Atal.addStyles();const alertElement=Atal.createAlertHTML(options);document.body.appendChild(alertElement);const overlayElement=alertElement.querySelector(".atal-overlay");if(!options.animateIcon){const icon=overlayElement.querySelector(".atal-icon");if(icon){const paths=icon.querySelectorAll("*");paths.forEach(path=>{path.style.animation="none";path.style.strokeDasharray="none";path.style.strokeDashoffset="0";if(path.classList.contains("dot")){path.style.opacity="1"}})}}if(options.timer){setTimeout(()=>{Atal.closeAlert(alertElement);ok()},options.timer)}const confirmBtn=overlayElement.querySelector(".atal-confirm");if(confirmBtn){confirmBtn.addEventListener("click",()=>{Atal.closeAlert(alertElement);ok()})}const cancelBtn=overlayElement.querySelector(".atal-cancel");if(cancelBtn){cancelBtn.addEventListener("click",()=>{Atal.closeAlert(alertElement);cancel()})}const customBtns=overlayElement.querySelectorAll(".atal-custom");customBtns.forEach((btn,index)=>{btn.addEventListener("click",()=>{Atal.closeAlert(alertElement);custom({index:index,button:options.buttons.custom[index]})})});overlayElement.addEventListener("click",e=>{if(e.target===overlayElement){Atal.closeAlert(alertElement);cancel()}});close(()=>Atal.closeAlert(alertElement));Promise.resolve().then(()=>{created(alertElement)})})}static processButtons(buttonsConfig){const result={confirm:null,cancel:null,custom:[]};if(buttonsConfig.confirm!==undefined&&buttonsConfig.confirm!==null){if(buttonsConfig.confirm===true){result.confirm=Atal.defaultButtons.confirm}else if(typeof buttonsConfig.confirm==="string"||typeof buttonsConfig.confirm==="object"){result.confirm=buttonsConfig.confirm}}if(buttonsConfig.cancel!==undefined&&buttonsConfig.cancel!==null){if(buttonsConfig.cancel===true){result.cancel=Atal.defaultButtons.cancel}else if(typeof buttonsConfig.cancel==="string"||typeof buttonsConfig.cancel==="object"){result.cancel=buttonsConfig.cancel}}if(Array.isArray(buttonsConfig.custom)){result.custom=buttonsConfig.custom.filter(btn=>btn!==null&&btn!==undefined&&btn!==false)}return result}static native(){window.alert=Atal.alert}static closeAlert(element){if(element&&element.parentNode){element.parentNode.removeChild(element)}}static addStyles(){if(document.getElementById("atal-styles"))return;const style=document.createElement("style");style.id="atal-styles";style.textContent=Atal.styles;document.head.appendChild(style)}static createAlertHTML(options){let iconHTML;let iconColor;if(typeof options.icon==="object"){iconHTML=options.icon?Atal.icons[options.icon.type](options.icon.color)||"":"";iconColor=options.icon.color}else if(typeof options.icon==="string"){iconHTML=options.icon?Atal.icons[options.icon](Atal.getIconColor(options.icon))||"":"";iconColor=Atal.getIconColor(options.icon)}else{iconHTML="";iconColor=Atal.getIconColor("")}let content="";if(options.content instanceof HTMLElement){content='<div id="atal-custom-content"></div>'}else if(typeof options.content==="string"){content=options.html?options.content:`<p>${Atal.escapeHTML(options.content)}</p>`}let customButtonsHTML="";if(options.buttons.custom&&options.buttons.custom.length>0){customButtonsHTML=options.buttons.custom.map((button,index)=>`<button class="atal-button atal-custom" data-index="${index}" style="--icon-color: ${button.color||iconColor}">${button.text}</button>`).join("")}let confirmButtonHTML="";if(options.buttons.confirm){if(typeof options.buttons.confirm==="object"){confirmButtonHTML=`<button class="atal-button atal-confirm" style="--icon-color: ${options.buttons.confirm.color||iconColor}">${options.buttons.confirm.text}</button>`}else if(typeof options.buttons.confirm==="string"){confirmButtonHTML=`<button class="atal-button atal-confirm" style="--icon-color: ${iconColor}">${options.buttons.confirm}</button>`}}let cancelButtonHTML="";if(options.buttons.cancel){if(typeof options.buttons.cancel==="object"){cancelButtonHTML=`<button class="atal-button atal-cancel" style="--icon-color: ${options.buttons.cancel.color||iconColor}">${options.buttons.cancel.text}</button>`}else if(typeof options.buttons.cancel==="string"){cancelButtonHTML=`<button class="atal-button atal-cancel" style="--icon-color: ${iconColor}">${options.buttons.cancel}</button>`}}const alertElement=document.createElement("div");alertElement.innerHTML=`\n <div class="atal-overlay">\n <div class="atal-container">\n <div class="atal-header">\n ${iconHTML}\n <h3 class="atal-title">${options.title}</h3>\n </div>\n <div class="atal-content" id="atal-content">\n ${content}\n </div>\n <div class="atal-footer">\n ${cancelButtonHTML}\n ${customButtonsHTML}\n ${confirmButtonHTML}\n </div>\n </div>\n </div>\n `;if(options.content instanceof HTMLElement){const contentContainer=alertElement.querySelector("#atal-custom-content");if(contentContainer){contentContainer.appendChild(options.content)}}return alertElement}static getIconColor(icon){const colors={success:"#4CAF50",error:"#F44336",warning:"#FF9800",info:"#2196F3",question:"#9C27B0",heart:"#E91E63",star:"#FFC107",download:"#2196F3",upload:"#2196F3",key:"#FF9800"};return colors[icon]||"#2196F3"}static escapeHTML(str){return str.replace(/[&<>"']/g,function(match){switch(match){case"&":return"&amp;";case"<":return"&lt;";case">":return"&gt;";case'"':return"&quot;";case"'":return"&#x27;";default:return match}})}static at(defaultOptions){const atAtal=function(){let options;if(arguments.length===1&&typeof arguments[0]==="object"){options={...defaultOptions,...arguments[0]}}else{options={...defaultOptions};if(arguments.length>=1)options.title=arguments[0];if(arguments.length>=2)options.content=arguments[1];if(arguments.length>=3)options.icon=arguments[2]}return Atal.alert(options)};Object.keys(Atal).forEach(key=>{if(typeof Atal[key]==="function"&&key!=="at"){atAtal[key]=Atal[key]}});atAtal.updateDefaults=function(newDefaults){Object.assign(defaultOptions,newDefaults)};atAtal.getDefaults=function(){return{...defaultOptions}};atAtal.at=function(newDefaults){return Atal.at({...defaultOptions,...newDefaults})};return atAtal}static setDefaults(options){if(options){Atal.defaults={...Atal.defaults,...options}}else{return Atal.defaults}}static setDefaultButtons(confirm,cancel){if(confirm===undefined&&cancel===undefined)return Atal.defaultButtons;if(confirm!==undefined)Atal.defaultButtons.confirm=confirm;if(cancel!==undefined)Atal.defaultButtons.cancel=cancel}}Atal.defaults={title:"",content:"",icon:"",buttons:{confirm:true,cancel:false,custom:[]},timer:null,animateIcon:true,html:false};Atal.defaultButtons={confirm:"OK",cancel:"Cancel"};Atal.icons={success:color=>`\n <svg class="atal-icon success-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="check" d="M20,32l10,10,14-20" style="stroke-dasharray: 44; stroke-dashoffset: 44;"/>\n </svg>\n `,error:color=>`\n <svg class="atal-icon error-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="line line1" d="M24,24L40,40" style="stroke-dasharray: 22.6; stroke-dashoffset: 22.6;"/>\n <path class="line line2" d="M40,24L24,40" style="stroke-dasharray: 22.6; stroke-dashoffset: 22.6;"/>\n </svg>\n `,warning:color=>`\n <svg class="atal-icon warning-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="triangle" d="M32,10L4,54H60L32,10Z" style="stroke-dasharray: 160; stroke-dashoffset: 160;"/>\n <path class="exclamation" d="M32,26V40M32,44V44" style="stroke-dasharray: 22; stroke-dashoffset: 22;"/>\n <path class="dot" d="M32,46a1,1,0,1,1-1,1A1,1,0,0,1,32,46Z" fill="${color}" style="opacity: 0;"/>\n </svg>\n `,info:color=>`\n <svg class="atal-icon info-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="dot" d="M32,16a2,2,0,1,1-2,2A2,2,0,0,1,32,16Z" style="opacity: 0;"/>\n <path class="line" d="M32,28V44" style="stroke-dasharray: 20; stroke-dashoffset: 20; stroke-width: 4;"/>\n </svg>\n `,question:color=>`\n <svg class="atal-icon question-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="question-mark" d="M24,24c0-4,4-8,8-8,6,0,8,4,8,8,0,4-4,6-6,8-2,2-2,6-2,6" style="stroke-dasharray: 60; stroke-dashoffset: 60;"/>\n <path class="dot" d="M32,48a2,2,0,1,1-2,2A2,2,0,0,1,32,48Z" fill="${color}" style="opacity: 0;"/>\n </svg>\n `,heart:color=>`\n <svg class="atal-icon heart-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" fill="none">\n <path class="heart" d="M32,14c5.5-11,22-8.5,22,5.5,0,16.5-22,27.5-22,27.5S10,36,10,19.5C10,6.5,26.5,3,32,14Z" style="stroke-dasharray: 142; stroke-dashoffset: 142;"/>\n </svg>\n `,star:color=>`\n <svg class="atal-icon star-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="star" d="M32,8l8,16,18,2-12,14,2,18-16-10-16,10,2-18L6,26l18-2L32,8Z" style="stroke-dasharray: 181; stroke-dashoffset: 181;"/>\n </svg>\n `,download:color=>`\n <svg class="atal-icon download-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="arrow" d="M32,20V44M32,44L24,36M32,44L40,36" style="stroke-dasharray: 48; stroke-dashoffset: 48;"/>\n <path class="line" d="M20,52H44" style="stroke-dasharray: 24; stroke-dashoffset: 24;"/>\n </svg>\n `,upload:color=>`\n <svg class="atal-icon upload-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="arrow" d="M32,44V20M32,20L24,28M32,20L40,28" style="stroke-dasharray: 48; stroke-dashoffset: 48;"/>\n <path class="line" d="M20,52H44" style="stroke-dasharray: 24; stroke-dashoffset: 24;"/>\n </svg>\n `,key:color=>`\n <svg class="atal-icon key-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none">\n <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/>\n <path class="key-handle" d="M44,32a6,6,0,0,1-6,6,6,6,0,0,1-6-6,6,6,0,0,1,6-6A6,6,0,0,1,44,32Z" style="stroke-dasharray: 38; stroke-dashoffset: 38;"/>\n <path class="key-teeth" d="M44,32H52" style="stroke-dasharray: 8; stroke-dashoffset: 8;"/>\n <path class="key-shaft" d="M32,32H40" style="stroke-dasharray: 8; stroke-dashoffset: 8;"/>\n </svg>\n `};Atal.styles=`\n .atal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n }\n \n .atal-container {\n background-color: white;\n border-radius: 16px;\n width: 90%;\n max-width: 420px;\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.15);\n overflow: hidden;\n animation: atalFadeIn 0.3s ease-out;\n }\n \n .atal-header {\n padding: 30px 25px 15px;\n text-align: center;\n }\n \n .atal-icon {\n width: 80px;\n height: 80px;\n margin-bottom: 20px;\n transform-origin: center;\n }\n \n .atal-title {\n margin: 0;\n font-size: 1.75rem;\n color: #333;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n \n .atal-content {\n padding: 15px 30px;\n text-align: center;\n color: #555;\n font-size: 1.1rem;\n line-height: 1.6;\n }\n \n .atal-footer {\n display: flex;\n justify-content: center;\n padding: 25px;\n gap: 15px;\n flex-wrap: wrap;\n }\n \n .atal-button {\n padding: 12px 28px;\n border: none;\n border-radius: 8px;\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n font-weight: 500;\n letter-spacing: 0.5px;\n min-width: 80px;\n }\n \n .atal-confirm {\n background-color: var(--icon-color);\n color: white;\n }\n \n .atal-confirm:hover {\n background-color: var(--icon-color);\n filter: brightness(0.9);\n transform: translateY(-2px);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12);\n }\n \n .atal-cancel {\n background-color: #f5f5f5;\n color: #666;\n border: 1px solid #ddd;\n }\n \n .atal-cancel:hover {\n background-color: #e5e5e5;\n transform: translateY(-2px);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);\n }\n \n .atal-custom {\n background-color: var(--icon-color, #6c757d);\n color: white;\n }\n \n .atal-custom:hover {\n background-color: var(--icon-color, #5a6268);\n filter: brightness(0.9);\n transform: translateY(-2px);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12);\n }\n \n @keyframes atalFadeIn {\n from { opacity: 0; transform: translateY(-20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n \n /* 图标动画 */\n /* 成功图标动画 */\n .success-icon .circle {\n animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .success-icon .check {\n animation: check-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards;\n }\n \n /* 错误图标动画 */\n .error-icon .circle {\n animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .error-icon .line {\n animation: error-line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards;\n }\n \n .error-icon .line2 {\n animation-delay: 0.8s;\n }\n \n /* 警告图标动画 */\n .warning-icon .triangle {\n animation: triangle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .warning-icon .exclamation {\n animation: exclamation-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards;\n }\n\n .warning-icon .dot {\n animation: dot-anim 0.3s ease-out 0.8s forwards;\n }\n \n /* 信息图标动画 */\n .info-icon .circle {\n animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .info-icon .line {\n animation: line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards;\n }\n \n .info-icon .dot {\n animation: dot-anim 0.3s ease-out 0.8s forwards;\n }\n \n /* 问题图标动画 */\n .question-icon .circle {\n animation: circle-anim 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .question-icon .question-mark {\n animation: question-anim 0.6s cubic-bezier(0.65, 0, 0.45, 1) 0.6s forwards;\n }\n \n .question-icon .dot {\n animation: dot-anim 0.3s ease-out 1.2s forwards;\n }\n \n /* 心形图标动画 */\n .heart-icon .heart {\n animation: heart-anim 0.5s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n transform-origin: center;\n }\n \n /* 星星图标动画 */\n .star-icon .star {\n animation: star-anim 0.8s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n transform-origin: center;\n }\n \n /* 动画关键帧 */\n @keyframes circle-anim {\n 0% { stroke-dashoffset: 180; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes check-anim {\n 0% { stroke-dashoffset: 44; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes error-line-anim {\n 0% { stroke-dashoffset: 22.6; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes triangle-anim {\n 0% { stroke-dashoffset: 150; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes exclamation-anim {\n 0% { stroke-dashoffset: 22; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes line-anim {\n 0% { stroke-dashoffset: 22; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes dot-anim {\n 0% { opacity: 0; transform: scale(0.3); }\n 50% { opacity: 1; transform: scale(1.2); }\n 100% { opacity: 1; transform: scale(1); }\n }\n \n @keyframes question-anim {\n 0% { stroke-dashoffset: 60; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes heart-anim {\n 0% { \n stroke-dashoffset: 120;\n transform: scale(0.8);\n }\n 50% {\n stroke-dashoffset: 60;\n transform: scale(1.1);\n }\n 100% { \n stroke-dashoffset: 0;\n transform: scale(1);\n }\n }\n \n @keyframes star-anim {\n 0% { \n stroke-dashoffset: 150;\n transform: scale(0.8) rotate(-30deg);\n opacity: 0;\n }\n 50% {\n stroke-dashoffset: 75;\n transform: scale(1.1) rotate(10deg);\n opacity: 1;\n }\n 100% { \n stroke-dashoffset: 0;\n transform: scale(1) rotate(0);\n opacity: 1;\n }\n }\n /* 下载图标动画 */\n .download-icon .circle {\n animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .download-icon .arrow {\n animation: download-arrow-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.4s forwards;\n }\n \n .download-icon .line {\n animation: download-line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;\n }\n \n /* 上传图标动画 */\n .upload-icon .circle {\n animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .upload-icon .arrow {\n animation: upload-arrow-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.4s forwards;\n }\n \n .upload-icon .line {\n animation: upload-line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;\n }\n \n /* 钥匙图标动画 */\n .key-icon .circle {\n animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards;\n }\n \n .key-icon .key-handle {\n animation: key-handle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.4s forwards;\n }\n \n .key-icon .key-teeth {\n animation: key-teeth-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;\n }\n \n .key-icon .key-shaft {\n animation: key-shaft-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 1.1s forwards;\n }\n \n /* 新增动画关键帧 */\n @keyframes download-arrow-anim {\n 0% { stroke-dashoffset: 48; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes download-line-anim {\n 0% { stroke-dashoffset: 24; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes upload-arrow-anim {\n 0% { stroke-dashoffset: 48; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes upload-line-anim {\n 0% { stroke-dashoffset: 24; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes key-handle-anim {\n 0% { \n stroke-dashoffset: 38;\n transform: scale(0.8);\n }\n 100% { \n stroke-dashoffset: 0;\n transform: scale(1);\n }\n }\n \n @keyframes key-teeth-anim {\n 0% { stroke-dashoffset: 8; }\n 100% { stroke-dashoffset: 0; }\n }\n \n @keyframes key-shaft-anim {\n 0% { stroke-dashoffset: 8; }\n 100% { stroke-dashoffset: 0; }\n }\n`;window.Atal=Atal})();