| |
| <script> |
| import { onMount, createEventDispatcher } from 'svelte'; |
| import Loader from './Loader.svelte'; |
| |
| const dispatch = createEventDispatcher(); |
| |
| let canvas; |
| let ctx; |
| let phase = 'loading'; |
| let progress = 0; |
| let opacity = 1; |
| |
| |
| const MBM_TEXT = 'एम.बी.एम'; |
| const MAC_TEXT = 'एम.ए.सी'; |
| const SUBTITLE = 'MBM AI Cloud'; |
| |
| let animFrame; |
| let startTime = 0; |
| const MORPH_DURATION = 3000; |
| const HOLD_DURATION = 1500; |
| const FADE_DURATION = 800; |
| |
| function skipAll() { |
| cancelAnimationFrame(animFrame); |
| try { localStorage.setItem('mac_intro_seen', '1'); } catch {} |
| opacity = 0; |
| setTimeout(() => dispatch('done'), 300); |
| } |
| |
| function lerp(a, b, t) { |
| return a + (b - a) * t; |
| } |
| |
| function easeInOutCubic(t) { |
| return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; |
| } |
| |
| function drawFrame(timestamp) { |
| if (!ctx || !canvas) return; |
| const w = canvas.width; |
| const h = canvas.height; |
| const elapsed = timestamp - startTime; |
| |
| ctx.clearRect(0, 0, w, h); |
| |
| |
| ctx.fillStyle = '#0e0d0c'; |
| ctx.fillRect(0, 0, w, h); |
| |
| if (elapsed < MORPH_DURATION) { |
| |
| const t = easeInOutCubic(Math.min(elapsed / MORPH_DURATION, 1)); |
| progress = t; |
| |
| |
| const mbmAlpha = 1 - t; |
| ctx.save(); |
| ctx.globalAlpha = mbmAlpha; |
| ctx.font = `bold ${Math.round(h * 0.18)}px serif`; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillStyle = '#E8E6E1'; |
| |
| const mbmY = h * 0.42 - t * 20; |
| ctx.fillText(MBM_TEXT, w / 2, mbmY); |
| ctx.restore(); |
| |
| |
| const macAlpha = t; |
| ctx.save(); |
| ctx.globalAlpha = macAlpha; |
| ctx.font = `bold ${Math.round(h * 0.18)}px serif`; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillStyle = '#D97449'; |
| const macY = h * 0.42 + (1 - t) * 20; |
| ctx.fillText(MAC_TEXT, w / 2, macY); |
| ctx.restore(); |
| |
| |
| if (t > 0.1 && t < 0.9) { |
| const glitchIntensity = Math.sin(t * Math.PI) * 0.5; |
| for (let i = 0; i < 3; i++) { |
| const gy = Math.random() * h; |
| const gw = Math.random() * w * 0.6; |
| const gx = Math.random() * (w - gw); |
| ctx.save(); |
| ctx.globalAlpha = glitchIntensity * 0.15; |
| ctx.fillStyle = i % 2 === 0 ? '#D97449' : '#E8E6E1'; |
| ctx.fillRect(gx, gy, gw, 2); |
| ctx.restore(); |
| } |
| } |
| |
| |
| ctx.save(); |
| ctx.globalAlpha = t * 0.5; |
| ctx.font = `${Math.round(h * 0.04)}px 'Inter', sans-serif`; |
| ctx.textAlign = 'center'; |
| ctx.fillStyle = 'rgba(255,255,255,0.35)'; |
| ctx.letterSpacing = '0.3em'; |
| ctx.fillText(SUBTITLE, w / 2, h * 0.62); |
| ctx.restore(); |
| |
| |
| ctx.save(); |
| const engAlpha = Math.sin(t * Math.PI) * 0.3; |
| ctx.globalAlpha = engAlpha; |
| ctx.font = `900 ${Math.round(h * 0.12)}px 'Courier New', monospace`; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| const engText = t < 0.5 ? 'MBM' : 'MAC'; |
| ctx.fillStyle = t < 0.5 ? 'rgba(232,230,225,0.15)' : 'rgba(217,116,73,0.2)'; |
| ctx.fillText(engText, w / 2, h * 0.78); |
| ctx.restore(); |
| |
| } else if (elapsed < MORPH_DURATION + HOLD_DURATION) { |
| |
| ctx.save(); |
| ctx.font = `bold ${Math.round(h * 0.18)}px serif`; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillStyle = '#D97449'; |
| ctx.fillText(MAC_TEXT, w / 2, h * 0.42); |
| ctx.restore(); |
| |
| |
| ctx.save(); |
| ctx.globalAlpha = 0.5; |
| ctx.font = `${Math.round(h * 0.04)}px 'Inter', sans-serif`; |
| ctx.textAlign = 'center'; |
| ctx.fillStyle = 'rgba(255,255,255,0.35)'; |
| ctx.fillText(SUBTITLE, w / 2, h * 0.62); |
| ctx.restore(); |
| |
| |
| const holdT = (elapsed - MORPH_DURATION) / HOLD_DURATION; |
| const glowAlpha = 0.08 + Math.sin(holdT * Math.PI * 2) * 0.04; |
| ctx.save(); |
| const gradient = ctx.createRadialGradient(w/2, h*0.42, 0, w/2, h*0.42, h*0.3); |
| gradient.addColorStop(0, `rgba(217,116,73,${glowAlpha})`); |
| gradient.addColorStop(1, 'transparent'); |
| ctx.fillStyle = gradient; |
| ctx.fillRect(0, 0, w, h); |
| ctx.restore(); |
| |
| } else if (elapsed < MORPH_DURATION + HOLD_DURATION + FADE_DURATION) { |
| |
| const fadeT = (elapsed - MORPH_DURATION - HOLD_DURATION) / FADE_DURATION; |
| opacity = 1 - easeInOutCubic(fadeT); |
| } else { |
| |
| try { localStorage.setItem('mac_intro_seen', '1'); } catch {} |
| dispatch('done'); |
| return; |
| } |
| |
| animFrame = requestAnimationFrame(drawFrame); |
| } |
| |
| function startMorph() { |
| phase = 'morph'; |
| const dpr = window.devicePixelRatio || 1; |
| canvas.width = canvas.offsetWidth * dpr; |
| canvas.height = canvas.offsetHeight * dpr; |
| ctx = canvas.getContext('2d'); |
| ctx.scale(dpr, dpr); |
| |
| canvas.style.width = canvas.offsetWidth + 'px'; |
| canvas.style.height = canvas.offsetHeight + 'px'; |
| |
| startTime = performance.now(); |
| animFrame = requestAnimationFrame(drawFrame); |
| } |
| |
| onMount(() => { |
| |
| setTimeout(startMorph, 600); |
| return () => { |
| if (animFrame) cancelAnimationFrame(animFrame); |
| }; |
| }); |
| </script> |
|
|
| <div class="morph-overlay" style="opacity: {opacity}" class:hidden={opacity <= 0}> |
| {#if phase === 'loading'} |
| <div class="morph-loader"> |
| <Loader size={80} color="#D97449" /> |
| </div> |
| {/if} |
|
|
| <canvas bind:this={canvas} class="morph-canvas"></canvas> |
|
|
| <button class="morph-skip" on:click={skipAll}>skip ›</button> |
| </div> |
|
|
| <style> |
| .morph-overlay { |
| position: fixed; |
| inset: 0; |
| z-index: 10000; |
| background: #0e0d0c; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: opacity 0.4s ease; |
| } |
| .morph-overlay.hidden { |
| pointer-events: none; |
| } |
| |
| .morph-canvas { |
| width: 100%; |
| height: 100%; |
| position: absolute; |
| inset: 0; |
| } |
| |
| .morph-loader { |
| position: absolute; |
| z-index: 2; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .morph-skip { |
| position: absolute; |
| bottom: 5%; |
| right: 4%; |
| z-index: 3; |
| font-size: 12px; |
| color: rgba(255,255,255,0.2); |
| background: none; |
| border: none; |
| cursor: pointer; |
| letter-spacing: 0.08em; |
| font-family: inherit; |
| padding: 4px 8px; |
| border-radius: 4px; |
| transition: color 0.2s, background 0.2s; |
| } |
| .morph-skip:hover { |
| color: rgba(255,255,255,0.5); |
| background: rgba(255,255,255,0.05); |
| } |
| </style> |
|
|