// SC AccountPro — Login Screen // Glass-morphism card, animated AI background, focus glow, logo shimmer, dark mode. const { useState, useEffect, useRef, useMemo } = React; // ───────────────────────────────────────────────────────────── // Theme palettes — keyed by accent + mode // ───────────────────────────────────────────────────────────── const PALETTES = { navy: { light: { bgFrom: '#0a2358', bgVia: '#0f3179', bgTo: '#1a4fa6', cardBg: 'rgba(255,255,255,0.92)', cardBorder: 'rgba(255,255,255,0.6)', text: '#0b1f4a', textMuted: '#4b5b7a', textSubtle: '#8693ad', inputBg: '#ffffff', inputBorder: '#e3e8f0', inputBorderFocus: '#2563eb', btnFrom: '#1d4ed8', btnTo: '#3b82f6', btnGlow: 'rgba(37,99,235,0.45)', net: 'rgba(120,170,255,0.35)', netGlow: '#5b9eff', gold: '#f4c14a', goldDeep: '#d99820', featTint: 'rgba(255,255,255,0.08)', featIcon: '#60a5fa', featText: 'rgba(255,255,255,0.78)', pillBg: 'rgba(10,25,60,0.55)', pillBorder: 'rgba(244,193,74,0.35)', pillText: '#ffd97a', footerText: 'rgba(255,255,255,0.55)', footerSub: 'rgba(255,255,255,0.4)' }, dark: { bgFrom: '#02060f', bgVia: '#050d22', bgTo: '#0a1a3a', cardBg: 'rgba(18,28,52,0.78)', cardBorder: 'rgba(120,160,230,0.18)', text: '#eaf0ff', textMuted: '#94a3c4', textSubtle: '#5d6c8c', inputBg: 'rgba(8,15,32,0.6)', inputBorder: 'rgba(120,160,230,0.18)', inputBorderFocus: '#60a5fa', btnFrom: '#1e40af', btnTo: '#3b82f6', btnGlow: 'rgba(96,165,250,0.45)', net: 'rgba(120,170,255,0.28)', netGlow: '#4f8eff', gold: '#f4c14a', goldDeep: '#d99820', featTint: 'rgba(255,255,255,0.04)', featIcon: '#7eb6ff', featText: 'rgba(220,232,255,0.7)', pillBg: 'rgba(8,18,42,0.7)', pillBorder: 'rgba(244,193,74,0.3)', pillText: '#ffd97a', footerText: 'rgba(220,232,255,0.45)', footerSub: 'rgba(220,232,255,0.32)' } }, emerald: { light: { bgFrom: '#052e2a', bgVia: '#0b4a3f', bgTo: '#11705c', cardBg: 'rgba(255,255,255,0.92)', cardBorder: 'rgba(255,255,255,0.6)', text: '#04261f', textMuted: '#3a5a52', textSubtle: '#7f9a92', inputBg: '#ffffff', inputBorder: '#dfeae5', inputBorderFocus: '#0d9488', btnFrom: '#0f766e', btnTo: '#14b8a6', btnGlow: 'rgba(20,184,166,0.45)', net: 'rgba(110,220,200,0.32)', netGlow: '#34d4bd', gold: '#f5cf6a', goldDeep: '#dba83a', featTint: 'rgba(255,255,255,0.08)', featIcon: '#5eead4', featText: 'rgba(255,255,255,0.78)', pillBg: 'rgba(4,30,28,0.55)', pillBorder: 'rgba(245,207,106,0.35)', pillText: '#ffe49a', footerText: 'rgba(255,255,255,0.55)', footerSub: 'rgba(255,255,255,0.4)' }, dark: { bgFrom: '#020f0e', bgVia: '#03201d', bgTo: '#053934', cardBg: 'rgba(14,32,30,0.78)', cardBorder: 'rgba(94,234,212,0.16)', text: '#e8fbf6', textMuted: '#8fbcb1', textSubtle: '#5a7e76', inputBg: 'rgba(4,18,16,0.6)', inputBorder: 'rgba(94,234,212,0.16)', inputBorderFocus: '#5eead4', btnFrom: '#0f766e', btnTo: '#2dd4bf', btnGlow: 'rgba(45,212,191,0.45)', net: 'rgba(110,220,200,0.28)', netGlow: '#34d4bd', gold: '#f5cf6a', goldDeep: '#dba83a', featTint: 'rgba(255,255,255,0.04)', featIcon: '#7eead4', featText: 'rgba(220,250,243,0.7)', pillBg: 'rgba(4,18,16,0.7)', pillBorder: 'rgba(245,207,106,0.3)', pillText: '#ffe49a', footerText: 'rgba(220,250,243,0.45)', footerSub: 'rgba(220,250,243,0.32)' } }, royal: { light: { bgFrom: '#1a0a47', bgVia: '#2d1372', bgTo: '#4f2bae', cardBg: 'rgba(255,255,255,0.92)', cardBorder: 'rgba(255,255,255,0.6)', text: '#1a0a47', textMuted: '#574a78', textSubtle: '#8e87a8', inputBg: '#ffffff', inputBorder: '#e6e3f0', inputBorderFocus: '#7c3aed', btnFrom: '#6d28d9', btnTo: '#8b5cf6', btnGlow: 'rgba(139,92,246,0.45)', net: 'rgba(170,140,255,0.32)', netGlow: '#9d7bff', gold: '#f4c14a', goldDeep: '#d99820', featTint: 'rgba(255,255,255,0.08)', featIcon: '#c4b5fd', featText: 'rgba(255,255,255,0.78)', pillBg: 'rgba(20,10,55,0.55)', pillBorder: 'rgba(244,193,74,0.35)', pillText: '#ffd97a', footerText: 'rgba(255,255,255,0.55)', footerSub: 'rgba(255,255,255,0.4)' }, dark: { bgFrom: '#0a0418', bgVia: '#150a35', bgTo: '#241354', cardBg: 'rgba(28,18,55,0.78)', cardBorder: 'rgba(196,181,253,0.16)', text: '#f3eeff', textMuted: '#b4a8d5', textSubtle: '#766b94', inputBg: 'rgba(12,6,30,0.6)', inputBorder: 'rgba(196,181,253,0.16)', inputBorderFocus: '#c4b5fd', btnFrom: '#5b21b6', btnTo: '#8b5cf6', btnGlow: 'rgba(139,92,246,0.45)', net: 'rgba(170,140,255,0.28)', netGlow: '#9d7bff', gold: '#f4c14a', goldDeep: '#d99820', featTint: 'rgba(255,255,255,0.04)', featIcon: '#c4b5fd', featText: 'rgba(243,238,255,0.7)', pillBg: 'rgba(12,6,30,0.7)', pillBorder: 'rgba(244,193,74,0.3)', pillText: '#ffd97a', footerText: 'rgba(243,238,255,0.45)', footerSub: 'rgba(243,238,255,0.32)' } } }; // ───────────────────────────────────────────────────────────── // Animated network background — buildings + nodes + flowing lines // ───────────────────────────────────────────────────────────── function NetworkBg({ p, speed = 1, paused = false, transparent = false }) { // Deterministic nodes const nodes = useMemo(() => { const seed = (s) => {let h = 2166136261;for (let i = 0; i < s.length; i++) h = Math.imul(h ^ s.charCodeAt(i), 16777619);return (h >>> 0) / 4294967295;}; return Array.from({ length: 22 }, (_, i) => ({ x: seed('x' + i) * 100, y: seed('y' + i) * 60 + (seed('yo' + i) > 0.5 ? 0 : 35), r: seed('r' + i) * 1.4 + 1.2, delay: seed('d' + i) * 6 })); }, []); // Connections (pairs of nodes within distance) const links = useMemo(() => { const out = []; for (let i = 0; i < nodes.length; i++) { for (let j = i + 1; j < nodes.length; j++) { const dx = nodes[i].x - nodes[j].x,dy = nodes[i].y - nodes[j].y; const d = Math.hypot(dx, dy); if (d > 8 && d < 24) out.push({ a: i, b: j, d, delay: (i * 7 + j * 13) % 12 }); } } return out.slice(0, 36); }, [nodes]); const dur = `${(14 / Math.max(0.2, speed)).toFixed(1)}s`; return (
{/* gradient base — skipped when transparent so the page bg shows through */} {!transparent && (
)} {/* soft top glow */}
{/* bottom horizon glow */}
{/* Network SVG */} {/* static thin links */} {links.map((l, i) => { const a = nodes[l.a],b = nodes[l.b]; return ( ); })} {/* flowing pulses along links */} {links.slice(0, 18).map((l, i) => { const a = nodes[l.a],b = nodes[l.b]; return ( ); })} {/* nodes */} {nodes.map((n, i) => )} {/* Building silhouettes — left */} {/* buildings */} {/* windows — twinkle */} {Array.from({ length: 70 }).map((_, i) => { const bx = [12, 54, 102, 140][i % 4]; const bw = [38, 44, 34, 40][i % 4]; const col = Math.floor(i / 14); const row = i % 14; return ( ); })} {/* mirrored right side — graph chart */} {/* bar chart silhouette */} {/* trend line */} {[[30, 290], [60, 250], [90, 210], [120, 170], [150, 130], [175, 90]].map(([x, y], i) => )} {/* gold diagonal accents */}
); } // ───────────────────────────────────────────────────────────── // SC AccountPro — fully native SVG brand mark // Geometric SC monogram + neural circuit mesh + animated nodes, // ACCOUNTPRO wordmark, gold accent rule, AI badge. // No PNG — everything is animatable SVG/CSS. // ───────────────────────────────────────────────────────────── function SCLogo({ p, paused = false, font = 'Bodoni Moda' }) { const NAVY = '#0e1f44'; const NAVY_MID = '#1d3766'; const NAVY_LT = '#3a5e9e'; const GOLD = p.gold || '#e8a834'; const GOLD_DK = p.goldDeep || '#b87d12'; const CYAN = '#7eb6ff'; const BLUE = '#3b82f6'; // Per-font size/weight/letter-spacing tuning so different families sit nicely const fontTuning = { 'Bodoni Moda': { size: 105, weight: 900, ls: -5 }, 'Playfair Display': { size: 110, weight: 900, ls: -5 }, 'Cinzel': { size: 95, weight: 900, ls: -3 }, 'Cormorant Garamond': { size: 118, weight: 700, ls: -7 }, 'DM Serif Display': { size: 110, weight: 400, ls: -5 }, 'Abril Fatface': { size: 105, weight: 400, ls: -5 }, 'Yeseva One': { size: 108, weight: 400, ls: -4 }, 'Libre Bodoni': { size: 108, weight: 900, ls: -5 }, 'EB Garamond': { size: 118, weight: 700, ls: -7 }, 'Cardo': { size: 118, weight: 700, ls: -7 }, 'Spectral SC': { size: 92, weight: 800, ls: 0 }, 'Marcellus': { size: 108, weight: 400, ls: -3 }, 'Italiana': { size: 130, weight: 400, ls: -7 }, 'Inter': { size: 100, weight: 800, ls: -7 }, 'Bebas Neue': { size: 130, weight: 400, ls: 0 }, 'Oswald': { size: 118, weight: 700, ls: -3 }, 'Anton': { size: 122, weight: 400, ls: -3 }, 'Archivo Black': { size: 100, weight: 400, ls: -5 }, 'Russo One': { size: 100, weight: 400, ls: -3 }, 'Big Shoulders Display': { size: 130, weight: 900, ls: -5 }, 'Audiowide': { size: 92, weight: 400, ls: 0 }, 'Orbitron': { size: 95, weight: 900, ls: -3 }, 'Krona One': { size: 92, weight: 400, ls: 0 }, 'Michroma': { size: 76, weight: 400, ls: 0 }, 'Major Mono Display': { size: 92, weight: 400, ls: -3 }, 'Syncopate': { size: 84, weight: 700, ls: 0 }, 'Iceland': { size: 130, weight: 400, ls: -5 }, 'Wallpoet': { size: 100, weight: 400, ls: 0 }, 'Black Ops One': { size: 108, weight: 400, ls: -3 }, 'Bungee': { size: 92, weight: 400, ls: -3 }, 'Stalinist One': { size: 92, weight: 400, ls: -3 }, 'Limelight': { size: 102, weight: 400, ls: -3 }, 'Monoton': { size: 108, weight: 400, ls: -3 }, 'Faster One': { size: 118, weight: 400, ls: -5 }, 'Tangerine': { size: 170, weight: 700, ls: -5 }, 'Great Vibes': { size: 140, weight: 400, ls: -5 }, 'Allura': { size: 140, weight: 400, ls: -5 }, 'Pinyon Script': { size: 140, weight: 400, ls: -5 }, }; const ft = fontTuning[font] || fontTuning['Bodoni Moda']; const FS = ft.size; const FW = ft.weight; const LS = ft.ls; const FAM = `"${font}", "Bodoni Moda", "Playfair Display", serif`; // Hex grid pattern for the neural side const hexNodes = [ { x: 178, y: 70, r: 3.6, d: 0 }, { x: 198, y: 55, r: 2.4, d: 0.4 }, { x: 218, y: 75, r: 3, d: 0.8 }, { x: 195, y: 92, r: 2.6, d: 0.2 }, { x: 215, y: 110, r: 3.2, d: 1.0 }, { x: 190, y: 128, r: 2.4, d: 1.3 }, { x: 175, y: 110, r: 3, d: 0.6 }, { x: 230, y: 95, r: 2.2, d: 1.5 }, { x: 168, y: 88, r: 2.6, d: 1.8 }, ]; const hexEdges = [ [0,1],[1,2],[2,4],[4,5],[5,6],[6,0],[0,3],[3,1],[3,4],[3,6],[2,7],[4,7],[0,8],[6,8],[3,8], ]; return (
{/* === Ambient glow — pure circular, no rectangular bounds === */}
{/* === Floating ambient particles === */} {[ [22, 18, 0],[42, 36, 0.6],[24, 165, 1.2],[58, 178, 0.3], [265, 24, 0.8],[252, 168, 1.4],[270, 92, 0.5],[12, 96, 1.6], ].map(([x,y,d], i) => ( ))} {/* === MAIN COMPOSITION === */} {/* Gold metallic — softened to blend with navy bg */} {/* Steel/silver for C — softer top so it doesn't pop against dark navy */} {/* Wordmark gradient */} {/* Cyan glow for nodes */} {/* Shine sweep */} {/* Drop shadow */} {/* Letter masks for shimmer overlay */} S C {/* ─── Decorative concentric hex rings on the right (AI side) ─── */} {/* ─── Neural mesh on the right (AI face area) ─── */} {/* edges */} {hexEdges.map(([a,b], i) => ( ))} {/* nodes */} {hexNodes.map((n, i) => ( ))} {/* AI badge removed */} {/* ─── S (gold 3D, in front) ─── */} {/* dark underlay for depth — soft + low contrast */} S {/* main gold */} S {/* shimmer over S — softened opacity to match ambient light */} {/* ─── C (silver/steel, behind on right) ─── */} C C {/* shimmer over C */} {/* ─── Gold pillar accent inside C (subtle building hint) ─── */} {/* ─── Gold accent rule under SC ─── */} {/* ─── ACCOUNTPRO wordmark ─── */} ACCOUNT PRO {/* ─── Thai tagline ─── */} ระบบบัญชี AI สำหรับสำนักงานบัญชีไทย {/* ─── Tiny side rules around tagline ─── */} {/* ─── Sparkles ─── */} {/* === Corner brackets removed for seamless blend === */} {/* === Diagonal shine sweep removed (no tile to shine on) === */}
); } // ───────────────────────────────────────────────────────────── // Inline icons // ───────────────────────────────────────────────────────────── const Icons = { shield: (c) => , mail: (c) => , lock: (c) => , eye: (c) => , eyeOff: (c) => , chip: (c) => {[3, 12, 21].map((y, i) => )} , doc: (c) => {/* Document outline */} {/* "AI" badge inside document (small filled box) */} AI , chart: (c) => {/* Bars — outline */} {/* Trend arrow up */} , shieldFeat: (c) => {/* Shield outline */} {/* Check mark */} , cloud: (c) => {/* Cloud outline */} }; // ───────────────────────────────────────────────────────────── // Login Screen // ───────────────────────────────────────────────────────────── function LoginScreen({ tweaks, animPaused }) { const p = PALETTES[tweaks.accent][tweaks.dark ? 'dark' : 'light']; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [showPw, setShowPw] = useState(false); const [remember, setRemember] = useState(true); const [focused, setFocused] = useState(null); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const [errors, setErrors] = useState({}); const [showForgot, setShowForgot] = useState(false); // ─── Load remembered email ตอน mount (Option A — Auto-fill email) ─── useEffect(() => { try { const savedEmail = localStorage.getItem('remember_email'); if (savedEmail) { setEmail(savedEmail); setRemember(true); } else { // ถ้าไม่มี · ค่า default remember = false (ไม่บังคับ) setRemember(false); } } catch (e) { /* localStorage ใช้ไม่ได้ — ignore */ } }, []); const blurAmt = tweaks.glass; // 0..24 const onSubmit = async (e) => { e.preventDefault(); const errs = {}; if (!email) errs.email = 'กรุณากรอกอีเมล'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = 'รูปแบบอีเมลไม่ถูกต้อง'; if (!password) errs.password = 'กรุณากรอกรหัสผ่าน'; else if (password.length < 6) errs.password = 'รหัสผ่านอย่างน้อย 6 ตัวอักษร'; setErrors(errs); if (Object.keys(errs).length) return; setLoading(true); try { if (!window.api) throw new Error('API client ไม่พร้อม — รีเฟรชหน้าใหม่'); await window.api.login(email, password); // ─── Save / clear remembered email ตามที่ ติ๊ก "จำฉันไว้" ─── try { if (remember) localStorage.setItem('remember_email', email); else localStorage.removeItem('remember_email'); } catch (e) { /* ignore */ } setLoading(false); setSuccess(true); setTimeout(() => { window.location.href = '/static/dashboard.html'; }, 600); } catch (err) { setLoading(false); setErrors({ password: err.message || 'เข้าสู่ระบบไม่สำเร็จ — ตรวจ email/password อีกครั้ง' }); } }; const inputStyle = (key) => ({ width: '100%', boxSizing: 'border-box', background: p.inputBg, border: `1.5px solid ${errors[key] ? '#ef4444' : focused === key ? p.inputBorderFocus : p.inputBorder}`, borderRadius: 14, padding: '12px 14px 12px 50px', fontSize: 15, color: p.text, outline: 'none', transition: 'all .18s ease', boxShadow: focused === key ? `0 0 0 4px ${p.inputBorderFocus}26, 0 2px 8px ${p.inputBorderFocus}15` : '0 1px 2px rgba(0,0,0,0.03)', fontFamily: 'inherit' }); return (
{tweaks.showFrame && ( )} {/* LINE webview header removed */} {/* Scrollable content */}
{/* Logo */} {/* AI POWERED pill */}
{Icons.chip('#0a2358')}
AI Powered Accounting
{/* Glass Login Card */}
{/* lock badge */}
{!success ? <>
เข้าสู่ระบบ
ยินดีต้อนรับสู่ SC AccountPro
{/* Email */} {/* Password */} {/* Remember / forgot */}
{ e.preventDefault(); setShowForgot(true); }} style={{ color: p.inputBorderFocus, fontWeight: 600, textDecoration: 'none', cursor: 'pointer' }}>ลืมรหัสผ่าน?
{/* Login button */} : // Success state
เข้าสู่ระบบสำเร็จ
กำลังพาคุณไปหน้า Dashboard...
}
{/* Feature row */}
{[ { icon: '📄', label: 'จัดการเอกสาร', sub: 'อัตโนมัติด้วย AI' }, { icon: '📊', label: 'วิเคราะห์ข้อมูล', sub: 'เชิงลึก' }, { icon: '🛡️', label: 'ปลอดภัย', sub: 'ระดับองค์กร' }, { icon: '☁️', label: 'ทำงานได้ทุกที่', sub: 'ทุกเวลา' } ].map((f, i) =>
{f.icon}
{f.label}
{f.sub}
)}
{/* Footer */}
© 2026 SC AccountPro · v1.0.0
Professional Accounting AI System for Thai Accountants
{/* ─── Forgot Password Modal ─────────────────────── */} {showForgot && (
setShowForgot(false)} style={{ position: 'fixed', inset: 0, zIndex: 9999, background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, animation: 'fadeIn .15s ease' }}>
e.stopPropagation()} style={{ background: '#fff', borderRadius: 18, maxWidth: 440, width: '100%', padding: 28, boxShadow: '0 24px 80px rgba(0,0,0,0.4)', fontFamily: 'inherit' }}>
🔐

ลืมรหัสผ่าน?

วิธี reset รหัสผ่านของคุณ

📞 ติดต่อ Owner (ผู้ดูแลระบบ)
แจ้ง Owner เพื่อ reset รหัสผ่านให้คุณ · Owner สามารถออกรหัสผ่านชั่วคราวให้ผ่านหน้า "จัดการผู้ใช้ (User Admin)" ได้
ขั้นตอน:
  1. ติดต่อ Owner / ผู้ดูแลระบบ
  2. Owner reset password ในหน้า User Admin
  3. รับรหัสผ่านชั่วคราวจาก Owner
  4. Login → ระบบจะให้ตั้งรหัสผ่านใหม่ทันที
🛡️ ระบบ reset ผ่าน Owner เท่านั้น เพื่อความปลอดภัยสูงสุด
(Email reset link จะมีในอัพเดตหน้า)
)}
); } // Export to window so App in login.html can render Object.assign(window, { LoginScreen, PALETTES });