From 0b295749bb6a3fa88e6f32cd9a71be277b2a8ab9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:01:56 +0000 Subject: [PATCH 1/3] Initial plan From 8fd0ac80c7f3649b4bbdb7d6a6418ee025a4f9e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:11:25 +0000 Subject: [PATCH 2/3] Implement Magic Login and Group Selection with beautiful UI Co-authored-by: jgarise <90566860+jgarise@users.noreply.github.com> --- assets/css/animations.css | 359 ++++++++++++++++++++ assets/css/components.css | 452 +++++++++++++++++++++++++ assets/css/main.css | 628 +++++++++++++++++++++++++++++++++++ assets/js/auth.js | 285 ++++++++++++++++ assets/js/group-selection.js | 264 +++++++++++++++ assets/js/utils.js | 381 +++++++++++++++++++++ index.html | 55 +++ select-group.html | 53 +++ 8 files changed, 2477 insertions(+) create mode 100644 assets/css/animations.css create mode 100644 assets/css/components.css create mode 100644 assets/css/main.css create mode 100644 assets/js/auth.js create mode 100644 assets/js/group-selection.js create mode 100644 assets/js/utils.js create mode 100644 index.html create mode 100644 select-group.html diff --git a/assets/css/animations.css b/assets/css/animations.css new file mode 100644 index 0000000..416b18c --- /dev/null +++ b/assets/css/animations.css @@ -0,0 +1,359 @@ +/* ð Animation Library */ +/* Smooth, elegant animations for enhanced user experience */ + +/* === KEYFRAME ANIMATIONS === */ + +/* Gradient Background Animation */ +@keyframes gradientShift { + 0%, 100% { + transform: translateX(0) translateY(0) rotate(0deg); + opacity: 1; + } + 25% { + transform: translateX(5%) translateY(-5%) rotate(1deg); + opacity: 0.8; + } + 50% { + transform: translateX(-3%) translateY(3%) rotate(-0.5deg); + opacity: 0.9; + } + 75% { + transform: translateX(2%) translateY(-2%) rotate(0.5deg); + opacity: 0.85; + } +} + +/* Elegant Spinner */ +@keyframes elegantSpin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Pulse Animation */ +@keyframes pulse { + 0%, 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.05); + opacity: 0.8; + } +} + +/* Bounce In Animation */ +@keyframes bounceIn { + 0% { + transform: scale(0.3) translateY(-50px); + opacity: 0; + } + 50% { + transform: scale(1.05) translateY(0); + opacity: 0.8; + } + 70% { + transform: scale(0.9); + opacity: 0.9; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +/* Fade In Up */ +@keyframes fadeInUp { + 0% { + transform: translateY(30px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Slide In Right */ +@keyframes slideInRight { + 0% { + transform: translateX(100%); + opacity: 0; + } + 100% { + transform: translateX(0); + opacity: 1; + } +} + +/* Scale In */ +@keyframes scaleIn { + 0% { + transform: scale(0); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +/* Float Animation */ +@keyframes float { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } +} + +/* Progress Bar Fill */ +@keyframes progressFill { + 0% { + width: 0%; + } + 100% { + width: var(--progress-width, 0%); + } +} + +/* Shimmer Effect */ +@keyframes shimmer { + 0% { + background-position: -200px 0; + } + 100% { + background-position: calc(200px + 100%) 0; + } +} + +/* === ANIMATION CLASSES === */ + +/* Loading Spinner */ +.elegant-spinner { + width: 40px; + height: 40px; + border: 3px solid rgba(255, 255, 255, 0.3); + border-top: 3px solid var(--white); + border-radius: 50%; + animation: elegantSpin 1s linear infinite; +} + +/* Large Spinner for Pages */ +.spinner-large { + width: 60px; + height: 60px; + border-width: 4px; +} + +/* Bounce In Animation */ +.animate-bounce-in { + animation: bounceIn 0.6s ease-out; +} + +/* Fade In Up */ +.animate-fade-in-up { + animation: fadeInUp 0.5s ease-out; +} + +/* Slide In Right */ +.animate-slide-in-right { + animation: slideInRight 0.5s ease-out; +} + +/* Scale In */ +.animate-scale-in { + animation: scaleIn 0.3s ease-out; +} + +/* Float */ +.animate-float { + animation: float 3s ease-in-out infinite; +} + +/* Pulse */ +.animate-pulse { + animation: pulse 2s ease-in-out infinite; +} + +/* === HOVER EFFECTS === */ + +/* Card Hover Effect */ +.card-hover { + transition: all var(--transition-normal); + cursor: pointer; +} + +.card-hover:hover { + transform: translateY(-8px) scale(1.02); + box-shadow: var(--shadow-heavy); +} + +/* 3D Card Effect */ +.card-3d { + perspective: 1000px; + transition: all var(--transition-normal); +} + +.card-3d:hover { + transform: rotateY(5deg) rotateX(5deg) translateY(-10px); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.15), + 0 10px 20px rgba(102, 126, 234, 0.1); +} + +/* Button Hover Glow */ +.button-glow:hover { + box-shadow: + 0 0 20px rgba(102, 126, 234, 0.4), + 0 5px 15px rgba(0, 0, 0, 0.1); +} + +/* === PROGRESS ANIMATIONS === */ + +/* Animated Progress Bar */ +.progress-animated { + overflow: hidden; + position: relative; +} + +.progress-animated::after { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.4) 50%, + transparent 100% + ); + width: 50px; + animation: shimmer 2s ease-in-out infinite; +} + +/* Progress Bar Fill Animation */ +.progress-fill { + transition: width 0.8s ease-out; + animation: progressFill 1.5s ease-out; +} + +/* === STATE ANIMATIONS === */ + +/* Success State */ +.animate-success { + animation: bounceIn 0.6s ease-out, pulse 2s ease-in-out 0.6s; +} + +/* Error State */ +.animate-error { + animation: pulse 0.3s ease-in-out 3; + border-color: #ff4757 !important; +} + +/* Loading State */ +.animate-loading { + animation: pulse 1.5s ease-in-out infinite; +} + +/* === ENTRANCE ANIMATIONS === */ + +/* Stagger Animation for Lists */ +.stagger-children > * { + opacity: 0; + transform: translateY(20px); + animation: fadeInUp 0.5s ease-out forwards; +} + +.stagger-children > *:nth-child(1) { animation-delay: 0.1s; } +.stagger-children > *:nth-child(2) { animation-delay: 0.2s; } +.stagger-children > *:nth-child(3) { animation-delay: 0.3s; } +.stagger-children > *:nth-child(4) { animation-delay: 0.4s; } +.stagger-children > *:nth-child(5) { animation-delay: 0.5s; } +.stagger-children > *:nth-child(6) { animation-delay: 0.6s; } + +/* === UTILITY ANIMATIONS === */ + +/* Delay Classes */ +.delay-100 { animation-delay: 0.1s; } +.delay-200 { animation-delay: 0.2s; } +.delay-300 { animation-delay: 0.3s; } +.delay-500 { animation-delay: 0.5s; } +.delay-700 { animation-delay: 0.7s; } +.delay-1000 { animation-delay: 1s; } + +/* Duration Classes */ +.duration-fast { animation-duration: 0.2s; } +.duration-normal { animation-duration: 0.5s; } +.duration-slow { animation-duration: 1s; } + +/* === MICRO-INTERACTIONS === */ + +/* Input Focus Animation */ +.input-focus-animation { + position: relative; + overflow: hidden; +} + +.input-focus-animation::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background: var(--primary-gradient); + transition: all var(--transition-normal); + transform: translateX(-50%); +} + +.input-focus-animation:focus-within::after { + width: 100%; +} + +/* Button Click Animation */ +.button-click { + position: relative; + overflow: hidden; +} + +.button-click::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: all var(--transition-fast); +} + +.button-click:active::before { + width: 200px; + height: 200px; +} + +/* === REDUCED MOTION === */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + .animate-pulse, + .animate-float, + .animated-background::before { + animation: none !important; + } +} \ No newline at end of file diff --git a/assets/css/components.css b/assets/css/components.css new file mode 100644 index 0000000..6a8460a --- /dev/null +++ b/assets/css/components.css @@ -0,0 +1,452 @@ +/* ð§Đ Reusable Components */ +/* Consistent, flexible components for the Group Mission Workflow */ + +/* === CARD COMPONENTS === */ + +/* Base Card */ +.card { + background: var(--white); + border-radius: var(--radius-xl); + padding: var(--space-xl); + box-shadow: var(--shadow-light); + border: 1px solid rgba(0, 0, 0, 0.05); + transition: all var(--transition-normal); +} + +/* Glass Card */ +.card-glass { + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +/* Gradient Cards */ +.card-primary { + background: var(--primary-gradient); + color: var(--white); +} + +.card-secondary { + background: var(--secondary-gradient); + color: var(--white); +} + +.card-success { + background: var(--success-gradient); + color: var(--white); +} + +.card-warning { + background: var(--warning-gradient); + color: var(--white); +} + +/* Card Hover States */ +.card-interactive { + cursor: pointer; + transition: all var(--transition-normal); +} + +.card-interactive:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-medium); +} + +/* === BUTTON COMPONENTS === */ + +/* Base Button */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-md) var(--space-lg); + font-size: var(--font-size-base); + font-weight: 600; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-normal); + text-decoration: none; + position: relative; + overflow: hidden; +} + +/* Button Variants */ +.btn-primary { + background: var(--primary-gradient); + color: var(--white); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +.btn-secondary { + background: var(--secondary-gradient); + color: var(--white); +} + +.btn-secondary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(255, 106, 136, 0.3); +} + +.btn-success { + background: var(--success-gradient); + color: var(--white); +} + +.btn-success:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(17, 153, 142, 0.3); +} + +.btn-outline { + background: transparent; + color: var(--primary-blue); + border: 2px solid var(--primary-blue); +} + +.btn-outline:hover { + background: var(--primary-blue); + color: var(--white); +} + +/* Button Sizes */ +.btn-sm { + padding: var(--space-sm) var(--space-md); + font-size: var(--font-size-sm); +} + +.btn-lg { + padding: var(--space-lg) var(--space-xl); + font-size: var(--font-size-lg); +} + +.btn-xl { + padding: var(--space-xl) var(--space-2xl); + font-size: var(--font-size-xl); +} + +/* Full Width Button */ +.btn-full { + width: 100%; +} + +/* === FORM COMPONENTS === */ + +/* Input Group */ +.form-group { + margin-bottom: var(--space-lg); +} + +.form-label { + display: block; + margin-bottom: var(--space-sm); + font-weight: 600; + color: var(--dark-gray); +} + +.form-input { + width: 100%; + padding: var(--space-md); + font-size: var(--font-size-base); + border: 2px solid rgba(0, 0, 0, 0.1); + border-radius: var(--radius-md); + transition: all var(--transition-normal); + background: var(--white); +} + +.form-input:focus { + outline: none; + border-color: var(--primary-blue); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.form-textarea { + min-height: 120px; + resize: vertical; +} + +.form-select { + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; +} + +/* === PROGRESS COMPONENTS === */ + +/* Progress Bar */ +.progress { + width: 100%; + height: 8px; + background: rgba(0, 0, 0, 0.1); + border-radius: var(--radius-sm); + overflow: hidden; + position: relative; +} + +.progress-bar { + height: 100%; + background: var(--primary-gradient); + transition: width 0.8s ease-out; + border-radius: var(--radius-sm); +} + +/* Colored Progress Bars */ +.progress-bar-success { + background: var(--success-gradient); +} + +.progress-bar-warning { + background: var(--warning-gradient); +} + +.progress-bar-danger { + background: var(--secondary-gradient); +} + +/* Large Progress Bar */ +.progress-lg { + height: 12px; +} + +.progress-xl { + height: 16px; +} + +/* === AVATAR COMPONENTS === */ + +.avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--primary-gradient); + display: flex; + align-items: center; + justify-content: center; + color: var(--white); + font-weight: 600; + font-size: var(--font-size-sm); +} + +.avatar-sm { + width: 32px; + height: 32px; + font-size: var(--font-size-xs); +} + +.avatar-lg { + width: 56px; + height: 56px; + font-size: var(--font-size-lg); +} + +.avatar-xl { + width: 72px; + height: 72px; + font-size: var(--font-size-xl); +} + +/* Avatar Group */ +.avatar-group { + display: flex; + align-items: center; +} + +.avatar-group .avatar { + margin-left: -0.5rem; + border: 2px solid var(--white); +} + +.avatar-group .avatar:first-child { + margin-left: 0; +} + +/* === BADGE COMPONENTS === */ + +.badge { + display: inline-flex; + align-items: center; + padding: var(--space-xs) var(--space-sm); + font-size: var(--font-size-xs); + font-weight: 600; + border-radius: var(--radius-sm); + text-transform: uppercase; + letter-spacing: 0.025em; +} + +.badge-primary { + background: var(--primary-blue); + color: var(--white); +} + +.badge-success { + background: var(--success-teal); + color: var(--white); +} + +.badge-warning { + background: var(--warning-yellow); + color: var(--dark-gray); +} + +.badge-danger { + background: var(--secondary-pink); + color: var(--white); +} + +/* === ALERT COMPONENTS === */ + +.alert { + padding: var(--space-lg); + border-radius: var(--radius-md); + margin-bottom: var(--space-lg); + border-left: 4px solid; +} + +.alert-info { + background: rgba(102, 126, 234, 0.1); + border-color: var(--primary-blue); + color: var(--primary-purple); +} + +.alert-success { + background: rgba(17, 153, 142, 0.1); + border-color: var(--success-teal); + color: var(--success-teal); +} + +.alert-warning { + background: rgba(255, 210, 0, 0.1); + border-color: var(--warning-yellow); + color: var(--warning-orange); +} + +.alert-danger { + background: rgba(255, 106, 136, 0.1); + border-color: var(--secondary-pink); + color: var(--secondary-pink); +} + +/* === MODAL COMPONENTS === */ + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 0; + visibility: hidden; + transition: all var(--transition-normal); +} + +.modal-overlay.active { + opacity: 1; + visibility: visible; +} + +.modal { + background: var(--white); + border-radius: var(--radius-xl); + padding: var(--space-2xl); + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + transform: scale(0.9) translateY(20px); + transition: all var(--transition-normal); +} + +.modal-overlay.active .modal { + transform: scale(1) translateY(0); +} + +.modal-header { + margin-bottom: var(--space-lg); + padding-bottom: var(--space-lg); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); +} + +.modal-title { + font-size: var(--font-size-xl); + font-weight: 700; + color: var(--dark-gray); + margin: 0; +} + +.modal-body { + margin-bottom: var(--space-lg); +} + +.modal-footer { + display: flex; + gap: var(--space-md); + justify-content: flex-end; +} + +/* === UTILITY COMPONENTS === */ + +/* Loading Spinner */ +.spinner { + width: 24px; + height: 24px; + border: 2px solid rgba(0, 0, 0, 0.1); + border-top: 2px solid var(--primary-blue); + border-radius: 50%; + animation: elegantSpin 1s linear infinite; +} + +/* Divider */ +.divider { + height: 1px; + background: rgba(0, 0, 0, 0.1); + margin: var(--space-lg) 0; +} + +/* Text Utilities */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.text-sm { font-size: var(--font-size-sm); } +.text-base { font-size: var(--font-size-base); } +.text-lg { font-size: var(--font-size-lg); } +.text-xl { font-size: var(--font-size-xl); } + +.font-light { font-weight: 300; } +.font-normal { font-weight: 400; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-bold { font-weight: 700; } + +/* Color Utilities */ +.text-primary { color: var(--primary-blue); } +.text-success { color: var(--success-teal); } +.text-warning { color: var(--warning-orange); } +.text-danger { color: var(--secondary-pink); } +.text-muted { color: var(--medium-gray); } + +/* Spacing Utilities */ +.m-0 { margin: 0; } +.mb-sm { margin-bottom: var(--space-sm); } +.mb-md { margin-bottom: var(--space-md); } +.mb-lg { margin-bottom: var(--space-lg); } +.mb-xl { margin-bottom: var(--space-xl); } + +.p-0 { padding: 0; } +.p-sm { padding: var(--space-sm); } +.p-md { padding: var(--space-md); } +.p-lg { padding: var(--space-lg); } +.p-xl { padding: var(--space-xl); } \ No newline at end of file diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..ef52359 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,628 @@ +/* ðĻ Group Mission Workflow - Main Styles */ +/* Design Philosophy: "Minimal Steps, Maximum Experience - āļŠāļ§āļĒ āđāļāđāļ āđāļāđāļāđāļēāļĒ āđāļĄāđāļāļąāļāļāđāļāļ" */ + +/* === GLOBAL RESET & VARIABLES === */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + /* ðĻ Color Palette */ + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --secondary-gradient: linear-gradient(135deg, #ff6a88 0%, #ff9a56 100%); + --success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); + --warning-gradient: linear-gradient(135deg, #ffd200 0%, #f7971e 100%); + + /* Individual Colors */ + --primary-blue: #667eea; + --primary-purple: #764ba2; + --secondary-pink: #ff6a88; + --secondary-orange: #ff9a56; + --success-teal: #11998e; + --success-green: #38ef7d; + --warning-yellow: #ffd200; + --warning-orange: #f7971e; + + /* Neutral Colors */ + --white: #ffffff; + --light-gray: #f8f9fa; + --medium-gray: #6c757d; + --dark-gray: #343a40; + --black: #000000; + + /* Shadows */ + --shadow-light: 0 2px 10px rgba(0, 0, 0, 0.1); + --shadow-medium: 0 4px 20px rgba(0, 0, 0, 0.15); + --shadow-heavy: 0 8px 30px rgba(0, 0, 0, 0.2); + --shadow-card: 0 10px 40px rgba(102, 126, 234, 0.15); + + /* Typography */ + --font-family: 'Segoe UI', 'Noto Sans Thai', Tahoma, Geneva, Verdana, sans-serif; + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 1.875rem; + --font-size-4xl: 2.25rem; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.5rem; + + /* Transitions */ + --transition-fast: 0.15s ease-out; + --transition-normal: 0.3s ease-out; + --transition-slow: 0.5s ease-out; +} + +/* === GLOBAL BASE STYLES === */ +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-family); + font-size: var(--font-size-base); + line-height: 1.6; + color: var(--dark-gray); + background: var(--light-gray); + overflow-x: hidden; +} + +/* === ANIMATED BACKGROUND === */ +.animated-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--primary-gradient); + z-index: -1; +} + +.animated-background::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(255, 106, 136, 0.3) 0%, transparent 50%), + radial-gradient(circle at 40% 40%, rgba(56, 239, 125, 0.2) 0%, transparent 50%); + animation: gradientShift 10s ease-in-out infinite; +} + +/* === LOGIN PAGE SPECIFIC === */ +.login-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-md); +} + +.login-container { + width: 100%; + max-width: 450px; + position: relative; + z-index: 1; +} + +.magic-login-card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: var(--radius-2xl); + padding: var(--space-3xl) var(--space-xl); + box-shadow: var(--shadow-card); + border: 1px solid rgba(255, 255, 255, 0.2); + transform: translateY(0); + transition: all var(--transition-normal); +} + +.magic-login-card:hover { + transform: translateY(-5px); + box-shadow: 0 20px 60px rgba(102, 126, 234, 0.25); +} + +.login-header { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.login-title { + font-size: var(--font-size-3xl); + font-weight: 700; + background: var(--primary-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: var(--space-sm); + line-height: 1.3; +} + +.login-subtitle { + font-size: var(--font-size-lg); + color: var(--medium-gray); + font-weight: 400; +} + +.login-form { + margin-bottom: var(--space-xl); +} + +.input-group { + position: relative; + margin-bottom: var(--space-xl); +} + +.magic-input { + width: 100%; + padding: var(--space-lg) var(--space-md); + font-size: var(--font-size-lg); + border: 2px solid rgba(102, 126, 234, 0.2); + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.9); + transition: all var(--transition-normal); + outline: none; + color: var(--dark-gray); +} + +.magic-input:focus { + border-color: var(--primary-blue); + background: var(--white); + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15); +} + +.magic-input::placeholder { + color: var(--medium-gray); + font-style: italic; +} + +.input-border { + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 3px; + background: var(--primary-gradient); + border-radius: var(--radius-sm); + transition: all var(--transition-normal); + transform: translateX(-50%); +} + +.magic-input:focus + .input-border { + width: 100%; +} + +.cta-button { + width: 100%; + padding: var(--space-lg) var(--space-xl); + font-size: var(--font-size-xl); + font-weight: 600; + color: var(--white); + background: var(--primary-gradient); + border: none; + border-radius: var(--radius-lg); + cursor: pointer; + position: relative; + overflow: hidden; + transition: all var(--transition-normal); + transform: translateY(0); +} + +.cta-button:hover { + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3); +} + +.cta-button:active { + transform: translateY(-1px); +} + +.button-text { + position: relative; + z-index: 2; +} + +.button-ripple { + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: all var(--transition-fast); + pointer-events: none; +} + +.cta-button:active .button-ripple { + width: 300px; + height: 300px; +} + +.login-footer { + text-align: center; +} + +.help-text { + font-size: var(--font-size-sm); + color: var(--medium-gray); + font-style: italic; +} + +/* === LOADING OVERLAY === */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 0; + visibility: hidden; + transition: all var(--transition-normal); +} + +.loading-overlay.active { + opacity: 1; + visibility: visible; +} + +.loading-text { + color: var(--white); + font-size: var(--font-size-lg); + margin-top: var(--space-lg); + font-weight: 500; +} + +/* === RESPONSIVE DESIGN === */ +/* Mobile (< 768px) */ +@media (max-width: 767px) { + .login-container { + max-width: 100%; + padding: 0 var(--space-md); + } + + .magic-login-card { + padding: var(--space-2xl) var(--space-lg); + } + + .login-title { + font-size: var(--font-size-2xl); + } + + .magic-input { + padding: var(--space-md); + font-size: var(--font-size-base); + } + + .cta-button { + padding: var(--space-md) var(--space-lg); + font-size: var(--font-size-lg); + } +} + +/* Tablet (768px - 1024px) */ +@media (min-width: 768px) and (max-width: 1024px) { + .login-container { + max-width: 400px; + } +} + +/* Desktop (> 1024px) */ +@media (min-width: 1025px) { + .magic-login-card { + padding: var(--space-3xl) var(--space-2xl); + } + + .login-title { + font-size: var(--font-size-4xl); + } +} + +/* === GROUP SELECTION PAGE === */ +.select-group-page { + min-height: 100vh; + background: var(--light-gray); +} + +.page-container { + max-width: 1200px; + margin: 0 auto; + padding: var(--space-lg); + position: relative; + z-index: 1; +} + +.page-header { + text-align: center; + margin-bottom: var(--space-3xl); + padding-top: var(--space-xl); +} + +.header-content { + max-width: 600px; + margin: 0 auto; +} + +.page-title { + font-size: var(--font-size-3xl); + font-weight: 700; + background: var(--primary-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: var(--space-md); +} + +.page-subtitle { + font-size: var(--font-size-lg); + color: var(--medium-gray); + margin-bottom: var(--space-xl); +} + +.user-info { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-md); + padding: var(--space-md) var(--space-lg); + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(10px); + border-radius: var(--radius-xl); + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: var(--shadow-light); + display: inline-flex; +} + +.user-name { + font-weight: 600; + color: var(--dark-gray); +} + +/* Groups Grid */ +.groups-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-xl); + margin-bottom: var(--space-3xl); +} + +.group-card { + background: var(--white); + border-radius: var(--radius-2xl); + overflow: hidden; + box-shadow: var(--shadow-medium); + transition: all var(--transition-normal); + position: relative; + cursor: pointer; +} + +.group-card:hover { + transform: translateY(-8px) scale(1.02); + box-shadow: var(--shadow-heavy); +} + +.card-inner { + padding: var(--space-xl); + height: 100%; + display: flex; + flex-direction: column; + background: var(--white); + position: relative; + overflow: hidden; +} + +.card-inner.card-primary { + background: var(--primary-gradient); + color: var(--white); +} + +.card-inner.card-secondary { + background: var(--secondary-gradient); + color: var(--white); +} + +.card-inner.card-success { + background: var(--success-gradient); + color: var(--white); +} + +.card-inner.card-warning { + background: var(--warning-gradient); + color: var(--white); +} + +.card-inner.card-gradient-special { + background: linear-gradient(135deg, #667eea 0%, #f093fb 100%); + color: var(--white); +} + +.card-header { + text-align: center; + margin-bottom: var(--space-lg); +} + +.group-icon { + font-size: var(--font-size-3xl); + margin-bottom: var(--space-sm); +} + +.group-name { + font-size: var(--font-size-xl); + font-weight: 700; + margin-bottom: var(--space-xs); +} + +.group-code { + font-size: var(--font-size-sm); + opacity: 0.8; + font-weight: 500; +} + +.card-body { + flex: 1; + margin-bottom: var(--space-lg); +} + +.member-info { + text-align: center; +} + +.member-count { + font-size: var(--font-size-2xl); + font-weight: 700; + margin-bottom: var(--space-md); +} + +.current { + color: inherit; +} + +.separator { + margin: 0 var(--space-xs); + opacity: 0.7; +} + +.max { + opacity: 0.8; +} + +.label { + font-size: var(--font-size-base); + margin-left: var(--space-xs); + opacity: 0.8; +} + +.progress { + margin: var(--space-md) 0; +} + +.status-text { + font-size: var(--font-size-sm); + font-weight: 600; +} + +.status-complete { + color: rgba(255, 255, 255, 0.9); +} + +.status-incomplete { + color: rgba(255, 255, 255, 0.8); +} + +.card-footer { + text-align: center; +} + +.btn-card { + width: 100%; + background: rgba(255, 255, 255, 0.2); + color: inherit; + border: 2px solid rgba(255, 255, 255, 0.3); + backdrop-filter: blur(10px); +} + +.btn-card:hover { + background: rgba(255, 255, 255, 0.3); + transform: translateY(-2px); +} + +.user-badge { + position: absolute; + top: var(--space-md); + right: var(--space-md); + background: rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); + padding: var(--space-xs) var(--space-sm); + border-radius: var(--radius-md); + font-size: var(--font-size-xs); + font-weight: 600; + color: rgba(255, 255, 255, 0.9); +} + +.user-group { + border: 3px solid rgba(255, 255, 255, 0.5); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3); +} + +/* Page Footer */ +.page-footer { + text-align: center; + padding-top: var(--space-xl); + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.footer-actions { + display: flex; + gap: var(--space-md); + justify-content: center; + flex-wrap: wrap; +} + +/* Member Count Display in Modal */ +.member-count-display { + font-size: var(--font-size-3xl); + font-weight: 700; + color: var(--primary-blue); + margin: var(--space-lg) 0; +} + +.member-count-display .count { + color: var(--secondary-pink); +} + +/* Responsive Design for Group Selection */ +@media (max-width: 767px) { + .groups-grid { + grid-template-columns: 1fr; + gap: var(--space-lg); + } + + .page-title { + font-size: var(--font-size-2xl); + } + + .footer-actions { + flex-direction: column; + align-items: center; + } + + .footer-actions .btn { + width: 100%; + max-width: 300px; + } +} + +@media (min-width: 768px) and (max-width: 1024px) { + .groups-grid { + grid-template-columns: repeat(2, 1fr); + } +} \ No newline at end of file diff --git a/assets/js/auth.js b/assets/js/auth.js new file mode 100644 index 0000000..acf0e2d --- /dev/null +++ b/assets/js/auth.js @@ -0,0 +1,285 @@ +/* ð Authentication Logic */ +/* Magic Login functionality for Group Mission Workflow */ + +class AuthManager { + constructor() { + this.form = null; + this.input = null; + this.submitButton = null; + this.init(); + } + + init() { + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.setupEventListeners()); + } else { + this.setupEventListeners(); + } + } + + setupEventListeners() { + this.form = document.getElementById('magicLoginForm'); + this.input = document.getElementById('accessCode'); + this.submitButton = this.form?.querySelector('button[type="submit"]'); + + if (!this.form || !this.input) { + console.error('Login form elements not found'); + return; + } + + // Form submission + this.form.addEventListener('submit', (e) => this.handleLogin(e)); + + // Input validation on typing + this.input.addEventListener('input', Utils.debounce((e) => this.validateInput(e), 300)); + + // Enter key support + this.input.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + this.handleLogin(e); + } + }); + + // Auto-focus input + this.input.focus(); + + // Check for existing session + this.checkExistingSession(); + } + + async handleLogin(event) { + event.preventDefault(); + + const accessCode = this.input.value.trim(); + + if (!accessCode) { + this.showInputError('āļāļĢāļļāļāļēāđāļŠāđāļĢāļŦāļąāļŠāđāļāđāļēāđāļāđāļāļēāļ'); + return; + } + + // Show loading + Utils.showLoading(true, 'āļāļģāļĨāļąāļāļāļĢāļ§āļāļŠāļāļāļĢāļŦāļąāļŠ...'); + this.setButtonLoading(true); + + try { + // Simulate API delay for better UX + await this.delay(1500); + + // Validate access code + const userData = Utils.validateAccessCode(accessCode); + + if (!userData) { + throw new Error('āļĢāļŦāļąāļŠāđāļĄāđāļāļđāļāļāđāļāļ āļāļĢāļļāļāļēāļāļĢāļ§āļāļŠāļāļāļāļĩāļāļāļĢāļąāđāļ'); + } + + // Save session + Utils.saveUserSession({ + ...userData, + loginTime: new Date().toISOString(), + sessionId: Utils.generateId() + }); + + // Success animation + this.showSuccessAnimation(); + + // Redirect based on user type + await this.delay(1000); + this.redirectUser(userData); + + } catch (error) { + Utils.showLoading(false); + this.setButtonLoading(false); + this.showInputError(error.message); + } + } + + validateInput(event) { + const value = event.target.value.trim(); + + // Clear previous error states + this.clearInputError(); + + if (value.length === 0) { + return; + } + + // Basic format validation + if (value.length < 6) { + this.showInputHint('āļĢāļŦāļąāļŠāļāđāļāļāļĄāļĩāļāļĒāđāļēāļāļāđāļāļĒ 6 āļāļąāļ§āļāļąāļāļĐāļĢ'); + return; + } + + // Check if it matches expected patterns + const isGroupCode = /^GROUP\d{3}$/i.test(value); + const isAdminCode = /^ADMIN\d{3}$/i.test(value); + + if (!isGroupCode && !isAdminCode) { + this.showInputHint('āļĢāļđāļāđāļāļ: GROUP001 āļŦāļĢāļ·āļ ADMIN001'); + return; + } + + // Show valid state + this.showInputValid(); + } + + showInputError(message) { + this.input.classList.add('animate-error'); + this.input.style.borderColor = '#ff4757'; + Utils.showAlert(message, 'danger', 4000); + + // Shake animation + setTimeout(() => { + this.input.classList.remove('animate-error'); + this.input.style.borderColor = ''; + }, 1000); + } + + showInputHint(message) { + // Create or update hint element + let hint = this.form.querySelector('.input-hint'); + if (!hint) { + hint = document.createElement('div'); + hint.className = 'input-hint'; + hint.style.cssText = ` + font-size: 0.875rem; + color: var(--medium-gray); + margin-top: 0.5rem; + font-style: italic; + opacity: 0; + transition: opacity 0.3s ease; + `; + this.input.parentElement.appendChild(hint); + } + + hint.textContent = message; + hint.style.opacity = '1'; + } + + showInputValid() { + this.input.style.borderColor = '#11998e'; + const hint = this.form.querySelector('.input-hint'); + if (hint) { + hint.style.opacity = '0'; + } + } + + clearInputError() { + this.input.style.borderColor = ''; + this.input.classList.remove('animate-error'); + const hint = this.form.querySelector('.input-hint'); + if (hint) { + hint.style.opacity = '0'; + } + } + + setButtonLoading(loading) { + if (!this.submitButton) return; + + const buttonText = this.submitButton.querySelector('.button-text'); + + if (loading) { + this.submitButton.disabled = true; + this.submitButton.style.opacity = '0.7'; + if (buttonText) { + buttonText.innerHTML = 'āļāļģāļĨāļąāļāđāļāđāļēāļŠāļđāđāļĢāļ°āļāļ...'; + } + } else { + this.submitButton.disabled = false; + this.submitButton.style.opacity = '1'; + if (buttonText) { + buttonText.textContent = 'āđāļāđāļēāļŠāļđāđāļĢāļ°āļāļ'; + } + } + } + + showSuccessAnimation() { + // Success feedback + this.input.style.borderColor = '#11998e'; + this.input.style.background = 'rgba(17, 153, 142, 0.1)'; + + const buttonText = this.submitButton.querySelector('.button-text'); + if (buttonText) { + buttonText.innerHTML = 'â āđāļāđāļēāļŠāļđāđāļĢāļ°āļāļāļŠāļģāđāļĢāđāļ!'; + } + + this.submitButton.style.background = 'var(--success-gradient)'; + + Utils.showAlert('āđāļāđāļēāļŠāļđāđāļĢāļ°āļāļāļŠāļģāđāļĢāđāļ! ð', 'success', 2000); + } + + redirectUser(userData) { + let targetPage; + + if (userData.type === 'admin') { + targetPage = 'admin-dashboard.html'; + } else if (userData.type === 'group') { + // Check if group has enough members + if (userData.data.members >= userData.data.maxMembers) { + targetPage = 'weekly-mission.html'; + } else { + targetPage = 'select-group.html'; + } + } + + if (targetPage) { + Utils.showLoading(true, 'āļāļģāļĨāļąāļāđāļŦāļĨāļāļŦāļāđāļēāļāļąāļāđāļ...'); + setTimeout(() => { + window.location.href = targetPage; + }, 500); + } + } + + checkExistingSession() { + const session = Utils.getUserSession(); + if (session) { + // Check if session is still valid (within 24 hours) + const loginTime = new Date(session.loginTime); + const now = new Date(); + const hoursPassed = (now - loginTime) / (1000 * 60 * 60); + + if (hoursPassed < 24) { + // Show option to continue or login again + Utils.showModal( + 'āļāļāđāļāļŠāļāļąāļāļāļēāļĢāđāļāđāļēāđāļāđāļāļēāļ', + `
āļāļļāļāđāļāļĒāđāļāđāļēāđāļāđāļāļēāļāļāđāļ§āļĒ ${session.data.name} āđāļĨāđāļ§
+āļāđāļāļāļāļēāļĢāļāļģāđāļāļīāļāļāļēāļĢāļāđāļāļŦāļĢāļ·āļāđāļāđāļēāđāļāđāļāļēāļāđāļŦāļĄāđ?
`, + [ + { + text: 'āđāļāđāļēāđāļāđāļāļēāļāđāļŦāļĄāđ', + class: 'btn-outline', + onClick: () => { + Utils.clearUserSession(); + Utils.closeModal(document.querySelector('.modal-overlay')); + } + }, + { + text: 'āļāļģāđāļāļīāļāļāļēāļĢāļāđāļ', + class: 'btn-primary', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + this.redirectUser(session); + } + } + ] + ); + } else { + // Clear expired session + Utils.clearUserSession(); + } + } + } + + delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +// Initialize authentication manager +document.addEventListener('DOMContentLoaded', () => { + new AuthManager(); +}); + +// Export for testing +window.AuthManager = AuthManager; \ No newline at end of file diff --git a/assets/js/group-selection.js b/assets/js/group-selection.js new file mode 100644 index 0000000..050d06f --- /dev/null +++ b/assets/js/group-selection.js @@ -0,0 +1,264 @@ +/* ðŊ Group Selection Logic */ +/* 3D Card selection with validation for Group Mission Workflow */ + +class GroupSelectionManager { + constructor() { + this.userSession = null; + this.groupsData = Utils.APP_CONFIG.GROUP_CODES; + this.init(); + } + + init() { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.setupPage()); + } else { + this.setupPage(); + } + } + + setupPage() { + // Check authentication + this.userSession = Utils.getUserSession(); + if (!this.userSession) { + window.location.href = 'index.html'; + return; + } + + this.setupUserInfo(); + this.renderGroupCards(); + this.setupEventListeners(); + } + + setupUserInfo() { + const userAvatar = document.getElementById('userAvatar'); + const userName = document.getElementById('userName'); + + if (userAvatar && userName && this.userSession) { + // Set avatar with first letter of group name or admin symbol + if (this.userSession.type === 'group') { + const groupName = this.userSession.data.name; + userAvatar.textContent = groupName.charAt(0); + userName.textContent = groupName; + } else { + userAvatar.textContent = 'ð'; + userName.textContent = this.userSession.data.name; + } + } + } + + renderGroupCards() { + const grid = document.getElementById('groupsGrid'); + if (!grid) return; + + grid.innerHTML = ''; + + Object.entries(this.groupsData).forEach(([code, data], index) => { + const card = this.createGroupCard(code, data, index); + grid.appendChild(card); + }); + } + + createGroupCard(code, data, index) { + const progress = (data.members / data.maxMembers) * 100; + const isComplete = data.members >= data.maxMembers; + const isUserGroup = this.userSession.code === code; + + const card = document.createElement('div'); + card.className = `group-card card-3d card-interactive ${isUserGroup ? 'user-group' : ''} ${isComplete ? 'complete' : 'incomplete'}`; + card.dataset.groupCode = code; + card.style.animationDelay = `${index * 0.1}s`; + + // Determine gradient class based on index + const gradientClasses = ['card-primary', 'card-secondary', 'card-success', 'card-warning', 'card-gradient-special']; + const gradientClass = gradientClasses[index % gradientClasses.length]; + + card.innerHTML = ` +āļāđāļāļāļāļĢāļ°āļāļēāļĻāļāđāļēāļ§āļāļĢāļ°āđāļŠāļĢāļīāļāđāļŦāđāļāļĢāļ 5 āļāļāļāđāļāļāđāļāđāļāļāļ·āđāļ
+āļāđāļāļāļāļēāļĢāļāļĩāļ ${groupData.maxMembers - groupData.members} āļāļ
+āļāļļāļāļāđāļāļāļāļēāļĢāļāļāļāļāļēāļāļĢāļ°āļāļāđāļāđāļŦāļĢāļ·āļāđāļĄāđ?
', + [ + { + text: 'āļĒāļāđāļĨāļīāļ', + class: 'btn-outline', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + } + }, + { + text: 'āļāļāļāļāļēāļāļĢāļ°āļāļ', + class: 'btn-secondary', + onClick: () => { + Utils.clearUserSession(); + window.location.href = 'index.html'; + } + } + ] + ); + } + + delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +// Initialize group selection manager +document.addEventListener('DOMContentLoaded', () => { + new GroupSelectionManager(); +}); + +// Export for testing +window.GroupSelectionManager = GroupSelectionManager; \ No newline at end of file diff --git a/assets/js/utils.js b/assets/js/utils.js new file mode 100644 index 0000000..533d6f3 --- /dev/null +++ b/assets/js/utils.js @@ -0,0 +1,381 @@ +/* ð ïļ Utility Functions */ +/* Helper functions for the Group Mission Workflow */ + +// === CONSTANTS === +const APP_CONFIG = { + // Group codes (āļĢāļŦāļąāļŠāļāļĨāļļāđāļĄ) + GROUP_CODES: { + 'GROUP001': { name: 'āļāļĨāļļāđāļĄāđāļŠāļāđāļĢāļ', members: 3, maxMembers: 5 }, + 'GROUP002': { name: 'āļāļĨāļļāđāļĄāļāļ§āļēāļĄāļŦāļ§āļąāļ', members: 5, maxMembers: 5 }, + 'GROUP003': { name: 'āļāļĨāļļāđāļĄāļāļ§āļēāļĄāļĢāļąāļ', members: 4, maxMembers: 5 }, + 'GROUP004': { name: 'āļāļĨāļļāđāļĄāļāļ§āļēāļĄāđāļāļ·āđāļ', members: 2, maxMembers: 5 }, + 'GROUP005': { name: 'āļāļĨāļļāđāļĄāļāļĢāļ°āļāļļāļ', members: 5, maxMembers: 5 }, + }, + + // Admin codes (āļĢāļŦāļąāļŠāđāļāļāļĄāļīāļ) + ADMIN_CODES: { + 'ADMIN001': { name: 'āļāļđāđāļāļđāđāļĨāļĢāļ°āļāļāļŦāļĨāļąāļ', level: 'super' }, + 'ADMIN002': { name: 'āļāļđāđāļāđāļ§āļĒāļāļđāđāļĨāļĢāļ°āļāļ', level: 'regular' } + }, + + // Session storage keys + STORAGE_KEYS: { + USER_SESSION: 'groupMissionUserSession', + GROUP_DATA: 'groupMissionGroupData', + WEEKLY_DATA: 'groupMissionWeeklyData' + } +}; + +// === UTILITY FUNCTIONS === + +/** + * Display loading overlay + * @param {boolean} show - Whether to show or hide the overlay + * @param {string} text - Loading text to display + */ +function showLoading(show = true, text = 'āļāļģāļĨāļąāļāđāļŦāļĨāļ...') { + const overlay = document.getElementById('loadingOverlay'); + if (!overlay) return; + + const loadingText = overlay.querySelector('.loading-text'); + if (loadingText) { + loadingText.textContent = text; + } + + if (show) { + overlay.classList.add('active'); + } else { + overlay.classList.remove('active'); + } +} + +/** + * Show notification/alert message + * @param {string} message - Message to display + * @param {string} type - Type of alert ('success', 'warning', 'danger', 'info') + * @param {number} duration - How long to show (milliseconds) + */ +function showAlert(message, type = 'info', duration = 3000) { + // Remove existing alerts + const existingAlerts = document.querySelectorAll('.alert-notification'); + existingAlerts.forEach(alert => alert.remove()); + + // Create new alert + const alert = document.createElement('div'); + alert.className = `alert alert-${type} alert-notification animate-bounce-in`; + alert.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + max-width: 400px; + box-shadow: var(--shadow-heavy); + `; + alert.textContent = message; + + document.body.appendChild(alert); + + // Auto remove after duration + setTimeout(() => { + alert.style.animation = 'fadeInUp 0.3s ease-out reverse'; + setTimeout(() => alert.remove(), 300); + }, duration); +} + +/** + * Show modal popup + * @param {string} title - Modal title + * @param {string} content - Modal content (HTML) + * @param {Array} buttons - Array of button objects {text, class, onClick} + */ +function showModal(title, content, buttons = []) { + // Remove existing modal + const existingModal = document.querySelector('.modal-overlay'); + if (existingModal) { + existingModal.remove(); + } + + // Create modal + const modalOverlay = document.createElement('div'); + modalOverlay.className = 'modal-overlay'; + modalOverlay.innerHTML = ` +āļĢāļ°āļāļāđāļ§āļīāļĢāđāļāđāļāļĨāļ§āđāļŠāđāļāļāļēāļāļāļĨāļļāđāļĄāļāļąāļāļāļāļīāļ
+āļāļĢāļ°āļāļēāļĻāļāđāļēāļ§āļāļĢāļ°āđāļŠāļĢāļīāļāđāļŦāđāļāļĢāļ 5 āļāļāļāđāļāļāđāļāđāļāļāļ·āđāļ
+ +āđāļāļāļāļāļĢāđāļāļāļēāļĢāļāļīāļāļāļēāļĄ | āļŠāļąāļāļāļēāļŦāđāļāļĩāđ 28
+${code}
+āļāļļāļāļāđāļāļāļāļēāļĢāļāļāļāļāļēāļāļĢāļ°āļāļāđāļāđāļŦāļĢāļ·āļāđāļĄāđ?
', + [ + { + text: 'āļĒāļāđāļĨāļīāļ', + class: 'btn-outline', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + } + }, + { + text: 'āļāļāļāļāļēāļāļĢāļ°āļāļ', + class: 'btn-secondary', + onClick: () => { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + } + Utils.clearUserSession(); + window.location.href = 'index.html'; + } + } + ] + ); + } +} + +// Initialize admin dashboard manager +document.addEventListener('DOMContentLoaded', () => { + new AdminDashboardManager(); +}); + +// Export for testing +window.AdminDashboardManager = AdminDashboardManager; \ No newline at end of file diff --git a/assets/js/mission.js b/assets/js/mission.js new file mode 100644 index 0000000..783f9ae --- /dev/null +++ b/assets/js/mission.js @@ -0,0 +1,699 @@ +/* ðą Mission Workflow Logic */ +/* 4-step weekly mission process for Group Mission Workflow */ + +class MissionManager { + constructor() { + this.userSession = null; + this.currentStep = 1; + this.missionData = { + attendance: [], + announcements: [], + media: [], + report: '' + }; + this.steps = { + 1: 'checkin', + 2: 'announce', + 3: 'upload', + 4: 'report' + }; + this.init(); + } + + init() { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.setupPage()); + } else { + this.setupPage(); + } + } + + setupPage() { + // Check authentication + this.userSession = Utils.getUserSession(); + if (!this.userSession || this.userSession.type !== 'group') { + window.location.href = 'index.html'; + return; + } + + this.setupHeader(); + this.setupEventListeners(); + this.loadCurrentStep(); + } + + setupHeader() { + const groupAvatar = document.getElementById('groupAvatar'); + const groupName = document.getElementById('groupName'); + const currentWeek = document.getElementById('currentWeek'); + const currentDate = document.getElementById('currentDate'); + + if (this.userSession && this.userSession.data) { + const groupData = this.userSession.data; + + if (groupAvatar) { + groupAvatar.textContent = groupData.name.charAt(0); + } + + if (groupName) { + groupName.textContent = groupData.name; + } + } + + if (currentWeek) { + currentWeek.textContent = Utils.getCurrentWeek(); + } + + if (currentDate) { + currentDate.textContent = Utils.formatThaiDate(new Date()); + } + } + + setupEventListeners() { + // Navigation buttons + const backButton = document.getElementById('backButton'); + const logoutButton = document.getElementById('logoutButton'); + + if (backButton) { + backButton.addEventListener('click', () => { + window.location.href = 'select-group.html'; + }); + } + + if (logoutButton) { + logoutButton.addEventListener('click', () => { + this.handleLogout(); + }); + } + + // Step navigation + document.addEventListener('click', (e) => { + if (e.target.closest('.step[data-step]')) { + const step = parseInt(e.target.closest('.step').dataset.step); + if (step <= this.currentStep || step === this.currentStep + 1) { + this.navigateToStep(step); + } + } + }); + } + + loadCurrentStep() { + this.updateProgressSteps(); + this.renderStepContent(); + } + + updateProgressSteps() { + const steps = document.querySelectorAll('.step'); + steps.forEach((step, index) => { + const stepNumber = index + 1; + step.classList.remove('active', 'completed', 'disabled'); + + if (stepNumber < this.currentStep) { + step.classList.add('completed'); + } else if (stepNumber === this.currentStep) { + step.classList.add('active'); + } else { + step.classList.add('disabled'); + } + }); + } + + renderStepContent() { + const content = document.getElementById('missionContent'); + if (!content) return; + + const stepMethod = `render${this.steps[this.currentStep].charAt(0).toUpperCase() + this.steps[this.currentStep].slice(1)}Step`; + + if (this[stepMethod]) { + content.innerHTML = ''; + this[stepMethod](content); + } + } + + renderCheckinStep(container) { + const stepContent = document.createElement('div'); + stepContent.className = 'step-content animate-fade-in-up'; + stepContent.innerHTML = ` +āļĢāļ°āļāļļāļŠāļĄāļēāļāļīāļāļāļĩāđāļĄāļēāļĢāđāļ§āļĄāļāļīāļāļāļĢāļĢāļĄāđāļāļŠāļąāļāļāļēāļŦāđāļāļĩāđ
+āļāļąāļāļāļķāļāļĢāļēāļĒāļāļ·āđāļāļāļđāđāļāļĩāđāđāļāđāļĢāļąāļāļāļēāļĢāļāļĢāļ°āļāļēāļĻ
+āļŠāđāļāļĢāļđāļāļ āļēāļāļāļīāļāļāļĢāļĢāļĄāļāļāļāļŠāļąāļāļāļēāļŦāđāļāļĩāđ
+āļŦāļĢāļ·āļāļāļĨāļīāļāđāļāļ·āđāļāđāļĨāļ·āļāļāđāļāļĨāđ
+ + +āļŠāļĢāļļāļāļāļĨāļāļēāļĢāļāļģāđāļāļīāļāļāļīāļāļāļĢāļĢāļĄāđāļāļŠāļąāļāļāļēāļŦāđāļāļĩāđ
+āļāļļāļāļāđāļāļāļāļēāļĢāļāļāļāļāļēāļāļĢāļ°āļāļāđāļāđāļŦāļĢāļ·āļāđāļĄāđ?
â ïļ āļāđāļāļĄāļđāļĨāļāļĩāđāļĒāļąāļāđāļĄāđāđāļāđāļāļąāļāļāļķāļāļāļ°āļŠāļđāļāļŦāļēāļĒ
', + [ + { + text: 'āļĒāļāđāļĨāļīāļ', + class: 'btn-outline', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + } + }, + { + text: 'āļāļāļāļāļēāļāļĢāļ°āļāļ', + class: 'btn-secondary', + onClick: () => { + Utils.clearUserSession(); + window.location.href = 'index.html'; + } + } + ] + ); + } + + delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +// Initialize mission manager +document.addEventListener('DOMContentLoaded', () => { + new MissionManager(); +}); + +// Export for testing +window.MissionManager = MissionManager; \ No newline at end of file diff --git a/assets/js/weekly-summary.js b/assets/js/weekly-summary.js new file mode 100644 index 0000000..8b35271 --- /dev/null +++ b/assets/js/weekly-summary.js @@ -0,0 +1,329 @@ +/* ð Weekly Summary Logic */ +/* Display completed mission results */ + +class WeeklySummaryManager { + constructor() { + this.userSession = null; + this.weeklyData = null; + this.init(); + } + + init() { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.setupPage()); + } else { + this.setupPage(); + } + } + + setupPage() { + // Check authentication + this.userSession = Utils.getUserSession(); + if (!this.userSession || this.userSession.type !== 'group') { + window.location.href = 'index.html'; + return; + } + + // Load weekly data + this.loadWeeklyData(); + this.setupHeader(); + this.setupEventListeners(); + this.renderSummary(); + } + + loadWeeklyData() { + try { + const data = localStorage.getItem(Utils.APP_CONFIG.STORAGE_KEYS.WEEKLY_DATA); + this.weeklyData = data ? JSON.parse(data) : this.createMockData(); + } catch (error) { + console.error('Error loading weekly data:', error); + this.weeklyData = this.createMockData(); + } + } + + createMockData() { + // Create mock data for demonstration + return { + week: Utils.getCurrentWeek(), + date: new Date().toISOString(), + groupCode: this.userSession.code, + attendance: [ + { name: 'āļŠāļĄāļāļēāļĒ āđāļāļāļĩ', status: 'present', isNew: false }, + { name: 'āļŠāļĄāļŦāļāļīāļ āļĢāļąāļāļāļĩ', status: 'absent', isNew: false }, + { name: 'āļŠāļĄāļĻāļĢāļĩ āļŠāļļāļāđāļ', status: 'absent', isNew: false }, + { name: 'āļŠāļĄāļāļāļĐāđ āļĄāļĩāļāļ§āļēāļĄāļŠāļļāļ', status: 'absent', isNew: true } + ], + announcements: [ + { name: 'āļāļĨāļīāļ āļŠāļĄāļīāļ', timestamp: new Date().toISOString() } + ], + media: [ + { name: 'mission_photo.jpg', size: 2048000 } + ], + report: 'āđāļāđāđāļāđāļāļāļąāļāļĢāļēāļāļāļēāļāļāļ§āļēāļĄāđāļāļ·āđāļāļāļąāļ āļāļĨāļīāļ āļŠāļĄāļīāļ āđāļāļāļĢāļđāđāļŠāļķāļāļŦāļāļļāļāđāļāđāļĨāļ°āļĄāļĩāļāļ§āļēāļĄāļŠāļļāļāļĄāļēāļ āđāļĢāļēāđāļāđāļāļāļīāļĐāļāļēāļāļĢāđāļ§āļĄāļāļąāļāđāļĨāļ°āļāđāļēāļāļēāļĒāđāļŦāđāđāļāļāđāļāļīāļāđāļāđāļāļāļ§āļēāļĄāđāļāļ·āđāļ' + }; + } + + setupHeader() { + const weekNumber = document.getElementById('weekNumber'); + const groupAvatar = document.getElementById('groupAvatar'); + const groupName = document.getElementById('groupName'); + const completionDate = document.getElementById('completionDate'); + + if (weekNumber) { + weekNumber.textContent = this.weeklyData.week; + } + + if (this.userSession && this.userSession.data) { + const groupData = this.userSession.data; + + if (groupAvatar) { + groupAvatar.textContent = groupData.name.charAt(0); + } + + if (groupName) { + groupName.textContent = groupData.name; + } + } + + if (completionDate) { + completionDate.textContent = Utils.formatThaiDate(new Date(this.weeklyData.date)); + } + } + + setupEventListeners() { + const backToMissionBtn = document.getElementById('backToMissionBtn'); + const startNewMissionBtn = document.getElementById('startNewMissionBtn'); + const logoutButton = document.getElementById('logoutButton'); + + if (backToMissionBtn) { + backToMissionBtn.addEventListener('click', () => { + window.location.href = 'weekly-mission.html'; + }); + } + + if (startNewMissionBtn) { + startNewMissionBtn.addEventListener('click', () => { + this.startNewMission(); + }); + } + + if (logoutButton) { + logoutButton.addEventListener('click', () => { + this.handleLogout(); + }); + } + } + + renderSummary() { + this.renderAttendanceSummary(); + this.renderAnnouncementSummary(); + this.renderMediaSummary(); + this.renderReportSummary(); + } + + renderAttendanceSummary() { + const presentCount = document.getElementById('presentCount'); + const absentCount = document.getElementById('absentCount'); + const newMemberCount = document.getElementById('newMemberCount'); + const presentMembers = document.getElementById('presentMembers'); + const newMembers = document.getElementById('newMembers'); + + if (!this.weeklyData.attendance) return; + + const present = this.weeklyData.attendance.filter(a => a.status === 'present'); + const absent = this.weeklyData.attendance.filter(a => a.status === 'absent'); + const newMembersData = this.weeklyData.attendance.filter(a => a.isNew); + + if (presentCount) presentCount.textContent = present.length; + if (absentCount) absentCount.textContent = absent.length; + if (newMemberCount) newMemberCount.textContent = newMembersData.length; + + if (presentMembers) { + const membersList = presentMembers.querySelector('.member-names'); + if (membersList) { + membersList.innerHTML = ''; + present.forEach(member => { + const li = document.createElement('li'); + li.textContent = member.name; + membersList.appendChild(li); + }); + } + } + + if (newMembers) { + const membersList = newMembers.querySelector('.member-names'); + if (membersList) { + membersList.innerHTML = ''; + newMembersData.forEach(member => { + const li = document.createElement('li'); + li.textContent = member.name; + membersList.appendChild(li); + }); + } + + // Hide section if no new members + if (newMembersData.length === 0) { + newMembers.style.display = 'none'; + } + } + } + + renderAnnouncementSummary() { + const announceCount = document.getElementById('announceCount'); + const announceList = document.getElementById('announceList'); + + if (!this.weeklyData.announcements) return; + + const count = this.weeklyData.announcements.length; + + if (announceCount) { + announceCount.textContent = count; + } + + if (announceList) { + const membersList = announceList.querySelector('.member-names'); + if (membersList) { + membersList.innerHTML = ''; + this.weeklyData.announcements.forEach(announce => { + const li = document.createElement('li'); + li.textContent = announce.name; + membersList.appendChild(li); + }); + } + } + + // Update progress bar + const progressBar = document.querySelector('.announce-stats + .announce-list + .progress-indicator .progress-bar'); + const progressText = document.querySelector('.progress-text'); + + if (progressBar && progressText) { + const percentage = Math.min((count / 5) * 100, 100); + progressBar.style.width = `${percentage}%`; + progressText.textContent = `${Math.round(percentage)}% āļāļāļāđāļāđāļēāļŦāļĄāļēāļĒ 5 āļāļ`; + + // Update color based on progress + progressBar.className = 'progress-bar ' + Utils.getProgressColor(percentage); + } + } + + renderMediaSummary() { + const mediaStatus = document.getElementById('mediaStatus'); + const fileInfo = document.getElementById('fileInfo'); + + if (!this.weeklyData.media || this.weeklyData.media.length === 0) { + if (mediaStatus) { + mediaStatus.className = 'media-status warning'; + mediaStatus.innerHTML = ` + +āļĒāļąāļāđāļĄāđāđāļāđāļāļąāļāđāļŦāļĨāļ
+ `; + } + if (fileInfo) { + fileInfo.innerHTML = 'āđāļĄāđāļĄāļĩāđāļāļĨāđ
'; + } + return; + } + + if (fileInfo) { + fileInfo.innerHTML = ''; + this.weeklyData.media.forEach(file => { + const fileItem = document.createElement('div'); + fileItem.className = 'file-item'; + fileItem.innerHTML = ` + + ${file.name} + `; + fileInfo.appendChild(fileItem); + }); + } + } + + renderReportSummary() { + const reportContent = document.getElementById('reportContent'); + + if (!this.weeklyData.report) { + if (reportContent) { + reportContent.innerHTML = 'āđāļĄāđāļĄāļĩāļĢāļēāļĒāļāļēāļ
'; + } + return; + } + + if (reportContent) { + const reportText = reportContent.querySelector('.report-text'); + if (reportText) { + reportText.textContent = `"${this.weeklyData.report}"`; + } + } + } + + startNewMission() { + Utils.showModal( + 'ð āđāļĢāļīāđāļĄāļāļąāļāļāļāļīāļāđāļŦāļĄāđ', + 'āļāļļāļāļāđāļāļāļāļēāļĢāđāļĢāļīāđāļĄāļāļąāļāļāļāļīāļāļŠāļąāļāļāļēāļŦāđāđāļŦāļĄāđāđāļāđāļŦāļĢāļ·āļāđāļĄāđ?
āļāđāļāļĄāļđāļĨāļāļāļāļŠāļąāļāļāļēāļŦāđāļāļąāļāļāļļāļāļąāļāļāļ°āļāļđāļāđāļāđāļāđāļ§āđ
', + [ + { + text: 'āļĒāļāđāļĨāļīāļ', + class: 'btn-outline', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + } + }, + { + text: 'āđāļĢāļīāđāļĄāđāļŦāļĄāđ', + class: 'btn-success', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + this.resetForNewMission(); + } + } + ] + ); + } + + resetForNewMission() { + // Clear weekly data but keep user session + try { + localStorage.removeItem(Utils.APP_CONFIG.STORAGE_KEYS.WEEKLY_DATA); + } catch (error) { + console.error('Error clearing weekly data:', error); + } + + Utils.showAlert('āđāļĢāļīāđāļĄāļāļąāļāļāļāļīāļāđāļŦāļĄāđāđāļĢāļĩāļĒāļāļĢāđāļāļĒāđāļĨāđāļ§! ð', 'success'); + + setTimeout(() => { + window.location.href = 'weekly-mission.html'; + }, 1500); + } + + handleLogout() { + Utils.showModal( + 'āļāļāļāļāļēāļāļĢāļ°āļāļ', + 'āļāļļāļāļāđāļāļāļāļēāļĢāļāļāļāļāļēāļāļĢāļ°āļāļāđāļāđāļŦāļĢāļ·āļāđāļĄāđ?
', + [ + { + text: 'āļĒāļāđāļĨāļīāļ', + class: 'btn-outline', + onClick: () => { + Utils.closeModal(document.querySelector('.modal-overlay')); + } + }, + { + text: 'āļāļāļāļāļēāļāļĢāļ°āļāļ', + class: 'btn-secondary', + onClick: () => { + Utils.clearUserSession(); + window.location.href = 'index.html'; + } + } + ] + ); + } +} + +// Initialize weekly summary manager +document.addEventListener('DOMContentLoaded', () => { + new WeeklySummaryManager(); +}); + +// Export for testing +window.WeeklySummaryManager = WeeklySummaryManager; \ No newline at end of file diff --git a/weekly-mission.html b/weekly-mission.html new file mode 100644 index 0000000..3835164 --- /dev/null +++ b/weekly-mission.html @@ -0,0 +1,73 @@ + + + + + +āļŠāļąāļāļāļēāļŦāđāļāļĩāđ 1 | āļ§āļąāļāļāļĩāđ
+āļŠāļąāļāļāļēāļŦāđāļāļĩāđ 28
+āļ§āļąāļāļāļĩāđ
+āļŠāļĄāļēāļāļīāļāļāļĩāđāļĄāļē:
+āļŠāļĄāļēāļāļīāļāđāļŦāļĄāđ:
+āđāļāđāļĢāļąāļāļāļēāļĢāļāļĢāļ°āļāļēāļĻ
+āļĢāļēāļĒāļāļ·āđāļ:
+20% āļāļāļāđāļāđāļēāļŦāļĄāļēāļĒ 5 āļāļ
+āļāļąāļāđāļŦāļĨāļāđāļĢāļĩāļĒāļāļĢāđāļāļĒ
++ "āđāļāđāđāļāđāļāļāļąāļāļĢāļēāļāļāļēāļāļāļ§āļēāļĄāđāļāļ·āđāļāļāļąāļ āļāļĨāļīāļ āļŠāļĄāļīāļ āđāļāļāļĢāļđāđāļŠāļķāļāļŦāļāļļāļāđāļāđāļĨāļ°āļĄāļĩāļāļ§āļēāļĄāļŠāļļāļāļĄāļēāļ āđāļĢāļēāđāļāđāļāļāļīāļĐāļāļēāļāļĢāđāļ§āļĄāļāļąāļāđāļĨāļ°āļāđāļēāļāļēāļĒāđāļŦāđāđāļāļāđāļāļīāļāđāļāđāļāļāļ§āļēāļĄāđāļāļ·āđāļ" ++