Buckets:
| import { LitElement, css, html } from 'lit'; | |
| import { customElement, state } from 'lit/decorators.js'; | |
| ('gdm-loading-screen') | |
| export class GDMLoadingScreen extends LitElement { | |
| () private progress = 0; | |
| () private statusText = ''; | |
| () private visible = true; | |
| () private phase: 'intro' | 'loading' | 'ready' | 'exit' = 'intro'; | |
| private resolveReady!: () => void; | |
| ready: Promise<void> = new Promise((r) => (this.resolveReady = r)); | |
| private statuses = [ | |
| { at: 0, text: 'بارگذاری ماژولهای سهبعدی...', dur: 600 }, | |
| { at: 18, text: 'اتصال به موتور هوش مصنوعی...', dur: 800 }, | |
| { at: 35, text: 'راهاندازی سیستم صوتی...', dur: 700 }, | |
| { at: 50, text: 'کالیبره کردن پردازشگر جیوه...', dur: 600 }, | |
| { at: 62, text: 'فعالسازی شبکه عصبی...', dur: 900 }, | |
| { at: 76, text: 'آمادهسازی محیط تعاملی...', dur: 700 }, | |
| { at: 88, text: 'نهایتسازی ارتباط...', dur: 500 }, | |
| { at: 100, text: 'آماده!', dur: 400 }, | |
| ]; | |
| private statusIdx = 0; | |
| private animFrame = 0; | |
| private particles: { x: number; y: number; vx: number; vy: number; r: number; a: number; s: number }[] = []; | |
| private canvas!: HTMLCanvasElement; | |
| private ctx!: CanvasRenderingContext2D; | |
| static styles = css` | |
| :host { | |
| position: fixed; inset: 0; z-index: 9999; | |
| display: flex; align-items: center; justify-content: center; | |
| direction: rtl; | |
| font-family: 'Vazirmatn', 'Tahoma', sans-serif; | |
| overflow: hidden; | |
| transition: opacity 0.8s ease, transform 0.8s ease; | |
| } | |
| :host(.hidden) { | |
| opacity: 0; | |
| transform: scale(1.05); | |
| pointer-events: none; | |
| } | |
| canvas { | |
| position: absolute; inset: 0; | |
| width: 100%; height: 100%; | |
| } | |
| .content { | |
| position: relative; z-index: 2; | |
| display: flex; flex-direction: column; | |
| align-items: center; gap: 32px; | |
| } | |
| .orb-container { | |
| position: relative; | |
| width: 120px; height: 120px; | |
| display: flex; align-items: center; justify-content: center; | |
| } | |
| .orb { | |
| width: 80px; height: 80px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle at 35% 30%, #e8f0ff, #7090c0 50%, #304060 100%); | |
| box-shadow: | |
| 0 0 60px rgba(100, 160, 255, 0.3), | |
| 0 0 120px rgba(100, 160, 255, 0.15), | |
| inset 0 -10px 40px rgba(0, 0, 0, 0.4); | |
| animation: orbPulse 3s ease-in-out infinite; | |
| position: relative; | |
| } | |
| .orb::before { | |
| content: ''; | |
| position: absolute; inset: 2px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, 0.5) 0%, transparent 60%); | |
| } | |
| .orb-ring { | |
| position: absolute; | |
| border-radius: 50%; | |
| border: 1.5px solid rgba(120, 180, 255, 0.2); | |
| animation: ringExpand 3s ease-out infinite; | |
| } | |
| .orb-ring:nth-child(2) { width: 100px; height: 100px; animation-delay: 0.5s; } | |
| .orb-ring:nth-child(3) { width: 120px; height: 120px; animation-delay: 1s; border-color: rgba(120, 180, 255, 0.1); } | |
| @keyframes ringExpand { | |
| 0% { transform: scale(0.8); opacity: 0.8; } | |
| 100% { transform: scale(1.5); opacity: 0; } | |
| } | |
| @keyframes orbPulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.03); } | |
| } | |
| .title { | |
| font-size: 22px; | |
| font-weight: 300; | |
| letter-spacing: 3px; | |
| color: rgba(255, 255, 255, 0.9); | |
| text-shadow: 0 2px 20px rgba(100, 160, 255, 0.2); | |
| display: flex; flex-direction: column; align-items: center; gap: 6px; | |
| } | |
| .title-fa { | |
| font-size: 26px; | |
| font-weight: 600; | |
| background: linear-gradient(135deg, #c8d8f0, #88bbff, #aaccff); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| animation: shimmer 3s ease-in-out infinite; | |
| } | |
| .title-en { | |
| font-size: 11px; | |
| font-weight: 400; | |
| color: rgba(255, 255, 255, 0.25); | |
| letter-spacing: 5px; | |
| text-transform: uppercase; | |
| } | |
| @keyframes shimmer { | |
| 0%, 100% { filter: brightness(1); } | |
| 50% { filter: brightness(1.3); } | |
| } | |
| .progress-wrap { | |
| width: 240px; | |
| position: relative; | |
| } | |
| .progress-track { | |
| width: 100%; height: 2px; | |
| background: rgba(255, 255, 255, 0.06); | |
| border-radius: 2px; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| width: 0%; | |
| background: linear-gradient(90deg, #4488ff, #88ccff, #aaccff); | |
| border-radius: 2px; | |
| transition: width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); | |
| box-shadow: 0 0 12px rgba(100, 160, 255, 0.4); | |
| position: relative; | |
| } | |
| .progress-fill::after { | |
| content: ''; | |
| position: absolute; top: 0; right: 0; bottom: 0; | |
| width: 30px; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent); | |
| animation: glide 1.5s ease-in-out infinite; | |
| } | |
| @keyframes glide { | |
| 0% { right: 100%; } | |
| 100% { right: -30px; } | |
| } | |
| .status { | |
| font-size: 12px; | |
| color: rgba(255, 255, 255, 0.35); | |
| text-align: center; | |
| min-height: 20px; | |
| transition: all 0.3s ease; | |
| letter-spacing: 1px; | |
| } | |
| .ready-btn { | |
| margin-top: 8px; | |
| padding: 12px 40px; | |
| border-radius: 30px; | |
| border: 1px solid rgba(100, 160, 255, 0.3); | |
| background: rgba(100, 160, 255, 0.1); | |
| color: #aaccff; | |
| font-size: 14px; | |
| font-family: inherit; | |
| cursor: pointer; | |
| transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); | |
| backdrop-filter: blur(10px); | |
| letter-spacing: 1px; | |
| opacity: 0; | |
| transform: translateY(10px); | |
| animation: fadeUp 0.6s ease forwards; | |
| } | |
| .ready-btn:hover { | |
| background: rgba(100, 160, 255, 0.2); | |
| border-color: rgba(100, 160, 255, 0.6); | |
| box-shadow: 0 8px 30px rgba(100, 160, 255, 0.15); | |
| transform: translateY(-2px) scale(1.02); | |
| } | |
| @keyframes fadeUp { | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .percent { | |
| font-size: 11px; | |
| color: rgba(255, 255, 255, 0.2); | |
| text-align: center; | |
| margin-top: 4px; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| `; | |
| constructor() { | |
| super(); | |
| this.statusText = this.statuses[0].text; | |
| } | |
| connectedCallback() { | |
| super.connectedCallback(); | |
| setTimeout(() => this.startParticles(), 50); | |
| setTimeout(() => this.startLoading(), 400); | |
| } | |
| disconnectedCallback() { | |
| super.disconnectedCallback(); | |
| cancelAnimationFrame(this.animFrame); | |
| } | |
| private startParticles() { | |
| this.canvas = this.shadowRoot!.querySelector('canvas')!; | |
| this.ctx = this.canvas.getContext('2d')!; | |
| this.resize(); | |
| window.addEventListener('resize', () => this.resize()); | |
| for (let i = 0; i < 60; i++) this.particles.push(this.createParticle()); | |
| this.renderParticles(); | |
| } | |
| private resize() { | |
| this.canvas.width = window.innerWidth; | |
| this.canvas.height = window.innerHeight; | |
| } | |
| private createParticle() { | |
| return { | |
| x: Math.random() * window.innerWidth, | |
| y: Math.random() * window.innerHeight, | |
| vx: (Math.random() - 0.5) * 0.3, | |
| vy: (Math.random() - 0.5) * 0.3 - 0.1, | |
| r: 0.5 + Math.random() * 1.5, | |
| a: 0.1 + Math.random() * 0.3, | |
| s: Math.random() * 0.02 + 0.005, | |
| }; | |
| } | |
| private renderParticles = () => { | |
| this.animFrame = requestAnimationFrame(this.renderParticles); | |
| const ctx = this.ctx; | |
| const w = this.canvas.width; | |
| const h = this.canvas.height; | |
| ctx.clearRect(0, 0, w, h); | |
| for (const p of this.particles) { | |
| p.x += p.vx; | |
| p.y += p.vy; | |
| p.a += (Math.sin(Date.now() * p.s) * 0.5 + 0.5 - p.a) * 0.02; | |
| if (p.x < -10) p.x = w + 10; | |
| if (p.x > w + 10) p.x = -10; | |
| if (p.y < -10) p.y = h + 10; | |
| if (p.y > h + 10) p.y = -10; | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); | |
| ctx.fillStyle = `rgba(150, 200, 255, ${p.a * 0.4})`; | |
| ctx.fill(); | |
| } | |
| // Connections | |
| for (let i = 0; i < this.particles.length; i++) { | |
| for (let j = i + 1; j < this.particles.length; j++) { | |
| const a = this.particles[i]; | |
| const b = this.particles[j]; | |
| const dx = a.x - b.x; | |
| const dy = a.y - b.y; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < 120) { | |
| const alpha = (1 - dist / 120) * 0.08; | |
| ctx.beginPath(); | |
| ctx.moveTo(a.x, a.y); | |
| ctx.lineTo(b.x, b.y); | |
| ctx.strokeStyle = `rgba(100, 160, 255, ${alpha})`; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| }; | |
| private startLoading() { | |
| this.phase = 'loading'; | |
| this.advanceProgress(); | |
| } | |
| private advanceProgress = () => { | |
| if (this.statusIdx >= this.statuses.length) return; | |
| const status = this.statuses[this.statusIdx]; | |
| if (this.progress < status.at) { | |
| this.progress += 0.5 + Math.random() * 1.5; | |
| if (this.progress > status.at) this.progress = status.at; | |
| setTimeout(this.advanceProgress, 30 + Math.random() * 60); | |
| return; | |
| } | |
| this.statusText = status.text; | |
| this.statusIdx++; | |
| if (this.statusIdx >= this.statuses.length) { | |
| this.progress = 100; | |
| setTimeout(() => this.onReady(), 500); | |
| return; | |
| } | |
| setTimeout(this.advanceProgress, status.dur); | |
| }; | |
| private onReady() { | |
| this.phase = 'ready'; | |
| this.resolveReady(); | |
| } | |
| dismiss() { | |
| this.classList.add('hidden'); | |
| this.dispatchEvent(new CustomEvent('start-request', { bubbles: true, composed: true })); | |
| setTimeout(() => { | |
| this.visible = false; | |
| this.remove(); | |
| }, 800); | |
| } | |
| render() { | |
| return html` | |
| <canvas></canvas> | |
| <div class="content"> | |
| <div class="orb-container"> | |
| <div class="orb-ring"></div> | |
| <div class="orb-ring"></div> | |
| <div class="orb"></div> | |
| </div> | |
| <div class="title"> | |
| <span class="title-fa">دستیار هوشمند</span> | |
| <span class="title-en">Persian AI Assistant</span> | |
| </div> | |
| <div class="progress-wrap"> | |
| <div class="progress-track"> | |
| <div class="progress-fill" style="width:${this.progress}%"></div> | |
| </div> | |
| <div class="percent">${Math.round(this.progress)}%</div> | |
| </div> | |
| <div class="status">${this.statusText}</div> | |
| ${this.phase === 'ready' | |
| ? html`<button class="ready-btn" @click=${this.dismiss}>شروع گفتگو</button>` | |
| : ''} | |
| </div> | |
| `; | |
| } | |
| } | |
| declare global { | |
| interface HTMLElementTagNameMap { | |
| 'gdm-loading-screen': GDMLoadingScreen; | |
| } | |
| } | |
Xet Storage Details
- Size:
- 10.8 kB
- Xet hash:
- 8e81f1bd6e59208f5d6463e16448ef8b77dd7055d690152734569582bc320380
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.