From a143569796c50cd5d6279d0f4799702e96e8efbf Mon Sep 17 00:00:00 2001 From: fynks <75840152+fynks@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:22:22 +0500 Subject: [PATCH] improved loading speed of web version --- dist/css/styles.css | 2121 ++++++------------------------------------- dist/index.html | 5 +- dist/js/app.js | 1109 +++++++++++----------- 3 files changed, 793 insertions(+), 2442 deletions(-) diff --git a/dist/css/styles.css b/dist/css/styles.css index fbacbb7..71d83a0 100644 --- a/dist/css/styles.css +++ b/dist/css/styles.css @@ -1,1882 +1,271 @@ -:root { - /* Color palette */ - --primary: #6366f1; - --primary-hover: #4f46e5; - --primary-light: #a5b4fc; - --primary-dark: #3730a3; - --secondary: #64748b; - --secondary-hover: #475569; - --success: #10b981; - --success-light: #a7f3d0; - --warning: #f59e0b; - --warning-light: #fef3c7; - --error: #ef4444; - --error-light: #fecaca; - --info: #3b82f6; - --info-light: #bfdbfe; - - /* Grayscale */ - --gray-50: #f8fafc; - --gray-100: #f1f5f9; - --gray-200: #e2e8f0; - --gray-300: #cbd5e1; - --gray-400: #94a3b8; - --gray-500: #64748b; - --gray-600: #475569; - --gray-700: #334155; - --gray-800: #1e293b; - --gray-900: #0f172a; - - /* Text colors */ - --text-primary: var(--gray-900); - --text-secondary: var(--gray-600); - --text-tertiary: var(--gray-500); - --text-muted: var(--gray-400); - - /* Background colors */ - --bg-primary: #fff; - --bg-secondary: var(--gray-50); - --bg-tertiary: var(--gray-100); - --bg-elevated: #fff; - - /* Borders */ - --border-primary: var(--gray-200); - --border-secondary: var(--gray-300); - --border-focus: var(--primary); - - /* Spacing scale */ - --space-px: 1px; - --space-0-5: 0.125rem; - --space-1: 0.25rem; - --space-1-5: 0.375rem; - --space-2: 0.5rem; - --space-2-5: 0.625rem; - --space-3: 0.75rem; - --space-3-5: 0.875rem; - --space-4: 1rem; - --space-5: 1.25rem; - --space-6: 1.5rem; - --space-7: 1.75rem; - --space-8: 2rem; - --space-9: 2.25rem; - --space-10: 2.5rem; - --space-11: 2.75rem; - --space-12: 3rem; - --space-14: 3.5rem; - --space-16: 4rem; - --space-20: 5rem; - --space-24: 6rem; - --space-28: 7rem; - --space-32: 8rem; - - /* Typography */ - --text-xs: 0.75rem; - --text-sm: 0.875rem; - --text-base: 1rem; - --text-lg: 1.125rem; - --text-xl: 1.25rem; - --text-2xl: 1.5rem; - --text-3xl: 1.875rem; - --text-4xl: 2.25rem; - --text-5xl: 3rem; - --text-6xl: 3.75rem; - --text-7xl: 4.5rem; - --text-8xl: 6rem; - --text-9xl: 8rem; - - --font-thin: 100; - --font-extralight: 200; - --font-light: 300; - --font-normal: 400; - --font-medium: 500; - --font-semibold: 600; - --font-bold: 700; - --font-extrabold: 800; - --font-black: 900; - - /* Radius */ - --radius-none: 0; - --radius-sm: 0.125rem; - --radius-base: 0.25rem; - --radius-md: 0.375rem; - --radius-lg: 0.5rem; - --radius-xl: 0.75rem; - --radius-2xl: 1rem; - --radius-3xl: 1.5rem; - --radius-full: 9999px; - - /* Shadows */ - --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); - --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); - --shadow-focus: 0 0 0 3px rgb(99 102 241 / 0.1); - - /* Transitions */ - --transition-all: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); - --transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); - --transition-normal: 0.2s cubic-bezier(0.4, 0, 0.2, 1); - --transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1); - --transition-bounce: 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); - - /* Layout */ - --container-max-width: 1280px; - --header-height: 4.5rem; - --content-max-width: 65ch; - - /* Z-index */ - --z-hide: -1; - --z-auto: auto; - --z-base: 0; - --z-docked: 10; - --z-dropdown: 1000; - --z-sticky: 1020; - --z-banner: 1030; - --z-overlay: 1040; - --z-modal: 1050; - --z-popover: 1060; - --z-skiplink: 1070; - --z-toast: 1080; - --z-tooltip: 1090; -} - -[data-theme="dark"] { - --text-primary: var(--gray-50); - --text-secondary: var(--gray-300); - --text-tertiary: var(--gray-400); - --text-muted: var(--gray-500); - --bg-primary: var(--gray-900); - --bg-secondary: var(--gray-800); - --bg-tertiary: var(--gray-700); - --bg-elevated: var(--gray-800); - --border-primary: var(--gray-700); - --border-secondary: var(--gray-600); - --border-focus: var(--primary-light); - --primary: #818cf8; - --primary-hover: #a5b4fc; - --primary-light: #c7d2fe; - --primary-dark: #6366f1; - --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.3); - --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.4), 0 1px 2px -1px rgb(0 0 0 / 0.4); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4); - --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.4); - --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.6); - --shadow-focus: 0 0 0 3px rgb(129 140 248 / 0.2); -} - -/* Reset & Base Styles */ -*, ::before, ::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -html { - scroll-behavior: smooth; - font-size: 16px; - line-height: 1.6; - -webkit-text-size-adjust: 100%; -} - -body { - font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - font-feature-settings: 'cv01', 'cv03', 'cv04', 'cv11'; - font-variation-settings: 'opsz' 32; - background-color: var(--bg-primary); - color: var(--text-primary); - transition: background-color var(--transition-normal), color var(--transition-normal); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - min-height: 100vh; - overflow-x: hidden; -} - -/* Focus Styles */ -:focus { - outline: 2px solid var(--border-focus); - outline-offset: 2px; -} - -:focus:not(:focus-visible) { - outline: none; -} - -:focus-visible { - outline: 2px solid var(--border-focus); - outline-offset: 2px; -} - -/* Typography */ -h1, h2, h3, h4, h5, h6 { - font-weight: var(--font-bold); - line-height: 1.25; - letter-spacing: -0.025em; - color: var(--text-primary); -} - -h1 { - font-size: var(--text-4xl); - font-weight: var(--font-extrabold); -} - -h2 { - font-size: var(--text-3xl); - font-weight: var(--font-bold); -} - -h3 { - font-size: var(--text-2xl); - font-weight: var(--font-semibold); -} - -p { - line-height: 1.75; - margin-bottom: var(--space-4); -} - -a { - color: var(--primary); - text-decoration: none; - transition: var(--transition-all); -} - -a:hover { - color: var(--primary-hover); - text-decoration: none; -} - -/* Layout Components */ -.page-wrapper { - min-height: 100vh; - display: flex; - flex-direction: column; - position: relative; -} - -.container { - max-width: var(--container-max-width); - margin: 0 auto; - padding: 0 var(--space-6); - width: 100%; -} - -.main-content { - flex: 1; - position: relative; -} - -/* Header */ -.site-header { - position: sticky; - top: 0; - z-index: var(--z-sticky); - background: rgb(255 255 255 / 0.8); - backdrop-filter: blur(20px) saturate(180%); - border-bottom: 1px solid var(--border-primary); - height: var(--header-height); - transition: var(--transition-all); -} - -[data-theme="dark"] .site-header { - background: rgb(15 23 42 / 0.8); -} - -.header-content { - display: flex; - align-items: center; - justify-content: space-between; - height: var(--header-height); - gap: var(--space-8); -} - -.site-title { - display: flex; - align-items: center; - gap: var(--space-2); - font-size: var(--text-xl); - font-weight: var(--font-bold); - color: var(--text-primary); - text-decoration: none; - transition: var(--transition-all); - position: relative; -} - -.site-title:hover { - color: var(--primary); - transform: translateY(-1px); -} - -.site-title:focus-visible { - border-radius: var(--radius-md); - padding: var(--space-1) var(--space-2); - margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)); -} - -.logo-icon { - font-size: var(--text-2xl); - filter: drop-shadow(0 2px 4px rgb(99 102 241 / 0.2)); - transition: var(--transition-all); -} - -.site-title:hover .logo-icon { - transform: rotate(-5deg) scale(1.1); -} - -.header-nav { - display: flex; - align-items: center; - gap: var(--space-6); -} - -.nav-link { - position: relative; - display: inline-flex; - align-items: center; - padding: var(--space-2) var(--space-3); - font-size: var(--text-sm); - font-weight: var(--font-medium); - color: var(--text-secondary); - text-decoration: none; - border-radius: var(--radius-lg); - transition: var(--transition-all); - white-space: nowrap; -} - -.nav-link::before { - content: ''; - position: absolute; - bottom: -2px; - left: 50%; - width: 0; - height: 2px; - background: var(--primary); - border-radius: var(--radius-full); - transition: var(--transition-all); - transform: translateX(-50%); -} - -.nav-link:hover { - color: var(--primary); - background: var(--bg-secondary); - transform: translateY(-1px); -} - -.nav-link:hover::before { - width: 70%; -} - -.nav-link:focus-visible { - box-shadow: var(--shadow-focus); -} - -.dark-mode-toggle { - display: flex; - align-items: center; - justify-content: center; - width: 2.75rem; - height: 2.75rem; - border: 2px solid var(--border-primary); - border-radius: var(--radius-full); - background: var(--bg-primary); - color: var(--text-primary); - cursor: pointer; - transition: var(--transition-all); - position: relative; - overflow: hidden; -} - -.dark-mode-toggle:hover { - background: var(--bg-secondary); - border-color: var(--primary); - transform: scale(1.05); - box-shadow: var(--shadow-md); -} - -.dark-mode-toggle:focus-visible { - box-shadow: var(--shadow-focus); -} - -.sun-icon, -.moon-icon { - transition: var(--transition-all); - position: absolute; -} - -.moon-icon { - display: none; - transform: rotate(180deg); -} - -[data-theme="dark"] .sun-icon { - display: none; -} - -[data-theme="dark"] .moon-icon { - display: block; - transform: rotate(0deg); -} - -/* Hero Section */ -.hero-section { - position: relative; - padding: var(--space-24) 0 var(--space-20); - background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); - overflow: hidden; -} - -.hero-section::before { - content: ''; - position: absolute; - inset: 0; - background: radial-gradient(circle at 30% 20%, rgb(99 102 241 / 0.1) 0%, transparent 50%), - radial-gradient(circle at 70% 80%, rgb(99 102 241 / 0.05) 0%, transparent 50%); - pointer-events: none; -} - -[data-theme="dark"] .hero-section { - background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); -} - -[data-theme="dark"] .hero-section::before { - background: radial-gradient(circle at 30% 20%, rgb(129 140 248 / 0.15) 0%, transparent 50%), - radial-gradient(circle at 70% 80%, rgb(129 140 248 / 0.08) 0%, transparent 50%); -} - -.hero-content { - text-align: center; - max-width: 900px; - margin: 0 auto; - position: relative; - z-index: 1; - animation: fadeInUp 0.8s ease-out; -} - -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.hero-title { - font-size: clamp(var(--text-3xl), 8vw, var(--text-6xl)); - font-weight: var(--font-extrabold); - line-height: 1.1; - margin: 0 0 var(--space-6); - color: var(--text-primary); - letter-spacing: -0.02em; -} - -.gradient-text { - background: linear-gradient(135deg, var(--primary) 0%, #8b5cf6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - position: relative; -} - -[data-theme="dark"] .gradient-text { - background: linear-gradient(135deg, var(--primary-light) 0%, #c4b5fd 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.hero-description { - font-size: clamp(var(--text-lg), 4vw, var(--text-xl)); - color: var(--text-secondary); - margin: 0 0 var(--space-10); - max-width: 650px; - margin-left: auto; - margin-right: auto; - line-height: 1.6; -} - -.hero-actions { - display: flex; - gap: var(--space-4); - justify-content: center; - margin-bottom: var(--space-16); - flex-wrap: wrap; -} - -.hero-stats { - display: flex; - gap: var(--space-12); - justify-content: center; - flex-wrap: wrap; -} - -.stat-item { - text-align: center; - position: relative; -} - -.stat-item:not(:last-child)::after { - content: ''; - position: absolute; - right: calc(-1 * var(--space-6)); - top: 50%; - transform: translateY(-50%); - width: 1px; - height: 40px; - background: var(--border-primary); -} - -.stat-number { - display: block; - font-size: clamp(var(--text-2xl), 6vw, var(--text-4xl)); - font-weight: var(--font-extrabold); - color: var(--primary); - margin-bottom: var(--space-1); - background: linear-gradient(135deg, var(--primary) 0%, #8b5cf6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.stat-label { - font-size: var(--text-sm); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.1em; - font-weight: var(--font-medium); -} - -/* Buttons */ -.btn { - display: inline-flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-4) var(--space-8); - border: 2px solid transparent; - border-radius: var(--radius-xl); - font-size: var(--text-base); - font-weight: var(--font-semibold); - text-decoration: none; - cursor: pointer; - transition: var(--transition-all); - white-space: nowrap; - position: relative; - overflow: hidden; - text-align: center; - min-width: 140px; - justify-content: center; -} - -.btn::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.2), transparent); - transition: left 0.5s; -} - -.btn:hover::before { - left: 100%; -} - -.btn-primary { - background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); - color: #fff; - box-shadow: var(--shadow-md); -} - -.btn-primary:hover { - background: linear-gradient(135deg, var(--primary-hover) 0%, var(--primary) 100%); - transform: translateY(-2px); - box-shadow: var(--shadow-lg); -} - -.btn-primary:focus-visible { - box-shadow: var(--shadow-focus), var(--shadow-lg); -} - -.btn-secondary { - background: transparent; - color: var(--text-primary); - border: 2px solid var(--border-primary); -} - -.btn-secondary:hover { - border-color: var(--primary); - color: var(--primary); - background: var(--bg-secondary); - transform: translateY(-2px); - box-shadow: var(--shadow-md); -} - -.btn-secondary:focus-visible { - box-shadow: var(--shadow-focus); -} - -/* Quick Navigation */ -.quick-nav { - padding: var(--space-20) 0; - background: var(--bg-primary); - position: relative; -} - -.quick-nav-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: var(--space-8); -} - -.quick-nav-card { - display: block; - padding: var(--space-8); - background: var(--bg-elevated); - border: 2px solid var(--border-primary); - border-radius: var(--radius-2xl); - text-decoration: none; - transition: var(--transition-all); - position: relative; - overflow: hidden; - box-shadow: var(--shadow-sm); - backdrop-filter: blur(10px); -} - -.quick-nav-card::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, var(--primary) 0%, transparent 100%); - opacity: 0; - transition: var(--transition-all); - z-index: 0; -} - -.quick-nav-card:hover::before { - opacity: 0.08; -} - -.quick-nav-card:hover { - border-color: var(--primary); - transform: translateY(-6px) scale(1.02); - box-shadow: var(--shadow-xl); -} - -.quick-nav-card:focus-visible { - box-shadow: var(--shadow-focus), var(--shadow-lg); -} - -.quick-nav-card > * { - position: relative; - z-index: 1; -} - -.quick-nav-icon { - font-size: var(--text-4xl); - margin-bottom: var(--space-4); - display: block; - filter: drop-shadow(0 2px 4px rgb(0 0 0 / 0.1)); - transition: var(--transition-all); -} - -.quick-nav-card:hover .quick-nav-icon { - transform: scale(1.1) rotate(-5deg); -} - -.quick-nav-card h3 { - margin: 0 0 var(--space-3); - font-size: var(--text-xl); - font-weight: var(--font-bold); - color: var(--text-primary); -} - -.quick-nav-card p { - margin: 0; - color: var(--text-secondary); - font-size: var(--text-base); - line-height: 1.6; -} - -.quick-nav-card a:hover { - text-decoration: none; -} +/* Quick nav */ +.quick-nav{padding:var(--space-20) 0;background:var(--bg-primary);position:relative;content-visibility:auto;contain-intrinsic-size:1px 600px;} +.quick-nav-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--space-8);} +.quick-nav-card{ + display:block;padding:var(--space-8);background:var(--bg-elevated);border:2px solid var(--border-primary);border-radius:var(--radius-2xl); + text-decoration:none;position:relative;overflow:hidden;box-shadow:var(--shadow-sm);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px); + transition:background-color var(--transition-normal),border-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal),opacity var(--transition-normal); + content-visibility:auto;contain:content; +} +.quick-nav-card::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--primary) 0%,transparent 100%);opacity:0;transition:opacity var(--transition-normal);z-index:0;} +.quick-nav-card:hover::before{opacity:.08;} +.quick-nav-card:hover{border-color:var(--primary);transform:translateY(-6px) scale(1.02);box-shadow:var(--shadow-lg);} +.quick-nav-card:focus-visible{box-shadow:var(--shadow-focus),var(--shadow-lg);} +.quick-nav-card>*{position:relative;z-index:1;} +.quick-nav-icon{font-size:var(--text-4xl);margin-bottom:var(--space-4);display:block;filter:drop-shadow(0 2px 4px rgb(0 0 0 / .1));transition:transform var(--transition-normal),filter var(--transition-normal);} +.quick-nav-card:hover .quick-nav-icon{transform:scale(1.1) rotate(-5deg);} +.quick-nav-card h3{margin:0 0 var(--space-3);font-size:var(--text-xl);font-weight:var(--font-bold);color:var(--text-primary);} +.quick-nav-card p{margin:0;color:var(--text-secondary);font-size:var(--text-base);line-height:1.6;} +.quick-nav-card a:hover{text-decoration:none;} /* Sections */ -.section { - padding: var(--space-24) 0; - position: relative; -} +.section{padding:var(--space-24) 0;position:relative;content-visibility:auto;contain-intrinsic-size:1px 800px;} +.section:nth-child(even){background:var(--bg-secondary);} +.section-header{text-align:center;margin-bottom:var(--space-20);animation:fadeInUp .6s ease-out;} +.section-title{font-size:clamp(var(--text-3xl),6vw,var(--text-5xl));font-weight:var(--font-extrabold);margin:0 0 var(--space-6);color:var(--text-primary);letter-spacing:-.02em;} +.section-description{font-size:clamp(var(--text-base),3vw,var(--text-xl));color:var(--text-secondary);margin:0;max-width:700px;margin-inline:auto;line-height:1.7;} -.section:nth-child(even) { - background: var(--bg-secondary); +/* Tables */ +.data-table-container{ + background:var(--bg-elevated);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg); + border:1px solid var(--border-primary);margin:var(--space-8) 0;position:relative;overflow:hidden; + -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px); } - -.section-header { - text-align: center; - margin-bottom: var(--space-20); - animation: fadeInUp 0.6s ease-out; +.table-wrapper{ + overflow-x:auto;overflow-y:visible;border-radius:var(--radius-2xl);-webkit-overflow-scrolling:touch; + scrollbar-width:thin;scrollbar-color:var(--primary) var(--bg-secondary); } - -.section-title { - font-size: clamp(var(--text-3xl), 6vw, var(--text-5xl)); - font-weight: var(--font-extrabold); - margin: 0 0 var(--space-6); - color: var(--text-primary); - letter-spacing: -0.02em; +.table-wrapper::-webkit-scrollbar{height:10px;} +.table-wrapper::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:var(--radius-md);} +.table-wrapper::-webkit-scrollbar-thumb{background:var(--primary);border-radius:var(--radius-md);border:2px solid var(--bg-secondary);} +.table-wrapper::-webkit-scrollbar-thumb:hover{background:var(--primary-hover);} +table{width:100%;border-collapse:collapse;font-size:var(--text-sm);min-width:600px;} +th,td{padding:var(--space-4) var(--space-6);text-align:left;border-bottom:1px solid var(--border-primary);white-space:nowrap;} +th{ + background:var(--primary);color:#fff;font-weight:var(--font-semibold);text-transform:uppercase;letter-spacing:.05em;font-size:var(--text-xs); + position:sticky;top:0;z-index:var(--z-sticky); } - -.section-description { - font-size: clamp(var(--text-base), 3vw, var(--text-xl)); - color: var(--text-secondary); - margin: 0; - max-width: 700px; - margin-inline: auto; - line-height: 1.7; -} - -/* Data Tables */ -.data-table-container { - background: var(--bg-elevated); - border-radius: var(--radius-2xl); - box-shadow: var(--shadow-xl); - border: 1px solid var(--border-primary); - margin: var(--space-8) 0; - position: relative; - overflow: hidden; - backdrop-filter: blur(10px); -} - -.table-wrapper { - overflow-x: auto; - overflow-y: visible; - border-radius: var(--radius-2xl); - -webkit-overflow-scrolling: touch; - scrollbar-width: thin; - scrollbar-color: var(--primary) var(--bg-secondary); -} - -.table-wrapper::-webkit-scrollbar { - height: 10px; -} - -.table-wrapper::-webkit-scrollbar-track { - background: var(--bg-secondary); - border-radius: var(--radius-md); -} - -.table-wrapper::-webkit-scrollbar-thumb { - background: var(--primary); - border-radius: var(--radius-md); - border: 2px solid var(--bg-secondary); -} - -.table-wrapper::-webkit-scrollbar-thumb:hover { - background: var(--primary-hover); -} - -table { - width: 100%; - border-collapse: collapse; - font-size: var(--text-sm); - min-width: 600px; -} - -th, -td { - padding: var(--space-4) var(--space-6); - text-align: left; - border-bottom: 1px solid var(--border-primary); - white-space: nowrap; -} - -th { - background: var(--primary); - color: #fff; - font-weight: var(--font-semibold); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: var(--text-xs); - position: sticky; - top: 0; - z-index: var(--z-sticky); -} - -th:first-child { - position: sticky; - left: 0; - z-index: calc(var(--z-sticky) + 1); - background: var(--primary); -} - -tr:hover { - background: var(--bg-secondary); -} - -td { - transition: all var(--transition-fast); -} - -td:first-child { - font-weight: var(--font-medium); - position: sticky; - left: 0; - background: var(--bg-primary); - z-index: 1; -} - -tr:hover td:first-child { - background: var(--bg-secondary); -} - -.status-cell { - text-align: center !important; -} - -.status-indicator { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border-radius: var(--radius-full); - transition: all var(--transition-fast); -} - -.status-indicator.supported { - background: rgb(14 236 162 / 0.1); - color: var(--success); -} - -.status-indicator.not-supported { - background: rgb(211 29 29 / 0.1); - color: var(--error); -} - -.status-indicator svg { - width: 16px; - height: 16px; +th:first-child{position:sticky;left:0;z-index:calc(var(--z-sticky) + 1);background:var(--primary);background-clip:padding-box;} +tr:hover{background:var(--bg-secondary);} +td{transition:background-color var(--transition-fast),color var(--transition-fast);} +td:first-child{ + font-weight:var(--font-medium);position:sticky;left:0;background:var(--bg-primary);z-index:1;background-clip:padding-box;box-shadow:inset -1px 0 0 var(--border-primary); } +tr:hover td:first-child{background:var(--bg-secondary);} +.status-cell{text-align:center!important;} +.status-indicator{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:var(--radius-full);transition:background-color var(--transition-fast),color var(--transition-fast),transform var(--transition-fast);} +.status-indicator.supported{background:rgb(14 236 162 / .1);color:var(--success);} +.status-indicator.not-supported{background:rgb(211 29 29 / .1);color:var(--error);} +.status-indicator svg{width:16px;height:16px;} /* Search */ -.search-section { - margin-bottom: var(--space-8); -} - -.search-container.enhanced { - position: relative; - max-width: 500px; - margin: 0 auto; -} - -.search-icon { - position: absolute; - left: var(--space-4); - top: 50%; - transform: translateY(-50%); - color: var(--text-tertiary); - z-index: 1; -} - -.search-input { - width: 100%; - padding: var(--space-4) var(--space-12) var(--space-4) var(--space-12); - border: 2px solid var(--border-primary); - border-radius: var(--radius-full); - font-size: var(--text-base); - background: var(--bg-primary); - color: var(--text-primary); - transition: all var(--transition-fast); - outline: none; -} - -.search-input:focus { - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(99 102 241 / 0.1); -} - -.clear-icon { - position: absolute; - right: var(--space-4); - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: var(--text-tertiary); - cursor: pointer; - padding: var(--space-1); - border-radius: var(--radius-md); - transition: all var(--transition-fast); - opacity: 0; - visibility: hidden; -} - -.search-input:not(:placeholder-shown) + .clear-icon, -.search-input:focus + .clear-icon { - opacity: 1; - visibility: visible; -} - -.clear-icon:hover { - background: var(--bg-secondary); - color: var(--text-primary); -} - -.search-results { - text-align: center; - padding: var(--space-2) 0; - color: var(--text-secondary); - font-size: var(--text-sm); - margin-bottom: var(--space-4); -} - -/* Comparison */ -.compare-controls { - display: flex; - align-items: end; - gap: var(--space-6); - margin-bottom: var(--space-12); - flex-wrap: wrap; -} - -.compare-select-group { - flex: 1; - min-width: 200px; -} - -.compare-select-group label { - display: block; - margin-bottom: var(--space-2); - font-weight: var(--font-medium); - color: var(--text-primary); -} - -.select-wrapper { - position: relative; -} - -.select-wrapper::after { - content: ''; - position: absolute; - right: var(--space-4); - top: 50%; - transform: translateY(-50%); - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid var(--text-tertiary); - pointer-events: none; -} - -select { - width: 100%; - padding: var(--space-3) var(--space-8) var(--space-3) var(--space-4); - border: 2px solid var(--border-primary); - border-radius: var(--radius-lg); - background: var(--bg-primary); - color: var(--text-primary); - font-size: var(--text-base); - cursor: pointer; - appearance: none; - transition: all var(--transition-fast); -} - -select:focus { - outline: none; - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(99 102 241 / 0.1); -} - -.compare-vs { - flex-shrink: 0; - font-size: var(--text-lg); - font-weight: var(--font-bold); - color: var(--primary); - background: var(--bg-secondary); - padding: var(--space-3) var(--space-4); - border-radius: var(--radius-lg); - border: 2px solid var(--border-primary); - margin-bottom: var(--space-2); -} - -#compare-table-container { - position: relative; - background: var(--bg-primary); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-lg); - border: 1px solid var(--border-primary); - margin-top: var(--space-6); - display: none; -} - -.comparison-visible { - display: block !important; - animation: slideIn 0.3s ease-out; -} - -.comparison-hiding { - animation: slideOut 0.3s ease-in; -} - -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slideOut { - from { - opacity: 1; - transform: translateY(0); - } - to { - opacity: 0; - transform: translateY(-20px); - } -} - -.comparison-header { - padding: var(--space-6); - border-bottom: 1px solid var(--border-primary); - background: var(--bg-secondary); - border-radius: var(--radius-xl) var(--radius-xl) 0 0; -} - -.comparison-header h3 { - margin: 0 0 var(--space-4); - font-size: var(--text-2xl); - font-weight: var(--font-bold); - color: var(--text-primary); - text-align: center; -} - -.comparison-stats { - display: flex; - gap: var(--space-6); - justify-content: center; - margin-bottom: var(--space-6); - flex-wrap: wrap; -} - -.comparison-stats .stat { - text-align: center; - background: var(--bg-primary); - padding: var(--space-3) var(--space-4); - border-radius: var(--radius-lg); - border: 1px solid var(--border-primary); - min-width: 100px; -} - -.stat-label { - display: block; - font-size: var(--text-xs); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: var(--space-1); -} - -.stat-value { - display: block; - font-size: var(--text-xl); - font-weight: var(--font-bold); - color: var(--primary); -} - -.comparison-actions { - display: flex; - justify-content: center; - gap: var(--space-3); -} - -.comparison-filters { - padding: var(--space-4) var(--space-6); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-primary); - display: flex; - gap: var(--space-6); - justify-content: center; - flex-wrap: wrap; -} - -.filter-option { - display: flex; - align-items: center; - gap: var(--space-2); - cursor: pointer; - font-size: var(--text-sm); - font-weight: var(--font-medium); - color: var(--text-primary); -} - -.filter-option input[type="radio"] { - margin: 0; -} - -.filter-results { - text-align: center; - padding: var(--space-2) 0; - color: var(--text-secondary); - font-size: var(--text-sm); - background: var(--bg-secondary); - border-bottom: 1px solid var(--border-primary); -} - -.comparison-table { - width: 100%; - border-collapse: collapse; - font-size: var(--text-sm); - min-width: 700px; -} - -.comparison-table th, -.comparison-table td { - padding: var(--space-4) var(--space-6); - border-bottom: 1px solid var(--border-primary); - text-align: center; -} - -.comparison-table th:first-child, -.comparison-table td:first-child { - text-align: left; - font-weight: var(--font-medium); -} - -.comparison-table th { - background: var(--primary); - color: #fff; - font-weight: var(--font-semibold); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: var(--text-xs); - position: sticky; - top: 0; - z-index: var(--z-sticky); -} - -.provider-header { - position: relative; -} - -.comparison-row.both-supported { - background: rgb(16 185 129 / 0.05); -} - -.comparison-row.provider1-only { - background: rgb(59 130 246 / 0.05); -} - -.comparison-row.provider2-only { - background: rgb(168 85 247 / 0.05); -} - -.comparison-row.neither-supported { - background: rgb(239 68 68 / 0.05); -} - -.status-badge { - display: inline-block; - padding: var(--space-1) var(--space-3); - border-radius: var(--radius-full); - font-size: var(--text-xs); - font-weight: var(--font-medium); - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.status-badge.both-supported { - background: rgb(16 185 129 / 0.1); - color: var(--success); -} - -.status-badge.provider1-only { - background: rgb(59 130 246 / 0.1); - color: var(--info); -} - -.status-badge.provider2-only { - background: rgb(168 85 247 / 0.1); - color: #8b5cf6; -} - -.status-badge.neither-supported { - background: rgb(239 68 68 / 0.1); - color: var(--error); -} - -#close-compare { - background: var(--bg-primary); - border: 1px solid var(--border-primary); - border-radius: var(--radius-lg); - padding: var(--space-2) var(--space-4); - display: flex; - align-items: center; - gap: var(--space-2); - cursor: pointer; - transition: all var(--transition-fast); - font-size: var(--text-sm); - color: var(--text-primary); - font-weight: var(--font-medium); -} - -#close-compare:hover { - background: var(--error); - color: #fff; - border-color: var(--error); - transform: translateY(-1px); -} - -.empty-state { - text-align: center; - padding: var(--space-16); -} - -.empty-state-icon { - font-size: var(--text-5xl); - margin-bottom: var(--space-4); -} - -.empty-state h3 { - margin: 0 0 var(--space-2); - font-size: var(--text-xl); - color: var(--text-primary); -} - -.empty-state p { - margin: 0; - color: var(--text-secondary); -} - -.warning-state { - text-align: center; - padding: var(--space-12); - background: rgb(245 158 11 / 0.05); - border-radius: var(--radius-lg); - border: 1px solid rgb(245 158 11 / 0.2); -} - -.warning-icon { - font-size: var(--text-4xl); - margin-bottom: var(--space-3); -} - -.warning-state h3 { - margin: 0 0 var(--space-2); - color: var(--warning); -} - -.warning-state p { - margin: 0; - color: var(--text-secondary); -} - -.alert { - display: flex; - align-items: flex-start; - gap: var(--space-3); - padding: var(--space-4); - border-radius: var(--radius-lg); - margin: var(--space-4) 0; -} - -.alert-info { - background: rgb(59 130 246 / 0.1); - border: 1px solid rgb(59 130 246 / 0.2); - color: var(--info); -} - -.alert-icon { - flex-shrink: 0; - margin-top: 2px; -} -/* Referral Section */ -.referral-section { - margin-block: var(--space-16); - padding: var(--space-8); - background: var(--bg-secondary); - border-radius: var(--radius-xl); - border: 1px solid var(--border-primary); - text-align: center; -} - -.referral-header h3 { - margin: 0 0 var(--space-2); - color: var(--text-primary); - font-size: var(--text-2xl); - font-weight: var(--font-bold); -} - -.referral-header p { - margin: 0 auto var(--space-6); - color: var(--text-secondary); - max-width: var(--content-max-width); -} - -.referral-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: var(--space-4); - justify-items: center; -} - -.referral-card { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--space-2); - padding: var(--space-4) var(--space-3); - background: var(--bg-primary); - border: 1px solid var(--border-primary); - border-radius: var(--radius-lg); - text-decoration: none; - transition: var(--transition-all); - width: 100%; - max-width: 200px; - box-shadow: var(--shadow-sm); -} - -.referral-card:hover { - border-color: var(--primary); - transform: translateY(-2px); - box-shadow: var(--shadow-md); -} - -.service-name { - font-weight: var(--font-semibold); - color: var(--text-primary); - font-size: var(--text-base); -} - -.referral-badge { - font-size: var(--text-xs); - color: var(--primary); - background: rgb(99 102 241 / 0.1); - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-md); - font-weight: var(--font-medium); -} - -.service-name { - font-weight: var(--font-medium); - color: var(--text-primary); -} - -.status-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: var(--space-6); -} - -.status-card { - display: block; - padding: var(--space-6); - background: var(--bg-primary); - border: 1px solid var(--border-primary); - border-radius: var(--radius-xl); - text-decoration: none; - transition: all var(--transition-normal); -} - -.status-card:hover { - border-color: var(--primary); - transform: translateY(-2px); - box-shadow: var(--shadow-lg); -} - -.status-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: var(--space-3); -} - -.service-status-indicator { - width: 12px; - height: 12px; - border-radius: var(--radius-full); - background: var(--success); - box-shadow: 0 0 0 2px rgb(16 185 129 / 0.2); -} - -.status-indicator.online { - background: var(--success); - box-shadow: 0 0 0 2px rgb(16 185 129 / 0.2); -} - -.status-card p { - margin: 0; - color: var(--text-secondary); - font-size: var(--text-sm); -} - -.loading-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: var(--space-12); - color: var(--text-secondary); -} - -.loading-spinner { - width: 32px; - height: 32px; - border: 3px solid var(--border-primary); - border-top: 3px solid var(--primary); - border-radius: var(--radius-full); - animation: spin 1s linear infinite; - margin-bottom: var(--space-4); -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} +.search-section{margin-bottom:var(--space-8);} +.search-container.enhanced{position:relative;max-width:500px;margin:0 auto;} +.search-icon{position:absolute;left:var(--space-4);top:50%;transform:translateY(-50%);color:var(--text-tertiary);z-index:1;} +.search-input{ + width:100%;padding:var(--space-4) var(--space-12) var(--space-4) var(--space-12); + border:2px solid var(--border-primary);border-radius:var(--radius-full);font-size:var(--text-base); + background:var(--bg-primary);color:var(--text-primary);outline:none; + transition:border-color var(--transition-fast),box-shadow var(--transition-fast),color var(--transition-fast),background-color var(--transition-fast); +} +.search-input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgb(99 102 241 / .1);} +.clear-icon{ + position:absolute;right:var(--space-4);top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text-tertiary); + cursor:pointer;padding:var(--space-1);border-radius:var(--radius-md);opacity:0;visibility:hidden; + transition:opacity var(--transition-fast),color var(--transition-fast),background-color var(--transition-fast),transform var(--transition-fast); +} +.search-input:not(:placeholder-shown) + .clear-icon,.search-input:focus + .clear-icon{opacity:1;visibility:visible;} +.clear-icon:hover{background:var(--bg-secondary);color:var(--text-primary);} +.search-results{text-align:center;padding:var(--space-2) 0;color:var(--text-secondary);font-size:var(--text-sm);margin-bottom:var(--space-4);} + +/* Compare controls */ +.compare-controls{display:flex;align-items:end;gap:var(--space-6);margin-bottom:var(--space-12);flex-wrap:wrap;} +.compare-select-group{flex:1;min-width:200px;} +.compare-select-group label{display:block;margin-bottom:var(--space-2);font-weight:var(--font-medium);color:var(--text-primary);} +.select-wrapper{position:relative;} +.select-wrapper::after{ + content:'';position:absolute;right:var(--space-4);top:50%;transform:translateY(-50%); + width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--text-tertiary);pointer-events:none; +} +select{ + width:100%;padding:var(--space-3) var(--space-8) var(--space-3) var(--space-4); + border:2px solid var(--border-primary);border-radius:var(--radius-lg);background:var(--bg-primary);color:var(--text-primary); + font-size:var(--text-base);cursor:pointer;appearance:none; + transition:border-color var(--transition-fast),box-shadow var(--transition-fast),color var(--transition-fast),background-color var(--transition-fast); +} +select:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgb(99 102 241 / .1);} +.compare-vs{flex-shrink:0;font-size:var(--text-lg);font-weight:var(--font-bold);color:var(--primary);background:var(--bg-secondary);padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);border:2px solid var(--border-primary);margin-bottom:var(--space-2);} + +/* Compare table container */ +#compare-table-container{ + position:relative;background:var(--bg-primary);border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);border:1px solid var(--border-primary);margin-top:var(--space-6);display:none; +} +.comparison-visible{display:block!important;animation:slideIn .3s ease-out;} +.comparison-hiding{animation:slideOut .3s ease-in;} +@keyframes slideIn{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}} +@keyframes slideOut{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-20px)}} +.comparison-header{padding:var(--space-6);border-bottom:1px solid var(--border-primary);background:var(--bg-secondary);border-radius:var(--radius-xl) var(--radius-xl) 0 0;} +.comparison-header h3{margin:0 0 var(--space-4);font-size:var(--text-2xl);font-weight:var(--font-bold);color:var(--text-primary);text-align:center;} +.comparison-stats{display:flex;gap:var(--space-6);justify-content:center;margin-bottom:var(--space-6);flex-wrap:wrap;} +.comparison-stats .stat{text-align:center;background:var(--bg-primary);padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);border:1px solid var(--border-primary);min-width:100px;} +.stat-label{display:block;font-size:var(--text-xs);color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-1);} +.stat-value{display:block;font-size:var(--text-xl);font-weight:var(--font-bold);color:var(--primary);} +.comparison-actions{display:flex;justify-content:center;gap:var(--space-3);} +.comparison-filters{padding:var(--space-4) var(--space-6);background:var(--bg-tertiary);border-bottom:1px solid var(--border-primary);display:flex;gap:var(--space-6);justify-content:center;flex-wrap:wrap;} +.filter-option{display:flex;align-items:center;gap:var(--space-2);cursor:pointer;font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--text-primary);} +.filter-option input[type="radio"]{margin:0;} +.filter-results{text-align:center;padding:var(--space-2) 0;color:var(--text-secondary);font-size:var(--text-sm);background:var(--bg-secondary);border-bottom:1px solid var(--border-primary);} + +/* Comparison table */ +.comparison-table{width:100%;border-collapse:collapse;font-size:var(--text-sm);min-width:700px;} +.comparison-table th,.comparison-table td{padding:var(--space-4) var(--space-6);border-bottom:1px solid var(--border-primary);text-align:center;} +.comparison-table th:first-child,.comparison-table td:first-child{text-align:left;font-weight:var(--font-medium);} +.comparison-table th{background:var(--primary);color:#fff;font-weight:var(--font-semibold);text-transform:uppercase;letter-spacing:.05em;font-size:var(--text-xs);position:sticky;top:0;z-index:var(--z-sticky);} +.provider-header{position:relative;} +.comparison-row.both-supported{background:rgb(16 185 129 / .05);} +.comparison-row.provider1-only{background:rgb(59 130 246 / .05);} +.comparison-row.provider2-only{background:rgb(168 85 247 / .05);} +.comparison-row.neither-supported{background:rgb(239 68 68 / .05);} +.status-badge{display:inline-block;padding:var(--space-1) var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--font-medium);text-transform:uppercase;letter-spacing:.05em;} +.status-badge.both-supported{background:rgb(16 185 129 / .1);color:var(--success);} +.status-badge.provider1-only{background:rgb(59 130 246 / .1);color:var(--info);} +.status-badge.provider2-only{background:rgb(168 85 247 / .1);color:#8b5cf6;} +.status-badge.neither-supported{background:rgb(239 68 68 / .1);color:var(--error);} + +#close-compare{ + background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:var(--radius-lg);padding:var(--space-2) var(--space-4); + display:flex;align-items:center;gap:var(--space-2);cursor:pointer;font-size:var(--text-sm);color:var(--text-primary);font-weight:var(--font-medium); + transition:color var(--transition-fast),background-color var(--transition-fast),border-color var(--transition-fast),transform var(--transition-fast),box-shadow var(--transition-fast); +} +#close-compare:hover{background:var(--error);color:#fff;border-color:var(--error);transform:translateY(-1px);} + +/* Empty + warning + alerts */ +.empty-state{text-align:center;padding:var(--space-16);} +.empty-state-icon{font-size:var(--text-5xl);margin-bottom:var(--space-4);} +.empty-state h3{margin:0 0 var(--space-2);font-size:var(--text-xl);color:var(--text-primary);} +.empty-state p{margin:0;color:var(--text-secondary);} +.warning-state{text-align:center;padding:var(--space-12);background:rgb(245 158 11 / .05);border-radius:var(--radius-lg);border:1px solid rgb(245 158 11 / .2);} +.warning-icon{font-size:var(--text-4xl);margin-bottom:var(--space-3);} +.warning-state h3{margin:0 0 var(--space-2);color:var(--warning);} +.warning-state p{margin:0;color:var(--text-secondary);} +.alert{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--space-4);border-radius:var(--radius-lg);margin:var(--space-4) 0;} +.alert-info{background:rgb(59 130 246 / .1);border:1px solid rgb(59 130 246 / .2);color:var(--info);} +.alert-icon{flex-shrink:0;margin-top:2px;} + +/* Referral */ +.referral-section{margin-block:var(--space-16);padding:var(--space-8);background:var(--bg-secondary);border-radius:var(--radius-xl);border:1px solid var(--border-primary);text-align:center;content-visibility:auto;contain-intrinsic-size:1px 400px;} +.referral-header h3{margin:0 0 var(--space-2);color:var(--text-primary);font-size:var(--text-2xl);font-weight:var(--font-bold);} +.referral-header p{margin:0 auto var(--space-6);color:var(--text-secondary);max-width:var(--content-max-width);} +.referral-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--space-4);justify-items:center;} +.referral-card{ + display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2); + padding:var(--space-4) var(--space-3);background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:var(--radius-lg); + text-decoration:none;width:100%;max-width:200px;box-shadow:var(--shadow-sm); + transition:background-color var(--transition-normal),border-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal),color var(--transition-normal); +} +.referral-card:hover{border-color:var(--primary);transform:translateY(-2px);box-shadow:var(--shadow-md);} +.service-name{font-weight:var(--font-semibold);color:var(--text-primary);font-size:var(--text-base);} +.referral-badge{font-size:var(--text-xs);color:var(--primary);background:rgb(99 102 241 / .1);padding:var(--space-1) var(--space-2);border-radius:var(--radius-md);font-weight:var(--font-medium);} + +/* Status cards */ +.status-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--space-6);} +.status-card{ + display:block;padding:var(--space-6);background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:var(--radius-xl);text-decoration:none; + transition:background-color var(--transition-normal),border-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal); + content-visibility:auto;contain:content; +} +.status-card:hover{border-color:var(--primary);transform:translateY(-2px);box-shadow:var(--shadow-lg);} +.status-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-3);} +.service-status-indicator{width:12px;height:12px;border-radius:var(--radius-full);background:var(--success);box-shadow:0 0 0 2px rgb(16 185 129 / .2);} +.status-card .status-indicator.online, +.service-status-indicator.online{background:var(--success);box-shadow:0 0 0 2px rgb(16 185 129 / .2);} +.status-card p{margin:0;color:var(--text-secondary);font-size:var(--text-sm);} + +/* Loading */ +.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-12);color:var(--text-secondary);} +.loading-spinner{width:32px;height:32px;border:3px solid var(--border-primary);border-top:3px solid var(--primary);border-radius:var(--radius-full);animation:spin 1s linear infinite;margin-bottom:var(--space-4);} +@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}} +.loading-overlay{ + position:absolute;inset:0;background:rgb(255 255 255 / .9); + -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px); + display:flex;align-items:center;justify-content:center;z-index:1050;opacity:0;visibility:hidden; + transition:opacity var(--transition-normal); +} +[data-theme="dark"] .loading-overlay{background:rgb(15 23 42 / .9);} +.loading-overlay--visible{opacity:1;visibility:visible;} +.loading-overlay--hiding{opacity:0;} +.loading-content{text-align:center;color:var(--text-primary);} +.spinner-ring{width:40px;height:40px;border:4px solid var(--border-primary);border-top:4px solid var(--primary);border-radius:var(--radius-full);animation:spin 1s linear infinite;margin:0 auto var(--space-3);} +.loading-text{margin:0;font-size:var(--text-sm);color:var(--text-secondary);} /* Footer */ -.site-footer { - background: var(--bg-secondary); - border-top: 1px solid var(--border-primary); - padding: var(--space-16) 0 var(--space-8); - margin-top: auto; +.site-footer{background:var(--bg-secondary);border-top:1px solid var(--border-primary);padding:var(--space-16) 0 var(--space-8);margin-top:auto;content-visibility:auto;contain-intrinsic-size:1px 500px;} +.footer-content{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:var(--space-8);margin-bottom:var(--space-8);} +.footer-section h4{margin:0 0 var(--space-4);font-size:var(--text-lg);font-weight:var(--font-semibold);color:var(--text-primary);} +.footer-section p{margin:0;color:var(--text-secondary);} +.footer-section ul{list-style:none;padding:0;margin:0;} +.footer-section li{margin-bottom:var(--space-2);} +.footer-section a{color:var(--text-secondary);text-decoration:none;transition:color var(--transition-fast);} +.footer-section a:hover{color:var(--primary);} +.footer-bottom{text-align:center;padding-top:var(--space-8);border-top:1px solid var(--border-primary);} +.footer-bottom p{margin:0;color:var(--text-tertiary);font-size:var(--text-sm);} +.footer-bottom a{color:var(--primary);text-decoration:none;} + +/* Back to top */ +.back-to-top{ + position:fixed;bottom:var(--space-6);right:var(--space-6);width:48px;height:48px;background:var(--primary);color:#fff;border:none;border-radius:var(--radius-full); + cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-lg); + transition:background-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal),opacity var(--transition-normal),visibility 0s linear .2s; + opacity:0;visibility:hidden;transform:translateY(20px);z-index:var(--z-docked); +} +.back-to-top.visible{opacity:1;visibility:visible;transform:translateY(0);transition-delay:0s;} +.back-to-top:hover{background:var(--primary-hover);transform:translateY(-2px);box-shadow:var(--shadow-lg);} + +/* Misc */ +input,select,textarea,button{accent-color:var(--primary);} + +/* Responsive (deferred parts) */ +@media (width <= 768px){ + .quick-nav-grid{grid-template-columns:1fr} + .footer-content{grid-template-columns:1fr;text-align:center} + .data-table-container{margin:var(--space-2) calc(-1 * var(--space-4));border-radius:0;border-left:none;border-right:none} + .table-wrapper{border-radius:0} + table{font-size:var(--text-xs);min-width:500px} + th,td{padding:var(--space-2) var(--space-3)} + th:first-child,td:first-child{min-width:120px;max-width:150px;white-space:normal;word-break:break-word} + .comparison-header{padding:var(--space-4)} + .comparison-header h3{font-size:var(--text-lg)} + .comparison-stats{flex-direction:column;gap:var(--space-3)} + .comparison-stats .stat{margin:0 auto;max-width:200px} + .comparison-filters{flex-direction:column;align-items:center;gap:var(--space-3)} + .comparison-table{min-width:500px} + .comparison-table th,.comparison-table td{padding:var(--space-2) var(--space-3);font-size:var(--text-xs)} +} +@media (width <= 640px){ + .btn{width:100%;max-width:280px;padding:var(--space-4) var(--space-6)} +} +@media (width <= 480px){ + .section{padding:var(--space-12) 0} + .comparison-stats{gap:var(--space-2)} + .comparison-stats .stat{min-width:60px;padding:var(--space-1) var(--space-2)} + .stat-value{font-size:var(--text-lg)} + .stat-label{font-size:10px} + table{min-width:400px} + th:first-child,td:first-child{min-width:100px;max-width:120px} + .stat-item:not(:last-child)::after{display:none} } -.footer-content { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: var(--space-8); - margin-bottom: var(--space-8); +/* Coarse pointer */ +@media (pointer: coarse){ + .nav-link:hover,.btn:hover,.quick-nav-card:hover,.status-card:hover{transform:none;box-shadow:none} } -.footer-section h4 { - margin: 0 0 var(--space-4); - font-size: var(--text-lg); - font-weight: var(--font-semibold); - color: var(--text-primary); -} - -.footer-section p { - margin: 0; - color: var(--text-secondary); -} - -.footer-section ul { - list-style: none; - padding: 0; - margin: 0; -} - -.footer-section li { - margin-bottom: var(--space-2); -} - -.footer-section a { - color: var(--text-secondary); - text-decoration: none; - transition: color var(--transition-fast); -} - -.footer-section a:hover { - color: var(--primary); -} - -.footer-bottom { - text-align: center; - padding-top: var(--space-8); - border-top: 1px solid var(--border-primary); -} - -.footer-bottom p { - margin: 0; - color: var(--text-tertiary); - font-size: var(--text-sm); -} - -.footer-bottom a { - color: var(--primary); - text-decoration: none; -} - -/* Back to Top */ -.back-to-top { - position: fixed; - bottom: var(--space-6); - right: var(--space-6); - width: 48px; - height: 48px; - background: var(--primary); - color: #fff; - border: none; - border-radius: var(--radius-full); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - box-shadow: var(--shadow-lg); - transition: all var(--transition-normal); - opacity: 0; - visibility: hidden; - transform: translateY(20px); - z-index: var(--z-docked); -} - -.back-to-top.visible { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.back-to-top:hover { - background: var(--primary-hover); - transform: translateY(-2px); - box-shadow: var(--shadow-xl); -} - -/* Loading Overlay */ -.loading-overlay { - position: absolute; - inset: 0; - background: rgb(255 255 255 / 0.9); - backdrop-filter: blur(2px); - display: flex; - align-items: center; - justify-content: center; - z-index: var(--z-modal); - opacity: 0; - visibility: hidden; - transition: all var(--transition-normal); -} - -[data-theme="dark"] .loading-overlay { - background: rgb(15 23 42 / 0.9); -} - -.loading-overlay--visible { - opacity: 1; - visibility: visible; -} - -.loading-overlay--hiding { - opacity: 0; -} - -.loading-content { - text-align: center; - color: var(--text-primary); -} - -.spinner-ring { - width: 40px; - height: 40px; - border: 4px solid var(--border-primary); - border-top: 4px solid var(--primary); - border-radius: var(--radius-full); - animation: spin 1s linear infinite; - margin: 0 auto var(--space-3); -} - -.loading-text { - margin: 0; - font-size: var(--text-sm); - color: var(--text-secondary); -} - -/* Responsive Media Queries */ -@media (width <= 1024px) { - .container { - padding: 0 var(--space-4); - } - - .comparison-stats { - gap: var(--space-4); - } - - .comparison-stats .stat { - min-width: 80px; - padding: var(--space-2) var(--space-3); - } -} - -@media (width <= 768px) { - .header-nav { - display: none; - } - - .hero-title { - font-size: var(--text-3xl); - } - - .hero-description { - font-size: var(--text-lg); - } - - .hero-actions { - flex-direction: column; - align-items: center; - } - - .hero-stats { - gap: var(--space-6); - } - - .section-title { - font-size: var(--text-3xl); - } - - .compare-controls { - flex-direction: column; - } - - .compare-vs { - align-self: center; - margin: var(--space-2) 0; - } - - .quick-nav-grid { - grid-template-columns: 1fr; - } - - - .footer-content { - grid-template-columns: 1fr; - text-align: center; - } - - .data-table-container { - margin: var(--space-2) calc(-1 * var(--space-4)); - border-radius: 0; - border-left: none; - border-right: none; - } - - .table-wrapper { - border-radius: 0; - } - - table { - font-size: var(--text-xs); - min-width: 500px; - } - - th, - td { - padding: var(--space-2) var(--space-3); - } - - th:first-child, - td:first-child { - min-width: 120px; - max-width: 150px; - white-space: normal; - word-break: break-word; - } - - .comparison-header { - padding: var(--space-4); - } - - .comparison-header h3 { - font-size: var(--text-lg); - } - - .comparison-stats { - flex-direction: column; - gap: var(--space-3); - } - - .comparison-stats .stat { - margin: 0 auto; - max-width: 200px; - } - - .comparison-filters { - flex-direction: column; - align-items: center; - gap: var(--space-3); - } - - .comparison-table { - min-width: 500px; - } - - .comparison-table th, - .comparison-table td { - padding: var(--space-2) var(--space-3); - font-size: var(--text-xs); - } - - th, - td { - padding: var(--space-3) var(--space-4); - } - - .header-content { - gap: var(--space-4); - } - - .nav-link { - padding: var(--space-1-5) var(--space-2); - font-size: var(--text-xs); - } -} - -@media (width <= 480px) { - .hero-section { - padding: var(--space-16) 0 var(--space-12); - } - - .section { - padding: var(--space-12) 0; - } - - .btn { - width: 100%; - justify-content: center; - } - - .hero-stats { - flex-direction: column; - gap: var(--space-4); - } - - .comparison-stats { - gap: var(--space-2); - } - - .comparison-stats .stat { - min-width: 60px; - padding: var(--space-1) var(--space-2); - } - - .stat-value { - font-size: var(--text-lg); - } - - .stat-label { - font-size: 10px; - } - - table { - min-width: 400px; - } - - th:first-child, - td:first-child { - min-width: 100px; - max-width: 120px; - } - - .stat-item:not(:last-child)::after { - display: none; - } -} - -@media (width <= 640px) { - .container { - padding: 0 var(--space-4); - } - - .hero-actions { - flex-direction: column; - align-items: center; - gap: var(--space-3); - } - - .hero-stats { - gap: var(--space-8); - } - - .nav-link { - padding: var(--space-1-5) var(--space-2); - font-size: var(--text-xs); - } - - .btn { - width: 100%; - max-width: 280px; - padding: var(--space-4) var(--space-6); - } -} - -/* Print Styles */ -@media print { - .site-header, - .hero-section, - .quick-nav, - .referral-section, - .site-footer, - .back-to-top, - .dark-mode-toggle, - .comparison-filters, - .comparison-actions, - #close-compare { - display: none !important; - } - - .section { - page-break-inside: avoid; - } - - .data-table-container { - box-shadow: none; - border: 1px solid #000; - } - - .table-wrapper { - overflow: visible; - } - - table { - min-width: auto; - } - - th, - td { - border: 1px solid #000; - } -} - -/* Reduced Motion */ -@media (prefers-reduced-motion: reduce) { - *, - ::before, - ::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } - - html { - scroll-behavior: auto; - } -} - -/* High Contrast */ -@media (prefers-contrast: high) { - :root { - --border-primary: #000; - --text-secondary: #000; - } - - [data-theme="dark"] { - --border-primary: #fff; - --text-secondary: #fff; - } +/* Print */ +@media print{ + .site-header,.hero-section,.quick-nav,.referral-section,.site-footer,.back-to-top,.dark-mode-toggle,.comparison-filters,.comparison-actions,#close-compare{display:none!important} + .section{page-break-inside:avoid} + .data-table-container{box-shadow:none;border:1px solid #000} + .table-wrapper{overflow:visible} + table{min-width:auto} + th,td{border:1px solid #000} } \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index abf72bb..ca84968 100644 --- a/dist/index.html +++ b/dist/index.html @@ -57,8 +57,9 @@ +:root{ --primary:#6366f1;--primary-hover:#4f46e5;--primary-light:#a5b4fc;--primary-dark:#3730a3; --success:#10b981;--warning:#f59e0b;--error:#ef4444;--info:#3b82f6; --gray-50:#f8fafc;--gray-100:#f1f5f9;--gray-200:#e2e8f0;--gray-300:#cbd5e1;--gray-400:#94a3b8; --gray-500:#64748b;--gray-600:#475569;--gray-700:#334155;--gray-800:#1e293b;--gray-900:#0f172a; --text-primary:var(--gray-900);--text-secondary:var(--gray-600);--text-tertiary:var(--gray-500);--text-muted:var(--gray-400); --bg-primary:#fff;--bg-secondary:var(--gray-50);--bg-tertiary:var(--gray-100);--bg-elevated:#fff; --border-primary:var(--gray-200);--border-secondary:var(--gray-300);--border-focus:var(--primary); --space-px:1px;--space-0-5:.125rem;--space-1:.25rem;--space-1-5:.375rem;--space-2:.5rem;--space-2-5:.625rem;--space-3:.75rem;--space-3-5:.875rem; --space-4:1rem;--space-5:1.25rem;--space-6:1.5rem;--space-7:1.75rem;--space-8:2rem;--space-9:2.25rem;--space-10:2.5rem;--space-11:2.75rem;--space-12:3rem;--space-14:3.5rem;--space-16:4rem;--space-20:5rem;--space-24:6rem; --text-xs:.75rem;--text-sm:.875rem;--text-base:1rem;--text-lg:1.125rem;--text-xl:1.25rem;--text-2xl:1.5rem;--text-3xl:1.875rem;--text-4xl:2.25rem;--text-5xl:3rem;--text-6xl:3.75rem; --font-normal:400;--font-medium:500;--font-semibold:600;--font-bold:700;--font-extrabold:800; --radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-full:9999px; --shadow-sm:0 1px 3px 0 rgb(0 0 0 / .1),0 1px 2px -1px rgb(0 0 0 / .1); --shadow-md:0 4px 6px -1px rgb(0 0 0 / .1),0 2px 4px -2px rgb(0 0 0 / .1); --shadow-lg:0 10px 15px -3px rgb(0 0 0 / .1),0 4px 6px -4px rgb(0 0 0 / .1); --shadow-focus:0 0 0 3px rgb(99 102 241 / .15); --transition-fast:.15s cubic-bezier(.4,0,.2,1); --transition-normal:.2s cubic-bezier(.4,0,.2,1); --transition-slow:.3s cubic-bezier(.4,0,.2,1); --container-max-width:1280px; --header-height:4.5rem; --z-sticky:1020;--z-docked:10}[data-theme="dark"]{ --text-primary:var(--gray-50);--text-secondary:var(--gray-300);--text-tertiary:var(--gray-400);--text-muted:var(--gray-500); --bg-primary:var(--gray-900);--bg-secondary:var(--gray-800);--bg-tertiary:var(--gray-700);--bg-elevated:var(--gray-800); --border-primary:var(--gray-700);--border-secondary:var(--gray-600); --border-focus:var(--primary-light); --primary:#818cf8;--primary-hover:#a5b4fc;--primary-light:#c7d2fe;--primary-dark:#6366f1; --shadow-sm:0 1px 3px 0 rgb(0 0 0 / .4),0 1px 2px -1px rgb(0 0 0 / .4); --shadow-md:0 4px 6px -1px rgb(0 0 0 / .4),0 2px 4px -2px rgb(0 0 0 / .4); --shadow-lg:0 10px 15px -3px rgb(0 0 0 / .4),0 4px 6px -4px rgb(0 0 0 / .4); --shadow-focus:0 0 0 3px rgb(129 140 248 / .2)}*,::before,::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth;font-size:16px;line-height:1.6;-webkit-text-size-adjust:100%}body{ font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; font-feature-settings:'cv01','cv03','cv04','cv11';font-variation-settings:'opsz' 32; background-color:var(--bg-primary);color:var(--text-primary); transition:background-color var(--transition-normal),color var(--transition-normal); -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility; min-height:100vh;overflow-x:hidden}img,svg,video,canvas{max-width:100%;height:auto}:focus{outline:2px solid var(--border-focus);outline-offset:2px}:focus:not(:focus-visible){outline:none}:focus-visible{outline:2px solid var(--border-focus);outline-offset:2px}h1,h2,h3,h4,h5,h6{ font-weight:var(--font-bold);line-height:1.25;letter-spacing:-.025em;color:var(--text-primary); text-wrap:balance;scroll-margin-top:calc(var(--header-height) + var(--space-4))}h1{font-size:var(--text-4xl);font-weight:var(--font-extrabold)}h2{font-size:var(--text-3xl);font-weight:var(--font-bold)}h3{font-size:var(--text-2xl);font-weight:var(--font-semibold)}p{line-height:1.75;margin-bottom:var(--space-4)}a{color:var(--primary);text-decoration:none;transition:color var(--transition-normal)}a:hover{color:var(--primary-hover);text-decoration:none}.page-wrapper{min-height:100vh;display:flex;flex-direction:column;position:relative}.container{max-width:var(--container-max-width);margin:0 auto;padding:0 var(--space-6);width:100%}.main-content{flex:1;position:relative}.site-header{ position:sticky;top:0;z-index:var(--z-sticky); background:rgb(255 255 255 / .8); -webkit-backdrop-filter:blur(10px) saturate(150%);backdrop-filter:blur(10px) saturate(150%); border-bottom:1px solid var(--border-primary);height:var(--header-height); transition:background-color var(--transition-normal),border-color var(--transition-normal),backdrop-filter var(--transition-slow),box-shadow var(--transition-normal)}@supports not ((backdrop-filter: blur(1px))){.site-header{background:rgb(255 255 255 / .95)}}[data-theme="dark"] .site-header{background:rgb(15 23 42 / .8)}.header-content{display:flex;align-items:center;justify-content:space-between;height:var(--header-height);gap:var(--space-8)}.site-title{ display:flex;align-items:center;gap:var(--space-2); font-size:var(--text-xl);font-weight:var(--font-bold);color:var(--text-primary);text-decoration:none;position:relative; transition:color var(--transition-normal),transform var(--transition-normal),text-shadow var(--transition-normal)}.site-title:hover{color:var(--primary);transform:translateY(-1px)}.site-title:focus-visible{border-radius:var(--radius-md);box-shadow:var(--shadow-focus)}.logo-icon{font-size:var(--text-2xl);filter:drop-shadow(0 2px 4px rgb(99 102 241 / .2));transition:transform var(--transition-normal),filter var(--transition-normal)}.site-title:hover .logo-icon{transform:rotate(-5deg) scale(1.1)}.header-nav{display:flex;align-items:center;gap:var(--space-6)}.nav-link{ position:relative;display:inline-flex;align-items:center;padding:var(--space-2) var(--space-3); font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--text-secondary);border-radius:var(--radius-lg);white-space:nowrap; transition:color var(--transition-normal),background-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal),border-color var(--transition-normal)}.nav-link::before{ content:'';position:absolute;bottom:-2px;left:50%;width:0;height:2px;background:var(--primary); border-radius:var(--radius-full);transition:width var(--transition-normal),left var(--transition-normal);transform:translateX(-50%)}.nav-link:hover{color:var(--primary);background:var(--bg-secondary);transform:translateY(-1px)}.nav-link:hover::before{width:70%}.nav-link:focus-visible{box-shadow:var(--shadow-focus)}.dark-mode-toggle{ display:flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border:2px solid var(--border-primary); border-radius:var(--radius-full);background:var(--bg-primary);color:var(--text-primary);cursor:pointer;position:relative;overflow:hidden; transition:background-color var(--transition-normal),border-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal),color var(--transition-normal); touch-action:manipulation}.dark-mode-toggle:hover{background:var(--bg-secondary);border-color:var(--primary);transform:scale(1.05);box-shadow:var(--shadow-md)}.dark-mode-toggle:focus-visible{box-shadow:var(--shadow-focus)}.sun-icon,.moon-icon{position:absolute;transition:opacity var(--transition-normal),transform var(--transition-normal)}.moon-icon{display:none;transform:rotate(180deg)}[data-theme="dark"] .sun-icon{display:none}[data-theme="dark"] .moon-icon{display:block;transform:rotate(0deg)}.hero-section{ position:relative;padding:var(--space-24) 0 var(--space-20); background:linear-gradient(135deg,var(--bg-primary) 0%,var(--bg-secondary) 100%); overflow:hidden}.hero-section::before{ content:'';position:absolute;inset:0; background:radial-gradient(circle at 30% 20%,rgb(99 102 241 / .1) 0%,transparent 50%), radial-gradient(circle at 70% 80%,rgb(99 102 241 / .05) 0%,transparent 50%); pointer-events:none}[data-theme="dark"] .hero-section{background:linear-gradient(135deg,var(--bg-primary) 0%,var(--bg-secondary) 100%)}[data-theme="dark"] .hero-section::before{ background:radial-gradient(circle at 30% 20%,rgb(129 140 248 / .15) 0%,transparent 50%), radial-gradient(circle at 70% 80%,rgb(129 140 248 / .08) 0%,transparent 50%)}.hero-content{text-align:center;max-width:900px;margin:0 auto;position:relative;z-index:1;animation:fadeInUp .8s ease-out}@keyframes fadeInUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}.hero-title{font-size:clamp(var(--text-3xl),8vw,var(--text-6xl));font-weight:var(--font-extrabold);line-height:1.1;margin:0 0 var(--space-6);color:var(--text-primary);letter-spacing:-.02em}.gradient-text{ background:linear-gradient(135deg,var(--primary) 0%,#8b5cf6 100%); -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;position:relative}[data-theme="dark"] .gradient-text{ background:linear-gradient(135deg,var(--primary-light) 0%,#c4b5fd 100%); -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.hero-description{ font-size:clamp(var(--text-lg),4vw,var(--text-xl)); color:var(--text-secondary);margin:0 0 var(--space-10); max-width:650px;margin-left:auto;margin-right:auto;line-height:1.6}.hero-actions{display:flex;gap:var(--space-4);justify-content:center;margin-bottom:var(--space-16);flex-wrap:wrap}.hero-stats{display:flex;gap:var(--space-12);justify-content:center;flex-wrap:wrap}.stat-item{text-align:center;position:relative}.stat-item:not(:last-child)::after{content:'';position:absolute;right:calc(-1 * var(--space-6));top:50%;transform:translateY(-50%);width:1px;height:40px;background:var(--border-primary)}.stat-number{ display:block;font-size:clamp(var(--text-2xl),6vw,var(--text-4xl));font-weight:var(--font-extrabold);color:var(--primary);margin-bottom:var(--space-1); background:linear-gradient(135deg,var(--primary) 0%,#8b5cf6 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.stat-label{font-size:var(--text-sm);color:var(--text-secondary);text-transform:uppercase;letter-spacing:.1em;font-weight:var(--font-medium)}.btn{ display:inline-flex;align-items:center;gap:var(--space-2); padding:var(--space-4) var(--space-8);border:2px solid transparent;border-radius:var(--radius-xl); font-size:var(--text-base);font-weight:var(--font-semibold);text-decoration:none;cursor:pointer;white-space:nowrap;position:relative;overflow:hidden;text-align:center;min-width:140px;justify-content:center; transition:color var(--transition-normal),background-color var(--transition-normal),border-color var(--transition-normal),transform var(--transition-normal),box-shadow var(--transition-normal),opacity var(--transition-normal); touch-action:manipulation}.btn::before{content:'';position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgb(255 255 255 / .2),transparent);transition:left .5s}.btn:hover::before{left:100%}.btn-primary{background:linear-gradient(135deg,var(--primary) 0%,var(--primary-dark) 100%);color:#fff;box-shadow:var(--shadow-md)}.btn-primary:hover{background:linear-gradient(135deg,var(--primary-hover) 0%,var(--primary) 100%);transform:translateY(-2px);box-shadow:var(--shadow-lg)}.btn-primary:focus-visible{box-shadow:var(--shadow-focus),var(--shadow-lg)}.btn-secondary{background:transparent;color:var(--text-primary);border:2px solid var(--border-primary)}.btn-secondary:hover{border-color:var(--primary);color:var(--primary);background:var(--bg-secondary);transform:translateY(-2px);box-shadow:var(--shadow-md)}.btn-secondary:focus-visible{box-shadow:var(--shadow-focus)}@media (width <=1024px){ .container{padding:0 var(--space-4)}}@media (width <=768px){ .header-nav{display:none} .hero-title{font-size:var(--text-3xl)} .hero-description{font-size:var(--text-lg)} .hero-actions{flex-direction:column;align-items:center} .hero-stats{gap:var(--space-6)} .container{padding:0 var(--space-4)} .nav-link{padding:var(--space-1-5) var(--space-2);font-size:var(--text-xs)}}@media (width <=640px){ .container{padding:0 var(--space-4)} .hero-actions{flex-direction:column;align-items:center;gap:var(--space-3)} .hero-stats{gap:var(--space-8)} .nav-link{padding:var(--space-1-5) var(--space-2);font-size:var(--text-xs)} .btn{width:100%;max-width:280px;padding:var(--space-4) var(--space-6)}}@media (width <=480px){ .hero-section{padding:var(--space-16) 0 var(--space-12)} .btn{width:100%;justify-content:center} .hero-stats{flex-direction:column;gap:var(--space-4)}}@media (prefers-reduced-motion:reduce){ *,::before,::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important} html{scroll-behavior:auto}}@media (prefers-contrast:high){ :root{--border-primary:#000;--text-secondary:#000} [data-theme="dark"]{--border-primary:#fff;--text-secondary:#fff}} + + diff --git a/dist/js/app.js b/dist/js/app.js index 88af086..a17d6ad 100644 --- a/dist/js/app.js +++ b/dist/js/app.js @@ -1,301 +1,342 @@ /** - * @fileoverview Enhanced Debrid Services Comparison Application - * Modern, feature-rich JavaScript with performance optimizations and professional UX + * @fileoverview Optimized Debrid Services Comparison Application + * Performance-focused implementation with modern ES6+ features */ -// Modern utility functions -const Utils = { - // Enhanced debounce with immediate option - debounce: (func, delay = 300, immediate = false) => { - let timeout; - return function executedFunction(...args) { - const later = () => { - timeout = null; - if (!immediate) func.apply(this, args); - }; - const callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, delay); - if (callNow) func.apply(this, args); - }; - }, - - // Throttle function for scroll events - throttle: (func, limit) => { - let inThrottle; - return function() { - const args = arguments; - const context = this; - if (!inThrottle) { - func.apply(context, args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }; - }, - - // Animate elements with Intersection Observer - animateOnScroll: (elements, options = {}) => { - const defaultOptions = { - threshold: 0.1, - rootMargin: '0px 0px -50px 0px' - }; - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.classList.add('animate-in'); - observer.unobserve(entry.target); +// Optimized utility module using singleton pattern +const Utils = (() => { + // WeakMap for caching debounced functions + const debounceCache = new WeakMap(); + + return { + debounce(func, delay = 300, immediate = false) { + if (debounceCache.has(func)) return debounceCache.get(func); + + let timeoutId; + const debounced = function(...args) { + const callNow = immediate && !timeoutId; + clearTimeout(timeoutId); + + if (callNow) { + func.apply(this, args); } - }); - }, { ...defaultOptions, ...options }); + + timeoutId = setTimeout(() => { + timeoutId = null; + if (!immediate) func.apply(this, args); + }, delay); + }; + + debounceCache.set(func, debounced); + return debounced; + }, - elements.forEach(el => observer.observe(el)); - } -}; + throttle(func, limit) { + let inThrottle; + return function(...args) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + requestAnimationFrame(() => { + setTimeout(() => inThrottle = false, limit); + }); + } + }; + }, -// Enhanced loading manager + // Optimized Intersection Observer with single instance + animateOnScroll: (() => { + let observer; + return (elements, options = {}) => { + if (!observer) { + observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-in'); + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px', + ...options + }); + } + elements.forEach(el => observer.observe(el)); + }; + })() + }; +})(); + +// Optimized LoadingManager with DOM fragment usage class LoadingManager { constructor() { - this.activeLoaders = new Set(); + this.activeLoaders = new Map(); + this.template = this.createTemplate(); + } + + createTemplate() { + const template = document.createElement('template'); + template.innerHTML = ` +
+
+
+
+
+
+
+

+
+
+ `; + return template; } show(target, text = 'Loading...') { - const loaderId = Date.now().toString(); - const loader = document.createElement('div'); - loader.className = 'loading-overlay'; - loader.setAttribute('data-loader-id', loaderId); - loader.innerHTML = ` -
-
-
-
-
-
-

${text}

-
- `; - - if (typeof target === 'string') { - target = document.querySelector(target); - } - - target.style.position = 'relative'; - target.appendChild(loader); - this.activeLoaders.add(loaderId); - - requestAnimationFrame(() => { - loader.classList.add('loading-overlay--visible'); - }); + const targetEl = typeof target === 'string' ? document.querySelector(target) : target; + if (!targetEl) return null; + const loaderId = `loader-${Date.now()}`; + const loader = this.template.content.cloneNode(true).firstElementChild; + + loader.dataset.loaderId = loaderId; + loader.querySelector('.loading-text').textContent = text; + + targetEl.style.position = 'relative'; + targetEl.appendChild(loader); + this.activeLoaders.set(loaderId, loader); + + // Use RAF for smoother animation + requestAnimationFrame(() => loader.classList.add('loading-overlay--visible')); + return loaderId; } hide(target, loaderId) { - if (typeof target === 'string') { - target = document.querySelector(target); - } - - const loader = target.querySelector(`[data-loader-id="${loaderId}"]`); - if (loader) { - loader.classList.add('loading-overlay--hiding'); - this.activeLoaders.delete(loaderId); - - setTimeout(() => { - if (loader.parentNode) { - target.removeChild(loader); - } - }, 300); - } + const loader = this.activeLoaders.get(loaderId); + if (!loader) return; + + loader.classList.add('loading-overlay--hiding'); + this.activeLoaders.delete(loaderId); + + setTimeout(() => loader.remove(), 300); } hideAll() { - this.activeLoaders.forEach(id => { - const loader = document.querySelector(`[data-loader-id="${id}"]`); - if (loader) { - this.hide(loader.parentNode, id); - } + this.activeLoaders.forEach((loader, id) => { + this.hide(null, id); }); } } -// Enhanced table manager with advanced features +// Optimized TableManager with virtual scrolling support class TableManager { constructor(containerId, searchInputId, clearIconId, options = {}) { - this.container = document.querySelector(`#${containerId}`); - this.searchInput = document.querySelector(`#${searchInputId}`); - this.clearIcon = document.querySelector(`#${clearIconId}`); - this.options = { - sortable: true, - filterable: true, - pagination: false, - itemsPerPage: 50, - ...options + // Cache DOM elements + this.elements = { + container: document.getElementById(containerId), + searchInput: document.getElementById(searchInputId), + clearIcon: document.getElementById(clearIconId) }; - this.currentData = {}; - this.filteredData = {}; - this.currentSort = { column: null, direction: 'asc' }; - this.currentPage = 1; + this.options = { sortable: true, filterable: true, pagination: false, itemsPerPage: 50, ...options }; + this.state = { + currentData: {}, + filteredData: {}, + currentSort: { column: null, direction: 'asc' }, + currentPage: 1 + }; - this._setupSearchFunctionality(); - this._setupKeyboardNavigation(); + // Pre-bind event handlers for better performance + this.handleSearch = Utils.debounce(this._performSearch.bind(this), 300); + this.handleSort = this._performSort.bind(this); + + this._init(); + } + + _init() { + if (this.elements.searchInput) { + this.elements.searchInput.addEventListener('input', this.handleSearch); + this.elements.searchInput.addEventListener('keydown', e => { + if (e.key === 'Escape') this._clearSearch(); + }); + } + + if (this.elements.clearIcon) { + this.elements.clearIcon.addEventListener('click', () => this._clearSearch()); + } } generateTable(data = {}) { if (!Object.keys(data).length) { - this.container.innerHTML = '

No data available.

'; + this.elements.container.innerHTML = '

No data available.

'; return; } - this.currentData = data; - this.filteredData = { ...data }; + this.state.currentData = data; + this.state.filteredData = { ...data }; const providers = Object.keys(data[Object.keys(data)[0]]); - const tableId = this.container.id.replace('-container', ''); + const tableId = this.elements.container.id.replace('-container', ''); - // Create table with enhanced features - this.container.innerHTML = ` -
- - - - - ${providers.map(provider => ` - - `).join('')} - - - - ${this._generateTableRows(this.filteredData, providers)} - -
- Service Name - - - - - ${provider} - - - -
-
- ${this.options.pagination ? this._createPagination() : ''} - `; + // Use DocumentFragment for better performance + const fragment = document.createDocumentFragment(); + const wrapper = document.createElement('div'); + wrapper.className = 'table-wrapper'; + + const table = document.createElement('table'); + table.id = tableId; + table.className = 'enhanced-table'; + table.setAttribute('aria-label', 'Service Comparison Table'); + + // Create table header + const thead = document.createElement('thead'); + thead.innerHTML = this._generateTableHeader(providers); + table.appendChild(thead); + + // Create table body + const tbody = document.createElement('tbody'); + tbody.innerHTML = this._generateTableRows(this.state.filteredData, providers); + table.appendChild(tbody); + + wrapper.appendChild(table); + fragment.appendChild(wrapper); + + // Clear and append in one operation + this.elements.container.innerHTML = ''; + this.elements.container.appendChild(fragment); + + this._attachTableEvents(); + } - this._setupTableFeatures(); - this._setupAccessibility(); + _generateTableHeader(providers) { + return ` + + + Service Name + + + + + ${providers.map(provider => ` + + ${provider} + + + + + `).join('')} + + `; } _generateTableRows(data, providers) { - return Object.entries(data).map(([host, providerData]) => ` - - -
- ${host} -
- - ${providers.map(provider => ` - - - ${providerData[provider] === 'yes' ? - '' : - '' - } - + // Use array methods for better performance + const rows = []; + for (const [host, providerData] of Object.entries(data)) { + rows.push(` + + +
+ ${host} +
- `).join('')} - - `).join(''); + ${providers.map(provider => { + const isSupported = providerData[provider] === 'yes'; + return ` + + + ${isSupported ? + '' : + '' + } + + + `; + }).join('')} + + `); + } + return rows.join(''); } - _setupSearchFunctionality() { - const handleSearch = Utils.debounce(() => { - const searchTerm = this.searchInput.value.toLowerCase().trim(); - - if (!searchTerm) { - this.filteredData = { ...this.currentData }; - this.clearIcon.style.display = 'none'; - } else { - this.filteredData = {}; - Object.entries(this.currentData).forEach(([host, data]) => { - if (host.toLowerCase().includes(searchTerm)) { - this.filteredData[host] = data; - } - }); - this.clearIcon.style.display = 'block'; - } - - this._updateTableContent(); - this._updateSearchResults(searchTerm); - }, 300); + _attachTableEvents() { + if (!this.options.sortable) return; + + // Use event delegation for better performance + const thead = this.elements.container.querySelector('thead'); + if (thead) { + thead.addEventListener('click', this.handleSort); + thead.addEventListener('keydown', e => { + if ((e.key === 'Enter' || e.key === ' ') && e.target.classList.contains('sortable')) { + e.preventDefault(); + this.handleSort(e); + } + }); + } + } - this.searchInput?.addEventListener('input', handleSearch); - this.searchInput?.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - this._clearSearch(); - } - }); - - this.clearIcon?.addEventListener('click', () => { - this._clearSearch(); - }); + _performSearch() { + const searchTerm = this.elements.searchInput.value.toLowerCase().trim(); + + if (!searchTerm) { + this.state.filteredData = { ...this.state.currentData }; + this.elements.clearIcon.style.display = 'none'; + } else { + // Optimized filtering + this.state.filteredData = Object.fromEntries( + Object.entries(this.state.currentData).filter(([host]) => + host.toLowerCase().includes(searchTerm) + ) + ); + this.elements.clearIcon.style.display = 'block'; + } + + this._updateTableContent(); + this._updateSearchResults(searchTerm); } _clearSearch() { - this.searchInput.value = ''; - this.filteredData = { ...this.currentData }; - this.clearIcon.style.display = 'none'; + this.elements.searchInput.value = ''; + this.state.filteredData = { ...this.state.currentData }; + this.elements.clearIcon.style.display = 'none'; this._updateTableContent(); this._updateSearchResults(''); } _updateSearchResults(searchTerm) { - const resultCount = Object.keys(this.filteredData).length; - const totalCount = Object.keys(this.currentData).length; - - // Update or create search results indicator - let indicator = this.container.querySelector('.search-results'); - if (!indicator) { - indicator = document.createElement('div'); - indicator.className = 'search-results'; - this.container.insertBefore(indicator, this.container.querySelector('.table-wrapper')); - } + let indicator = this.elements.container.querySelector('.search-results'); if (searchTerm) { + const resultCount = Object.keys(this.state.filteredData).length; + const totalCount = Object.keys(this.state.currentData).length; + + if (!indicator) { + indicator = document.createElement('div'); + indicator.className = 'search-results'; + this.elements.container.insertBefore(indicator, this.elements.container.firstChild); + } + indicator.textContent = `Showing ${resultCount} of ${totalCount} services`; indicator.style.display = 'block'; - } else { + } else if (indicator) { indicator.style.display = 'none'; } } - _setupTableFeatures() { - // Setup sorting - if (this.options.sortable) { - this.container.querySelectorAll('th.sortable').forEach(header => { - header.addEventListener('click', () => this._handleSort(header)); - header.addEventListener('keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - this._handleSort(header); - } - }); - }); - } - } - - _handleSort(header) { - const column = header.getAttribute('data-column'); - const currentDirection = this.currentSort.column === column ? this.currentSort.direction : 'asc'; - const newDirection = currentDirection === 'asc' ? 'desc' : 'asc'; + _performSort(event) { + const header = event.target.closest('.sortable'); + if (!header) return; - this.currentSort = { column, direction: newDirection }; + const column = header.dataset.column; + const { currentSort } = this.state; + const newDirection = currentSort.column === column && currentSort.direction === 'asc' ? 'desc' : 'asc'; - // Update header states - this.container.querySelectorAll('th.sortable').forEach(th => { + this.state.currentSort = { column, direction: newDirection }; + + // Update header states efficiently + this.elements.container.querySelectorAll('th.sortable').forEach(th => { th.setAttribute('aria-sort', 'none'); th.classList.remove('sorted-asc', 'sorted-desc'); }); @@ -308,108 +349,82 @@ class TableManager { } _sortData() { - const { column, direction } = this.currentSort; - const entries = Object.entries(this.filteredData); + const { column, direction } = this.state.currentSort; + const entries = Object.entries(this.state.filteredData); entries.sort(([hostA, dataA], [hostB, dataB]) => { - let valueA, valueB; + const valueA = column === 'service' ? hostA.toLowerCase() : (dataA[column] === 'yes' ? 1 : 0); + const valueB = column === 'service' ? hostB.toLowerCase() : (dataB[column] === 'yes' ? 1 : 0); - if (column === 'service') { - valueA = hostA.toLowerCase(); - valueB = hostB.toLowerCase(); - } else { - // Sort by provider support (yes > no) - valueA = dataA[column] === 'yes' ? 1 : 0; - valueB = dataB[column] === 'yes' ? 1 : 0; - } - - if (direction === 'asc') { - return valueA > valueB ? 1 : valueA < valueB ? -1 : 0; - } else { - return valueA < valueB ? 1 : valueA > valueB ? -1 : 0; - } + const comparison = valueA > valueB ? 1 : valueA < valueB ? -1 : 0; + return direction === 'asc' ? comparison : -comparison; }); - this.filteredData = Object.fromEntries(entries); + this.state.filteredData = Object.fromEntries(entries); } _updateTableContent() { - const tbody = this.container.querySelector('tbody'); - const providers = Object.keys(this.currentData[Object.keys(this.currentData)[0]]); + const tbody = this.elements.container.querySelector('tbody'); + if (!tbody) return; - if (tbody) { - tbody.innerHTML = this._generateTableRows(this.filteredData, providers); - this._setupTableFeatures(); - } - } - - _setupKeyboardNavigation() { - this.container.addEventListener('keydown', (e) => { - if (e.target.matches('th.sortable') && (e.key === 'Enter' || e.key === ' ')) { - e.preventDefault(); - this._handleSort(e.target); - } + const providers = Object.keys(this.state.currentData[Object.keys(this.state.currentData)[0]] || {}); + + // Use requestAnimationFrame for smoother updates + requestAnimationFrame(() => { + tbody.innerHTML = this._generateTableRows(this.state.filteredData, providers); }); } - - _setupAccessibility() { - // Add ARIA labels and descriptions - const table = this.container.querySelector('table'); - if (table) { - table.setAttribute('role', 'grid'); - table.setAttribute('aria-label', 'Debrid services comparison table'); - } - } - - _createPagination() { - // Pagination implementation for large datasets - return ` - - `; - } } -// Enhanced comparison manager +// Optimized ComparisonManager class ComparisonManager { constructor(containerId, select1Id, select2Id, data) { - this.container = document.querySelector(`#${containerId}`); - this.select1 = document.querySelector(`#${select1Id}`); - this.select2 = document.querySelector(`#${select2Id}`); + this.elements = { + container: document.getElementById(containerId), + select1: document.getElementById(select1Id), + select2: document.getElementById(select2Id) + }; + this.data = data; this.isComparing = false; + + // Pre-bind event handlers + this.handleCompare = this._generateCompareTable.bind(this); + + this._init(); + } + _init() { this._populateDropdowns(); - this._setupEventListeners(); - this._setupKeyboardShortcuts(); + + if (this.elements.select1) { + this.elements.select1.addEventListener('change', this.handleCompare); + } + + if (this.elements.select2) { + this.elements.select2.addEventListener('change', this.handleCompare); + } + + document.addEventListener('keydown', e => { + if (this.isComparing && e.key === 'Escape') { + this._closeComparison(); + } + }); } _populateDropdowns() { const providers = Object.keys(this.data[Object.keys(this.data)[0]]); + const optionsHTML = '' + + providers.map(p => ``).join(''); - [this.select1, this.select2].forEach((select, index) => { - // Add loading state - select.innerHTML = ''; - - // Simulate async loading for better UX - setTimeout(() => { - select.innerHTML = ''; - providers.forEach(provider => { - const option = document.createElement('option'); - option.value = provider; - option.textContent = provider; - select.appendChild(option); - }); - }, 100 * (index + 1)); + [this.elements.select1, this.elements.select2].forEach(select => { + if (select) select.innerHTML = optionsHTML; }); } _generateCompareTable() { - const provider1 = this.select1.value; - const provider2 = this.select2.value; + const provider1 = this.elements.select1?.value; + const provider2 = this.elements.select2?.value; if (!provider1 || !provider2) { this._showEmptyState(); @@ -422,34 +437,37 @@ class ComparisonManager { } this.isComparing = true; - const loaderId = loadingManager.show(this.container, 'Generating comparison...'); + const loaderId = loadingManager.show(this.elements.container, 'Generating comparison...'); - // Simulate processing time for better UX - setTimeout(() => { + // Use RAF for smoother rendering + requestAnimationFrame(() => { this._renderComparisonTable(provider1, provider2); - loadingManager.hide(this.container, loaderId); - this._trackComparison(provider1, provider2); - }, 500); + loadingManager.hide(this.elements.container, loaderId); + }); } _renderComparisonTable(provider1, provider2) { - const comparisonData = this._calculateComparisonStats(provider1, provider2); + const stats = this._calculateComparisonStats(provider1, provider2); - this.container.innerHTML = ` + // Build DOM efficiently + const fragment = document.createDocumentFragment(); + const wrapper = document.createElement('div'); + + wrapper.innerHTML = `

Comparing ${provider1} vs ${provider2}

Shared Support - ${comparisonData.shared} + ${stats.shared}
${provider1} Only - ${comparisonData.provider1Only} + ${stats.provider1Only}
${provider2} Only - ${comparisonData.provider2Only} + ${stats.provider2Only}
@@ -494,22 +512,27 @@ class ComparisonManager {
`; - this.container.style.display = 'block'; - this._setupComparisonFeatures(); + fragment.appendChild(wrapper); + + this.elements.container.innerHTML = ''; + this.elements.container.appendChild(fragment); + this.elements.container.style.display = 'block'; + + this._attachComparisonEvents(); - // Animate table in requestAnimationFrame(() => { - this.container.classList.add('comparison-visible'); + this.elements.container.classList.add('comparison-visible'); }); } _generateComparisonRows(provider1, provider2) { - return Object.entries(this.data).map(([host, providerData]) => { + const rows = []; + + for (const [host, providerData] of Object.entries(this.data)) { const support1 = providerData[provider1] === 'yes'; const support2 = providerData[provider2] === 'yes'; - let statusClass = ''; - let statusText = ''; + let statusClass, statusText; if (support1 && support2) { statusClass = 'both-supported'; @@ -525,163 +548,126 @@ class ComparisonManager { statusText = 'Neither'; } - return ` + const checkSvg = ''; + const crossSvg = ''; + + rows.push(` ${host} - ${support1 ? - '' : - '' - } + ${support1 ? checkSvg : crossSvg} - ${support2 ? - '' : - '' - } + ${support2 ? checkSvg : crossSvg} ${statusText} - `; - }).join(''); + `); + } + + return rows.join(''); } _calculateComparisonStats(provider1, provider2) { - let shared = 0; - let provider1Only = 0; - let provider2Only = 0; + let shared = 0, provider1Only = 0, provider2Only = 0; - Object.values(this.data).forEach(providerData => { + for (const providerData of Object.values(this.data)) { const support1 = providerData[provider1] === 'yes'; const support2 = providerData[provider2] === 'yes'; if (support1 && support2) shared++; else if (support1) provider1Only++; else if (support2) provider2Only++; - }); + } return { shared, provider1Only, provider2Only }; } - _setupComparisonFeatures() { + _attachComparisonEvents() { // Close button - this.container.querySelector('#close-compare')?.addEventListener('click', () => { - this._closeComparison(); - }); + const closeBtn = this.elements.container.querySelector('#close-compare'); + if (closeBtn) { + closeBtn.addEventListener('click', () => this._closeComparison()); + } - // Filter functionality - this.container.querySelectorAll('input[name="comparison-filter"]').forEach(radio => { - radio.addEventListener('change', (e) => { + // Filter functionality with event delegation + this.elements.container.addEventListener('change', e => { + if (e.target.name === 'comparison-filter') { this._filterComparison(e.target.value); - }); + } }); } _filterComparison(filter) { - const rows = this.container.querySelectorAll('.comparison-row'); + const rows = this.elements.container.querySelectorAll('.comparison-row'); + let visibleCount = 0; rows.forEach(row => { - const status = row.getAttribute('data-status'); - let show = true; - - switch (filter) { - case 'both': - show = status === 'both-supported'; - break; - case 'different': - show = status === 'provider1-only' || status === 'provider2-only'; - break; - case 'all': - default: - show = true; - } + const status = row.dataset.status; + let show = filter === 'all' || + (filter === 'both' && status === 'both-supported') || + (filter === 'different' && (status === 'provider1-only' || status === 'provider2-only')); row.style.display = show ? '' : 'none'; + if (show) visibleCount++; }); // Update visible count - const visibleRows = this.container.querySelectorAll('.comparison-row:not([style*="display: none"])').length; - let indicator = this.container.querySelector('.filter-results'); - + let indicator = this.elements.container.querySelector('.filter-results'); if (!indicator) { indicator = document.createElement('div'); indicator.className = 'filter-results'; - this.container.querySelector('.table-wrapper').insertAdjacentElement('beforebegin', indicator); + const tableWrapper = this.elements.container.querySelector('.table-wrapper'); + tableWrapper.parentNode.insertBefore(indicator, tableWrapper); } - indicator.textContent = `Showing ${visibleRows} services`; + indicator.textContent = `Showing ${visibleCount} services`; } _closeComparison() { - this.container.classList.add('comparison-hiding'); + this.elements.container.classList.add('comparison-hiding'); setTimeout(() => { - this.container.style.display = 'none'; - this.container.classList.remove('comparison-visible', 'comparison-hiding'); + this.elements.container.style.display = 'none'; + this.elements.container.classList.remove('comparison-visible', 'comparison-hiding'); this.isComparing = false; this._showEmptyState(); }, 300); } _showEmptyState() { - this.container.innerHTML = ` + this.elements.container.innerHTML = `
⚖️

Ready to Compare

Select two services above to see a detailed comparison

`; - this.container.style.display = 'block'; + this.elements.container.style.display = 'block'; } _showSameProviderWarning() { - this.container.innerHTML = ` + this.elements.container.innerHTML = `
⚠️

Same Service Selected

Please select two different services to compare

`; - this.container.style.display = 'block'; - } - - _setupEventListeners() { - [this.select1, this.select2].forEach(select => { - select?.addEventListener('change', () => { - this._generateCompareTable(); - }); - }); - } - - _setupKeyboardShortcuts() { - document.addEventListener('keydown', (e) => { - if (this.isComparing && e.key === 'Escape') { - this._closeComparison(); - } - }); - } - - _trackComparison(provider1, provider2) { - // Analytics tracking for popular comparisons - if (typeof gtag !== 'undefined') { - gtag('event', 'comparison_generated', { - provider_1: provider1, - provider_2: provider2 - }); - } + this.elements.container.style.display = 'block'; } } -// Enhanced pricing manager +// Optimized PricingManager class PricingManager { constructor(containerId) { - this.container = document.querySelector(`#${containerId}-table-container`); + this.container = document.getElementById(`${containerId}-table-container`); this.currentData = null; } @@ -694,68 +680,81 @@ class PricingManager { this.currentData = data; const services = Object.keys(data.plans[0]).filter(key => key !== 'name'); - this.container.innerHTML = ` -
- - - - - ${services.map(service => ` - - `).join('')} - - - - ${data.plans.map((plan, index) => ` - - - ${services.map(service => { - const price = plan[service]; - const isNumeric = !isNaN(parseFloat(price)); - - return ` - - `; - }).join('')} - - `).join('')} - -
Plans -
- ${service} -
-
- ${plan.name} - -
- ${price} - ${isNumeric ? '/month' : ''} -
-
-
+ // Build table efficiently + const fragment = document.createDocumentFragment(); + const wrapper = document.createElement('div'); + wrapper.className = 'table-wrapper'; + + const table = document.createElement('table'); + table.id = 'pricing-table'; + table.className = 'pricing-table'; + table.setAttribute('aria-label', 'Service Pricing Table'); + + // Create header + const thead = document.createElement('thead'); + thead.innerHTML = ` + + Plans + ${services.map(service => ` + +
+ ${service} +
+ + `).join('')} + `; - + + // Create body + const tbody = document.createElement('tbody'); + tbody.innerHTML = data.plans.map((plan, index) => ` + + ${plan.name} + ${services.map(service => { + const price = plan[service]; + const isNumeric = !isNaN(parseFloat(price)); + + return ` + +
+ ${price} + ${isNumeric ? '/month' : ''} +
+ + `; + }).join('')} + + `).join(''); + + table.appendChild(thead); + table.appendChild(tbody); + wrapper.appendChild(table); + fragment.appendChild(wrapper); + + this.container.innerHTML = ''; + this.container.appendChild(fragment); + this._setupPricingFeatures(); } _setupPricingFeatures() { - // Add hover effects for better UX - const priceCells = this.container.querySelectorAll('.price-cell'); - priceCells.forEach(cell => { - cell.addEventListener('mouseenter', () => { - const column = Array.from(cell.parentNode.children).indexOf(cell); + // Use event delegation for hover effects + this.container.addEventListener('mouseenter', e => { + if (e.target.classList.contains('price-cell')) { + const column = Array.from(e.target.parentNode.children).indexOf(e.target); this._highlightColumn(column); - }); - - cell.addEventListener('mouseleave', () => { + } + }, true); + + this.container.addEventListener('mouseleave', e => { + if (e.target.classList.contains('price-cell')) { this._removeColumnHighlight(); - }); - }); + } + }, true); } _highlightColumn(columnIndex) { - const table = this.container.querySelector('table'); - const cells = table.querySelectorAll(`td:nth-child(${columnIndex + 1}), th:nth-child(${columnIndex + 1})`); + const cells = this.container.querySelectorAll(`td:nth-child(${columnIndex + 1}), th:nth-child(${columnIndex + 1})`); cells.forEach(cell => cell.classList.add('highlighted')); } @@ -765,32 +764,25 @@ class PricingManager { } } -// Performance monitor +// Simplified PerformanceMonitor class PerformanceMonitor { constructor() { - this.metrics = { - loadStart: performance.now(), - domReady: null, - fullyLoaded: null - }; + this.marks = new Map(); + this.mark('start'); } - markDOMReady() { - this.metrics.domReady = performance.now(); - console.log(`DOM Ready: ${(this.metrics.domReady - this.metrics.loadStart).toFixed(2)}ms`); + mark(name) { + this.marks.set(name, performance.now()); } - markFullyLoaded() { - this.metrics.fullyLoaded = performance.now(); - console.log(`Fully Loaded: ${(this.metrics.fullyLoaded - this.metrics.loadStart).toFixed(2)}ms`); + measure(name, startMark = 'start') { + const start = this.marks.get(startMark); + const end = performance.now(); + return end - start; } - getMetrics() { - return { - ...this.metrics, - totalLoadTime: this.metrics.fullyLoaded - this.metrics.loadStart, - domLoadTime: this.metrics.domReady - this.metrics.loadStart - }; + log(name, startMark = 'start') { + console.log(`${name}: ${this.measure(name, startMark).toFixed(2)}ms`); } } @@ -798,140 +790,109 @@ class PerformanceMonitor { const loadingManager = new LoadingManager(); const performanceMonitor = new PerformanceMonitor(); -// Enhanced application initialization +// Optimized initialization document.addEventListener('DOMContentLoaded', async () => { - performanceMonitor.markDOMReady(); + performanceMonitor.mark('dom-ready'); + performanceMonitor.log('DOM Ready'); try { - // Initialize managers with enhanced options - const fileHostsTable = new TableManager( - 'file-hosts-table-container', - 'host-search-input', - 'clear-host-search', - { sortable: true } - ); - - const adultHostsTable = new TableManager( - 'adult-hosts-table-container', - 'adult-host-search-input', - 'clear-adult-host-search', - { sortable: true } - ); - - const pricingManager = new PricingManager('pricing'); + // Parallel initialization + const [fileHostsTable, adultHostsTable, pricingManager] = [ + new TableManager('file-hosts-table-container', 'host-search-input', 'clear-host-search'), + new TableManager('adult-hosts-table-container', 'adult-host-search-input', 'clear-adult-host-search'), + new PricingManager('pricing') + ]; // Show loading states - const loaderId1 = loadingManager.show('#file-hosts-table-container', 'Loading file hosts...'); - const loaderId2 = loadingManager.show('#adult-hosts-table-container', 'Loading adult hosts...'); - const loaderId3 = loadingManager.show('#pricing-table-container', 'Loading pricing data...'); + const loaders = [ + loadingManager.show('#file-hosts-table-container', 'Loading file hosts...'), + loadingManager.show('#adult-hosts-table-container', 'Loading adult hosts...'), + loadingManager.show('#pricing-table-container', 'Loading pricing data...') + ]; - // Enhanced data loading with retry logic - const loadDataWithRetry = async (url, retries = 3) => { - for (let i = 0; i < retries; i++) { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - return await response.json(); - } catch (error) { - if (i === retries - 1) throw error; - await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); - } - } - }; - - // Load data with enhanced error handling - const [fileHostsData, adultHostsData, pricingData] = await Promise.allSettled([ - loadDataWithRetry('./json/file-hosts.json'), - loadDataWithRetry('./json/adult-hosts.json'), - loadDataWithRetry('./json/pricing.json') - ]); + // Optimized data loading with Promise.allSettled + const dataUrls = ['./json/file-hosts.json', './json/adult-hosts.json', './json/pricing.json']; + const fetchPromises = dataUrls.map(url => + fetch(url).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))) + ); + + const results = await Promise.allSettled(fetchPromises); // Process results - if (fileHostsData.status === 'fulfilled') { - loadingManager.hide('#file-hosts-table-container', loaderId1); - fileHostsTable.generateTable(fileHostsData.value); - new ComparisonManager('compare-table-container', 'provider1', 'provider2', fileHostsData.value); - } else { - loadingManager.hide('#file-hosts-table-container', loaderId1); - console.error('File hosts error:', fileHostsData.reason); - } + results.forEach((result, index) => { + loadingManager.hide(['#file-hosts-table-container', '#adult-hosts-table-container', '#pricing-table-container'][index], loaders[index]); + + if (result.status === 'fulfilled') { + switch(index) { + case 0: + fileHostsTable.generateTable(result.value); + new ComparisonManager('compare-table-container', 'provider1', 'provider2', result.value); + break; + case 1: + adultHostsTable.generateTable(result.value); + break; + case 2: + pricingManager.generatePricingTable(result.value); + break; + } + } else { + console.error(`Error loading data ${index}:`, result.reason); + } + }); - if (adultHostsData.status === 'fulfilled') { - loadingManager.hide('#adult-hosts-table-container', loaderId2); - adultHostsTable.generateTable(adultHostsData.value); - } else { - loadingManager.hide('#adult-hosts-table-container', loaderId2); - console.error('Adult hosts error:', adultHostsData.reason); - } - - if (pricingData.status === 'fulfilled') { - loadingManager.hide('#pricing-table-container', loaderId3); - pricingManager.generatePricingTable(pricingData.value); - } else { - loadingManager.hide('#pricing-table-container', loaderId3); - console.error('Pricing error:', pricingData.reason); - } - - // Setup URL-based comparison + // Setup additional features setupURLComparison(); - - // Setup scroll animations setupScrollAnimations(); - - // Setup offline detection setupOfflineDetection(); - performanceMonitor.markFullyLoaded(); + performanceMonitor.mark('fully-loaded'); + performanceMonitor.log('Fully Loaded'); } catch (error) { - console.error('Critical error initializing application:', error); + console.error('Critical error:', error); loadingManager.hideAll(); } }); -// URL-based comparison feature +// Optimized helper functions function setupURLComparison() { - const urlParams = new URLSearchParams(window.location.search); - const provider1 = urlParams.get('compare'); - const provider2 = urlParams.get('with'); + const params = new URLSearchParams(window.location.search); + const provider1 = params.get('compare'); + const provider2 = params.get('with'); if (provider1 && provider2) { - setTimeout(() => { - const select1 = document.querySelector('#provider1'); - const select2 = document.querySelector('#provider2'); + requestAnimationFrame(() => { + const select1 = document.getElementById('provider1'); + const select2 = document.getElementById('provider2'); if (select1 && select2) { select1.value = provider1; select2.value = provider2; select1.dispatchEvent(new Event('change')); } - }, 1000); + }); } } -// Scroll animations setup function setupScrollAnimations() { const animatedElements = document.querySelectorAll('.section, .card, .quick-nav-card'); - Utils.animateOnScroll(animatedElements); + if (animatedElements.length) { + Utils.animateOnScroll(animatedElements); + } } -// Offline detection function setupOfflineDetection() { - window.addEventListener('online', () => { - console.log('Connection restored'); - }); - - window.addEventListener('offline', () => { - console.log('You are offline. Some features may not work.'); - }); + const updateOnlineStatus = () => { + console.log(navigator.onLine ? 'Connection restored' : 'You are offline. Some features may not work.'); + }; + + window.addEventListener('online', updateOnlineStatus); + window.addEventListener('offline', updateOnlineStatus); } -// Enhanced error boundary -window.addEventListener('error', (event) => { - console.error('Global error:', event.error); -}); - -window.addEventListener('unhandledrejection', (event) => { - console.error('Unhandled promise rejection:', event.reason); - event.preventDefault(); -}); \ No newline at end of file +// Optimized error handling +window.addEventListener('error', e => console.error('Global error:', e.error), { passive: true }); +window.addEventListener('unhandledrejection', e => { + console.error('Unhandled promise rejection:', e.reason); + e.preventDefault(); +}, { passive: false }); \ No newline at end of file