File manager - Edit - /home/webapp69.cm.in.th/u69319090030/Portfolio/script.js
Back
/* ==================== CUTSCENE / INTRO CONTROLLER ==================== */ (function () { 'use strict'; const overlay = document.getElementById('cutscene-overlay'); const video = document.getElementById('cutscene-video'); const enterBtn = document.getElementById('cutscene-skip-btn'); const starsEl = document.getElementById('cutscene-stars'); const typeEl = document.getElementById('cs-typewriter'); const waveform = document.getElementById('music-waveform'); const pauseBtn = document.getElementById('music-pause-btn'); if (!overlay) return; let exited = false; let wavePaused = false; /* --- Exit cutscene --- */ function exitCutscene() { if (exited) return; exited = true; if (video) video.pause(); overlay.classList.add('cutscene-exit'); overlay.addEventListener('transitionend', () => { overlay.classList.add('cutscene-hidden'); document.body.style.overflow = ''; // Start hero typewriter after cutscene finishes if (typeof window.startHeroTypewriter === 'function') { setTimeout(window.startHeroTypewriter, 200); } }, { once: true }); } /* --- Typewriter effect --- */ const phrases = [ 'นักศึกษาแผนกวิชาอิเล็กทรอนิกส์', 'วิทยาลัยเทคนิคเชียงใหม่', 'Electronics Student · Hobbyist Gamer' ]; let phraseIdx = 0, charIdx = 0, isDeleting = false; function typeWriter() { if (!typeEl || exited) return; const current = phrases[phraseIdx]; if (!isDeleting) { charIdx++; typeEl.textContent = current.slice(0, charIdx); if (charIdx === current.length) { isDeleting = true; setTimeout(typeWriter, 2000); return; } setTimeout(typeWriter, 70); } else { charIdx--; typeEl.textContent = current.slice(0, charIdx); if (charIdx === 0) { isDeleting = false; phraseIdx = (phraseIdx + 1) % phrases.length; setTimeout(typeWriter, 400); return; } setTimeout(typeWriter, 35); } } /* --- Spawn particle stars --- */ function spawnStars() { if (!starsEl) return; const COUNT = 60; for (let i = 0; i < COUNT; i++) { const star = document.createElement('div'); star.className = 'cs-star'; const size = Math.random() * 2.5 + 0.5; const dur = (Math.random() * 3 + 2).toFixed(1); const delay = (Math.random() * 4).toFixed(1); star.style.cssText = ` width:${size}px; height:${size}px; top:${Math.random()*100}%; left:${Math.random()*100}%; --dur:${dur}s; --delay:${delay}s; opacity:0.1; `; starsEl.appendChild(star); } } /* --- Video ready --- */ if (video) { video.addEventListener('canplay', () => video.classList.add('video-ready')); } /* --- Enter button --- */ if (enterBtn) enterBtn.addEventListener('click', exitCutscene); /* --- Keyboard Enter --- */ document.addEventListener('keydown', (e) => { if ((e.key === 'Enter' || e.key === ' ') && !exited) exitCutscene(); }); /* --- Music pause toggle --- */ if (pauseBtn && waveform) { pauseBtn.addEventListener('click', () => { wavePaused = !wavePaused; waveform.classList.toggle('paused', wavePaused); pauseBtn.innerHTML = wavePaused ? '<i class="fa-solid fa-play"></i>' : '<i class="fa-solid fa-pause"></i>'; }); } /* --- Custom Cursor --- */ const cursorDot = document.getElementById('cs-cursor-dot'); const cursorRing = document.getElementById('cs-cursor-ring'); let mouseX = -200, mouseY = -200; let ringX = -200, ringY = -200; let cursorRAF = null; function updateCursor() { // Dot: instant follow if (cursorDot) { cursorDot.style.left = mouseX + 'px'; cursorDot.style.top = mouseY + 'px'; } // Ring: smooth lag (lerp) ringX += (mouseX - ringX) * 0.12; ringY += (mouseY - ringY) * 0.12; if (cursorRing) { cursorRing.style.left = ringX + 'px'; cursorRing.style.top = ringY + 'px'; } if (!exited) cursorRAF = requestAnimationFrame(updateCursor); } overlay.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; if (cursorDot) cursorDot.style.opacity = '1'; if (cursorRing) cursorRing.style.opacity = '1'; }); overlay.addEventListener('mouseleave', () => { if (cursorDot) cursorDot.style.opacity = '0'; if (cursorRing) cursorRing.style.opacity = '0'; }); // Hover effect on interactive elements overlay.querySelectorAll('button, a, kbd').forEach(el => { el.addEventListener('mouseenter', () => { if (cursorRing) cursorRing.classList.add('hovering'); }); el.addEventListener('mouseleave', () => { if (cursorRing) cursorRing.classList.remove('hovering'); }); }); // Click ripple + shrink overlay.addEventListener('mousedown', () => { if (cursorDot) cursorDot.classList.add('clicking'); if (cursorRing) cursorRing.classList.add('clicking'); // Ripple const ripple = document.createElement('div'); ripple.className = 'cs-cursor-ripple'; ripple.style.left = mouseX + 'px'; ripple.style.top = mouseY + 'px'; overlay.appendChild(ripple); ripple.addEventListener('animationend', () => ripple.remove()); }); overlay.addEventListener('mouseup', () => { if (cursorDot) cursorDot.classList.remove('clicking'); if (cursorRing) cursorRing.classList.remove('clicking'); }); // Start cursor tracking if (cursorDot) { cursorDot.style.opacity = '0'; } if (cursorRing) { cursorRing.style.opacity = '0'; } cursorRAF = requestAnimationFrame(updateCursor); /* --- Init --- */ document.body.style.overflow = 'hidden'; spawnStars(); setTimeout(typeWriter, 1200); })(); /* ==================== HERO TYPEWRITER EFFECT ==================== */ function startHeroTypewriter() { const subtitle = document.getElementById('hero-subtitle'); const titleEl = document.getElementById('hero-title'); const nameSpan = document.getElementById('hero-name-span'); const profession = document.getElementById('hero-profession'); const descEl = document.getElementById('hero-description'); const btnsEl = document.getElementById('hero-buttons'); if (!subtitle) return; // Speed settings (ms per character) const SPEED_FAST = 45; const SPEED_MED = 55; // Helper: type text into element char by char function typeInto(el, text, speed, onDone) { el.style.opacity = '1'; el.textContent = ''; let i = 0; function next() { if (i < text.length) { el.textContent += text[i++]; setTimeout(next, speed); } else { if (onDone) onDone(); } } next(); } // Helper: fade in instantly function fadeIn(el, onDone) { el.style.transition = 'opacity 0.6s ease'; el.style.opacity = '1'; setTimeout(onDone || function(){}, 650); } // Sequence: subtitle → title prefix + name → profession → description → buttons typeInto(subtitle, 'ยินดีต้อนรับสู่พื้นที่ส่วนตัวของฉัน', SPEED_FAST, function () { // Show h1 container, then type both parts titleEl.style.opacity = '1'; // "สวัสดีครับ " — typed into a text node before the span const titlePrefix = 'สวัสดีครับ '; titleEl.textContent = ''; nameSpan.textContent = ''; titleEl.appendChild(nameSpan); // re-attach span let pi = 0; function typePrefix() { if (pi < titlePrefix.length) { titleEl.insertBefore(document.createTextNode(titlePrefix[pi++]), nameSpan); setTimeout(typePrefix, SPEED_MED); } else { // Now type the red name const nameText = 'ผมชื่อกุมภาพันธ์'; let ni = 0; function typeName() { if (ni < nameText.length) { nameSpan.textContent += nameText[ni++]; setTimeout(typeName, SPEED_FAST); } else { // profession typeInto(profession, 'นักศึกษาแผนกวิชาอิเล็กทรอนิกส์', SPEED_FAST, function () { // description fade in fadeIn(descEl, function () { // buttons fade in fadeIn(btnsEl); }); }); } } typeName(); } } typePrefix(); }); } // Expose so the cutscene exit can trigger it window.startHeroTypewriter = startHeroTypewriter; /* ==================== SELECT ELEMENTS ==================== */ const header = document.getElementById('header'); const navMenu = document.getElementById('nav-menu'); const navToggle = document.getElementById('nav-toggle'); const navLinks = document.querySelectorAll('.nav-link'); const sections = document.querySelectorAll('section[id]'); const contactForm = document.getElementById('contact-form'); const toastContainer = document.getElementById('toast-container'); /* ==================== MOBILE MENU TOGGLE ==================== */ if (navToggle) { navToggle.addEventListener('click', () => { navMenu.classList.toggle('show-menu'); navToggle.classList.toggle('toggle-active'); }); } // Close menu when clicking a nav link (both top and bottom) navLinks.forEach(link => { link.addEventListener('click', () => { navMenu.classList.remove('show-menu'); navToggle.classList.remove('toggle-active'); }); }); const bottomNavLinks = document.querySelectorAll('.bottom-nav-link'); bottomNavLinks.forEach(link => { link.addEventListener('click', () => { navMenu.classList.remove('show-menu'); navToggle.classList.remove('toggle-active'); }); }); /* ==================== STICKY HEADER ==================== */ function stickyHeader() { if (window.scrollY >= 50) { header.classList.add('scroll-header'); header.style.padding = '0.2rem 0'; header.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)'; } else { header.classList.remove('scroll-header'); header.style.padding = '0'; header.style.boxShadow = 'none'; } } window.addEventListener('scroll', stickyHeader); /* ==================== SCROLL SECTIONS ACTIVE LINK ==================== */ function scrollActive() { const scrollY = window.pageYOffset; sections.forEach(current => { const sectionHeight = current.offsetHeight; const sectionTop = current.offsetTop - 120; // offset to match header const sectionId = current.getAttribute('id'); if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { document.querySelector('.nav-menu a[href*=' + sectionId + ']')?.classList.add('active-link'); document.querySelector('.bottom-nav a[href*=' + sectionId + ']')?.classList.add('active-link'); } else { document.querySelector('.nav-menu a[href*=' + sectionId + ']')?.classList.remove('active-link'); document.querySelector('.bottom-nav a[href*=' + sectionId + ']')?.classList.remove('active-link'); } }); } window.addEventListener('scroll', scrollActive); /* ==================== TOAST NOTIFICATION FUNCTION ==================== */ function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `toast toast-${type}`; // Add icon and message toast.innerHTML = ` <i class="fa-solid fa-circle-check"></i> <span>${message}</span> `; toastContainer.appendChild(toast); // Trigger slide in setTimeout(() => { toast.classList.add('show'); }, 10); // Auto remove after 4 seconds setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { toast.remove(); }, 400); // match transition speed }, 4000); } /* ==================== CONTACT FORM SUBMISSION ==================== */ if (contactForm) { contactForm.addEventListener('submit', (e) => { e.preventDefault(); // Collect form data const nameVal = document.getElementById('name').value; const emailVal = document.getElementById('email').value; const messageVal = document.getElementById('message').value; // Submit Button animation state const submitBtn = contactForm.querySelector('.submit-btn'); const originalBtnHTML = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.innerHTML = `<span>กำลังส่งข้อความ...</span> <i class="fa-solid fa-spinner fa-spin"></i>`; // Simulate API post request delay (1.5 seconds) setTimeout(() => { // Show custom success toast showToast(`สวัสดีครับคุณ ${nameVal}! ข้อความของคุณส่งสำเร็จแล้ว ผมจะติดต่อกลับโดยเร็วที่สุดครับ`); // Reset form contactForm.reset(); // Restore button state submitBtn.disabled = false; submitBtn.innerHTML = originalBtnHTML; }, 1500); }); } /* ==================== DARK / LIGHT THEME TOGGLING ==================== */ const themeToggleBtn = document.getElementById('theme-toggle-btn'); const headerThemeToggle = document.getElementById('header-theme-toggle'); // Helper function to update theme icons function updateThemeIcons(isLight) { const iconClass = isLight ? 'fa-sun' : 'fa-moon'; if (themeToggleBtn) { themeToggleBtn.innerHTML = `<i class="fa-solid ${iconClass}"></i>`; } if (headerThemeToggle) { headerThemeToggle.innerHTML = `<i class="fa-solid ${iconClass}"></i>`; } } // Initial theme check const savedTheme = localStorage.getItem('theme'); const prefersLight = window.matchMedia('(prefers-color-scheme: light)').matches; if (savedTheme === 'light' || (!savedTheme && prefersLight)) { document.body.classList.add('light-mode'); updateThemeIcons(true); } else { document.body.classList.remove('light-mode'); updateThemeIcons(false); } // Theme toggler logic function toggleTheme() { document.body.classList.toggle('light-mode'); const isLight = document.body.classList.contains('light-mode'); localStorage.setItem('theme', isLight ? 'light' : 'dark'); updateThemeIcons(isLight); // Show status toast showToast(isLight ? 'เปิดโหมดสว่าง (Light Mode)' : 'เปิดโหมดมืด (Dark Mode)'); } if (themeToggleBtn) { themeToggleBtn.addEventListener('click', toggleTheme); } if (headerThemeToggle) { headerThemeToggle.addEventListener('click', toggleTheme); } /* ==================== TYPEWRITER & MOVING TEXT CONTROLLER ==================== */ function initTypewriterAndMovingText() { // 1. Logo Animation (Gumphaphan.dev) const logoEl = document.querySelector('.logo'); if (logoEl && !logoEl.dataset.initialized) { logoEl.dataset.initialized = 'true'; setupMovingLogo(logoEl); } // 2. Section Titles & Subtitles (Intersection Observer) const animElements = document.querySelectorAll('.section-title, .section-subtitle'); const observerOptions = { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const el = entry.target; if (!el.dataset.typed) { el.dataset.typed = 'true'; typeAndFloatText(el); } } }); }, observerOptions); animElements.forEach(el => { if (!el.dataset.originalText) { el.dataset.originalText = el.textContent.trim(); el.style.opacity = '0'; observer.observe(el); } }); } function typeAndFloatText(el) { const fullText = el.dataset.originalText || el.textContent.trim(); el.innerHTML = ''; el.style.opacity = '1'; const isSubtitle = el.classList.contains('section-subtitle'); const textContainer = document.createElement('span'); textContainer.className = isSubtitle ? 'clean-subtitle-container' : 'moving-title-container'; const cursor = document.createElement('span'); cursor.className = 'type-cursor'; el.appendChild(textContainer); el.appendChild(cursor); let charIdx = 0; const speed = isSubtitle ? 25 : 45; // ms per character typing function typeNextChar() { if (charIdx < fullText.length) { charIdx++; if (isSubtitle) { // Type intact Thai text so vowels and tone marks render perfectly with full readability textContainer.textContent = fullText.slice(0, charIdx); } else { const char = fullText[charIdx - 1]; const charSpan = document.createElement('span'); charSpan.className = 'floating-char'; charSpan.textContent = char === ' ' ? '\u00A0' : char; const delay = ((charIdx - 1) % 10) * 0.12; const duration = 3.0 + ((charIdx - 1) % 3) * 0.3; charSpan.style.animationDelay = `${delay.toFixed(2)}s`; charSpan.style.animationDuration = `${duration.toFixed(2)}s`; textContainer.appendChild(charSpan); } setTimeout(typeNextChar, speed); } else { el.classList.add('typed-done'); setTimeout(() => { cursor.style.transition = 'opacity 0.6s ease'; cursor.style.opacity = '0'; setTimeout(() => cursor.remove(), 600); }, 1000); } } typeNextChar(); } function setupMovingLogo(logoEl) { const brandPart = "Gumphaphan"; const extPart = ".dev"; logoEl.innerHTML = ''; const brandContainer = document.createElement('span'); brandContainer.className = 'logo-brand-moving'; for (let i = 0; i < brandPart.length; i++) { const span = document.createElement('span'); span.className = 'floating-char'; span.textContent = brandPart[i]; span.style.animationDelay = `${(i * 0.12).toFixed(2)}s`; brandContainer.appendChild(span); } const extContainer = document.createElement('span'); extContainer.className = 'logo-ext-moving'; for (let i = 0; i < extPart.length; i++) { const span = document.createElement('span'); span.className = 'floating-char-ext'; span.textContent = extPart[i]; span.style.animationDelay = `${((brandPart.length + i) * 0.12).toFixed(2)}s`; extContainer.appendChild(span); } logoEl.appendChild(brandContainer); logoEl.appendChild(extContainer); } // Auto init when document is loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTypewriterAndMovingText); } else { initTypewriterAndMovingText(); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings