diff --git a/dist/index.html b/dist/index.html index 6eec829..ac79bb4 100644 --- a/dist/index.html +++ b/dist/index.html @@ -7,269 +7,436 @@ - Debrid Services Comparison - + Debrid Services Comparison - Compare Premium Download Services + + + + + content="Compare features, pricing, and supported hosts across major debrid services including AllDebrid, Real-Debrid, LinkSnappy, Premiumize, Debrid-Link, TorBox and Mega-Debrid."> + content="debrid services comparison, AllDebrid, Real-Debrid, LinkSnappy, Premiumize, Debrid-Link, TorBox, Mega-Debrid, file hosting, download services, premium downloads"> - + + + content="Compare features, pricing, and supported hosts across major debrid services. Find the best premium download service for your needs."> - - + content="Compare features, pricing, and supported hosts across major debrid services. Find the best premium download service for your needs."> - - - - -
-
-

A concise comparison of pricing and supported hosts for popular debrid services including AllDebrid, Real-Debrid, LinkSnappy, - Premiumize, Debrid-Link, TorBox, and Mega-Debrid. -

-
- -
-

Table of Contents

- -
- -
-

Getting Started

-

Support This Project

-

If you find this comparison helpful and decide to sign up for a service, please consider using the referral links below. This helps support the maintenance and updates of this project. Thank you!

- - -
- -
-

Pricing

-
- -
Loading pricing data...
-
-
-

[ * ] = 7 Days free trial at Alldebrid requires phone verification.

-
-
-

Up-to-date Pricing

-

Please note that pricing can change. It is highly recommended to verify the latest pricing directly on the official websites before making a decision.

-

You can check the most current pricing for each individual service at:

- -
-
- -
-

Available Hosts

- + + -
-

Compare Providers

-
-
- - -
-
- - +
+ +
+
+
+

+ Compare Premium Debrid Services +

+

+ Find the perfect premium download service for your needs. Compare pricing, features, and supported hosts across all major debrid providers. +

+ +
+
+ 7 + Services +
+
+ 300+ + File Hosts +
+ +
-
-
-

Comparison

-
- -
Loading comparison...
-
-
+
-
-

Adult Hosts

-
-
- - + + +
+
+ +
+
+
+

Loading file hosts...

+
-
- -
Loading adult hosts...
+ + + +
+
+
+

Compare Services

+

Select two services to see a detailed side-by-side comparison

+
+ +
+
+ +
+ +
+
+
VS
+
+ +
+ +
+
+
+ +
+
+
⚖️
+

Ready to Compare

+

Select two services above to see a detailed comparison

+
+
-
+ -
-

Live status

-

For real-time information on host availability and service status, please refer to the official status pages.These pages provide the most accurate and up-to-date details on any potential issues or maintenance.

-

Up-to-date list of hosts for individual services can be tracked from:

- -
-
-

Important Notes

- + +
+
+
+

Adult Content Hosts

+

Supported adult content hosting services

+
+ +
+
+ + + + + + +
+
-
-
-

Useful Resources

- -
- +
+
+
+

Loading adult hosts...

+
+
+ + -
+ +
+
+
+

Service Status

+

Check real-time status and official host lists

+
+ + +
+
+ - - - - - + + + - \ No newline at end of file diff --git a/src/css/styles.css b/src/css/styles.css index 870e943..20f526f 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -1,126 +1,242 @@ +/* Design System & CSS Custom Properties */ :root { - --primary: #2563eb; - --primary-hover: #1d4ed8; + /* Color Palette */ + --primary: #6366f1; + --primary-hover: #4f46e5; + --primary-light: #a5b4fc; --secondary: #64748b; --secondary-hover: #475569; - --text-primary: #1e293b; - --text-secondary: #64748b; - --bg-light: #f8fafc; - --bg-white: #ffffff; - --border-color: #e2e8f0; - --border-radius: .75rem; - --box-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1); - --transition: all .2s cubic-bezier(.4, 0, .2, 1); - --font-family-base: 'Inter', system-ui, sans-serif + --success: #10b981; + --warning: #f59e0b; + --error: #ef4444; + --info: #3b82f6; + + /* Neutral Colors */ + --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; + + /* Semantic Colors */ + --text-primary: var(--gray-900); + --text-secondary: var(--gray-600); + --text-tertiary: var(--gray-500); + --bg-primary: #ffffff; + --bg-secondary: var(--gray-50); + --bg-tertiary: var(--gray-100); + --border-primary: var(--gray-200); + --border-secondary: var(--gray-300); + + /* Spacing Scale */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + --space-16: 4rem; + --space-20: 5rem; + --space-24: 6rem; + + /* Typography Scale */ + --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; + + /* Font Weights */ + --font-light: 300; + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.5rem; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --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); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 200ms ease; + --transition-slow: 300ms ease; + + /* Layout */ + --container-max-width: 1280px; + --header-height: 4rem; + + /* Z-Index Scale */ + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal: 1040; + --z-popover: 1050; + --z-tooltip: 1060; } +/* Dark Theme */ [data-theme="dark"] { - --primary: #3b82f6; - --primary-hover: #60a5fa; - --secondary: #94a3b8; - --secondary-hover: #cbd5e1; - --text-primary: #f1f5f9; - --text-secondary: #94a3b8; - --bg-white: #0f172a; - --bg-light: #1e293b; - --border-color: #334155; - --box-shadow: 0 1px 3px 0 rgb(0 0 0 / .5), 0 1px 2px -1px rgb(0 0 0 / .5); + --text-primary: var(--gray-100); + --text-secondary: var(--gray-300); + --text-tertiary: var(--gray-400); + --bg-primary: var(--gray-900); + --bg-secondary: var(--gray-800); + --bg-tertiary: var(--gray-700); + --border-primary: var(--gray-700); + --border-secondary: var(--gray-600); + --primary: #818cf8; + --primary-hover: #a5b4fc; +} + +/* Reset & Base Styles */ +*, +*::before, +*::after { + box-sizing: border-box; } html { scroll-behavior: smooth; font-size: 16px; + line-height: 1.5; } body { - font-family: var(--font-family-base); - line-height: 1.6; + font-family: 'Inter', system-ui, -apple-system, sans-serif; + font-feature-settings: 'cv01', 'cv03', 'cv04', 'cv11'; margin: 0; padding: 0; - background-color: var(--bg-white); + background-color: var(--bg-primary); color: var(--text-primary); - transition: background-color .3s, color .3s; + transition: background-color var(--transition-normal), color var(--transition-normal); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Layout Components */ +.page-wrapper { + min-height: 100vh; + display: flex; + flex-direction: column; } .container { - max-width: 1200px; + max-width: var(--container-max-width); margin: 0 auto; - padding: 20px; + padding: 0 var(--space-6); } -.card { - background-color: var(--bg-white); - border-radius: var(--border-radius); - box-shadow: var(--box-shadow); - border: 1px solid var(--border-color); - transition: var(--transition) -} - -.card:hover { - transform: translateY(-2px); - box-shadow: 0 10px 15px -3px rgb(0 0 0 / .1) +.main-content { + flex: 1; } +/* Header */ .site-header { position: sticky; - background-color: var(--bg-white); - padding: 1rem 0; - border-bottom: 1px solid var(--border-color); top: 0; - z-index: 100; - backdrop-filter: blur(8px); - box-shadow: 0 1px 3px 0 rgb(0 0 0 / .1); + z-index: var(--z-sticky); + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border-primary); + height: var(--header-height); } -.site-header .container { +[data-theme="dark"] .site-header { + background: rgba(15, 23, 42, 0.95); +} + +.header-content { display: flex; - justify-content: space-between; align-items: center; - padding: 0 2rem; + justify-content: space-between; + height: var(--header-height); + gap: var(--space-8); } .site-title { - font-size: 1.5rem; - font-weight: 600; + 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); + transition: color var(--transition-fast); } .site-title:hover { color: var(--primary); - opacity: 1; +} + +.logo-icon { + font-size: var(--text-2xl); +} + +.header-nav { + display: flex; + gap: var(--space-6); +} + +.nav-link { + font-weight: var(--font-medium); + color: var(--text-secondary); + text-decoration: none; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.nav-link:hover { + color: var(--primary); + background-color: var(--bg-secondary); } .dark-mode-toggle { - background: none; + display: flex; + align-items: center; + justify-content: center; + width: 2.5rem; + height: 2.5rem; border: none; + border-radius: var(--radius-full); + background: var(--bg-secondary); + color: var(--text-primary); cursor: pointer; - padding: 8px; - border-radius: 50%; - position: relative; - width: 40px; - height: 40px; - transition: background-color .3s; + transition: all var(--transition-fast); } .dark-mode-toggle:hover { - background-color: rgba(0, 0, 0, .1); + background: var(--bg-tertiary); + transform: scale(1.05); } -[data-theme="dark"] .dark-mode-toggle:hover { - background-color: rgba(255, 255, 255, .1); -} - -.dark-mode-toggle svg { - width: 24px; - height: 24px; - fill: currentColor; - transition: transform .3s, fill .3s; -} - -[data-theme="dark"] .dark-mode-toggle svg { - fill: #fbbf24 +.sun-icon, +.moon-icon { + transition: all var(--transition-fast); } .moon-icon { @@ -135,232 +251,432 @@ body { display: block; } -main { - padding: 30px 0; +/* Hero Section */ +.hero-section { + padding: var(--space-24) 0 var(--space-20); + background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); + position: relative; + overflow: hidden; } -.content-section { - margin-bottom: 40px; +.hero-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(circle at 30% 20%, rgba(99, 102, 241, 0.1) 0%, transparent 50%); + pointer-events: none; } -h2 { - font-size: 2rem; - margin-top: 0; - margin-bottom: 20px; - color: var(--text-primary); -} - -h3 { - font-size: 1.5rem; - margin-top: 30px; - margin-bottom: 15px; - color: var(--text-primary); -} - -p { - margin-bottom: 15px; - color: var(--text-primary); -} - -a { - color: var(--primary); - text-decoration: none; - transition: opacity .3s ease-in-out; -} - -a:hover { - opacity: .8; -} - -.link-list { - list-style: disc; - padding-left: 20px; -} - -blockquote { - background-color: var(--bg-light); - border-left: 5px solid var(--primary); - padding: 15px; - margin: 20px 0; - font-style: italic; - color: var(--text-secondary); -} - -code, -.last-updated-date { - background-color: var(--bg-light); - padding: .2em .8em; - border-radius: var(--border-radius); - font-size: .9em; - font-family: 'ui-monospace', 'Cascadia Code', 'Source Code Pro', Menlo, monospace; -} - -hr.divider { - border: 0; - border-top: 1px solid var(--border-color); - margin: 40px 0; -} - -.last-updated { +.hero-content { text-align: center; + max-width: 800px; + margin: 0 auto; + position: relative; + z-index: 1; +} + +.hero-title { + font-size: var(--text-5xl); + font-weight: var(--font-bold); + line-height: 1.1; + margin: 0 0 var(--space-6); + color: var(--text-primary); +} + +.gradient-text { + background: linear-gradient(135deg, var(--primary) 0%, #8b5cf6 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-description { + font-size: var(--text-xl); color: var(--text-secondary); - margin-top: 20px; + margin: 0 0 var(--space-8); + max-width: 600px; + margin-left: auto; + margin-right: auto; } -.site-footer { - background-color: var(--bg-white); - padding: .5rem 0; - border-top: 1px solid var(--border-color); - margin-top: 4rem; -} - -.site-footer .container { +.hero-actions { display: flex; + gap: var(--space-4); justify-content: center; - align-items: center; - gap: 1rem; + margin-bottom: var(--space-12); + flex-wrap: wrap; } -.site-footer p { +.hero-stats { + display: flex; + gap: var(--space-8); + justify-content: center; + flex-wrap: wrap; +} + +.stat-item { + text-align: center; +} + +.stat-number { + display: block; + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--primary); +} + +.stat-label { + font-size: var(--text-sm); + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Button Styles */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-6); + border: none; + border-radius: var(--radius-lg); + font-size: var(--text-base); + font-weight: var(--font-medium); + text-decoration: none; + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} + +.btn-primary { + background: var(--primary); + color: white; + box-shadow: var(--shadow-sm); +} + +.btn-primary:hover { + background: var(--primary-hover); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border: 2px solid var(--border-primary); +} + +.btn-secondary:hover { + border-color: var(--primary); + color: var(--primary); +} + +/* Quick Navigation */ +.quick-nav { + padding: var(--space-16) 0; + background: var(--bg-primary); +} + +.quick-nav-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-6); +} + +.quick-nav-card { + display: block; + padding: var(--space-6); + background: var(--bg-primary); + border: 2px solid var(--border-primary); + border-radius: var(--radius-xl); + text-decoration: none; + transition: all var(--transition-normal); + position: relative; + overflow: hidden; +} + +.quick-nav-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 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: 0.05; +} + +.quick-nav-card:hover { + border-color: var(--primary); + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +.quick-nav-card > * { + position: relative; + z-index: 1; +} + +.quick-nav-icon { + font-size: var(--text-3xl); + margin-bottom: var(--space-3); +} + +.quick-nav-card h3 { + margin: 0 0 var(--space-2); + font-size: var(--text-lg); + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.quick-nav-card p { margin: 0; color: var(--text-secondary); - font-size: .95rem; + font-size: var(--text-sm); } -.site-footer a { - color: var(--primary); - font-weight: 500; - transition: var(--transition); +/* Section Styles */ +.section { + padding: var(--space-20) 0; } -.site-footer a:hover { - color: var(--primary-hover); - opacity: 1; +.section:nth-child(even) { + background: var(--bg-secondary); } +.section-header { + text-align: center; + margin-bottom: var(--space-16); +} + +.section-title { + font-size: var(--text-4xl); + font-weight: var(--font-bold); + margin: 0 0 var(--space-4); + color: var(--text-primary); +} + +.section-description { + font-size: var(--text-lg); + color: var(--text-secondary); + margin: 0; + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +/* Data Tables */ .data-table-container { + background: var(--bg-primary); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + border: 1px solid var(--border-primary); + margin: var(--space-4) 0; + position: relative; +} + +.table-wrapper { overflow-x: auto; - border-radius: var(--border-radius); - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06); - margin: 1rem 0; + overflow-y: visible; + border-radius: var(--radius-xl); + /* Enable smooth scrolling on touch devices */ + -webkit-overflow-scrolling: touch; + /* Show scrollbar on hover for better UX */ + scrollbar-width: thin; + scrollbar-color: var(--primary) var(--bg-secondary); +} + +.table-wrapper::-webkit-scrollbar { + height: 8px; +} + +.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); +} + +.table-wrapper::-webkit-scrollbar-thumb:hover { + background: var(--primary-hover); } table { width: 100%; - border-collapse: separate; - border-spacing: 0; - margin: 0; - border: none; - background-color: var(--bg-white); - font-size: .95rem; + border-collapse: collapse; + font-size: var(--text-sm); + min-width: 600px; /* Ensure minimum width for better mobile experience */ } th, td { - padding: 1rem 1.2rem; + padding: var(--space-4) var(--space-6); text-align: left; - border-bottom: 1px solid rgba(0, 0, 0, .05); + border-bottom: 1px solid var(--border-primary); + white-space: nowrap; } th { - background-color: var(--primary); - color: var(--bg-white); - font-weight: 500; + background: var(--primary); + color: white; + font-weight: var(--font-semibold); text-transform: uppercase; - letter-spacing: .5px; - font-size: .85rem; + letter-spacing: 0.05em; + font-size: var(--text-xs); position: sticky; top: 0; - z-index: 2; - backdrop-filter: blur(8px); + z-index: var(--z-sticky); } -th:not(:last-child) { - border-right: 1px solid rgba(255, 255, 255, .1); -} - -tr:nth-child(even) { - background-color: rgba(0, 0, 0, .02); +th:first-child { + position: sticky; + left: 0; + z-index: calc(var(--z-sticky) + 1); + background: var(--primary); } tr:hover { - background-color: rgba(13, 110, 253, .04); + background: var(--bg-secondary); } td { - transition: all .2s ease; + transition: all var(--transition-fast); } -tr:hover td { - color: var(--primary); +td:first-child { + font-weight: var(--font-medium); + position: sticky; + left: 0; + background: var(--bg-primary); + z-index: 1; } -th:first-child, -th:last-child, -tr:last-child td:first-child, -tr:last-child td:last-child { - border-radius: 0; +tr:hover td:first-child { + background: var(--bg-secondary); } -.pricing-tb { - font-weight: normal; +/* Status indicators in table cells */ +.status-cell { + text-align: center !important; } -.search-container { +.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: rgba(14, 236, 162, 0.1); + color: var(--success); +} + +.status-indicator.not-supported { + background: rgba(211, 29, 29, 0.1); + color: var(--error); +} + +.status-indicator svg { + width: 16px; + height: 16px; +} + +/* Enhanced Search */ +.search-section { + margin-bottom: var(--space-8); +} + +.search-container.enhanced { position: relative; - margin-bottom: 10px; + 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: 10px 40px 10px 15px; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - font-size: 1rem; - transition: border-color .3s ease-in-out; - box-sizing: border-box; + 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 rgba(37, 99, 235, .1); - outline: none; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); } .clear-icon { position: absolute; - right: 10px; + 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; - transition: opacity .3s ease-in-out; + visibility: hidden; } -.search-input:not(:placeholder-shown)+.clear-icon, -.search-input:focus+.clear-icon { +.search-input:not(:placeholder-shown) + .clear-icon, +.search-input:focus + .clear-icon { opacity: 1; + visibility: visible; } -.clear-icon svg { - width: 16px; - height: 16px; - fill: var(--text-secondary); +.clear-icon:hover { + background: var(--bg-secondary); + color: var(--text-primary); } -.clear-icon:hover svg { - fill: var(--text-primary); +/* Search Results */ +.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-container { +/* Comparison Section */ +.compare-controls { display: flex; - gap: 15px; - margin-bottom: 15px; + align-items: end; + gap: var(--space-6); + margin-bottom: var(--space-12); flex-wrap: wrap; - align-items: flex-start; } .compare-select-group { @@ -370,189 +686,1018 @@ tr:last-child td:last-child { .compare-select-group label { display: block; - margin-bottom: 5px; - font-weight: 600; -} - -.compare-select-group select { - width: 100%; - padding: 8px 20px; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - font-size: 1rem; - appearance: none; - background-color: var(--bg-light); - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: right 10px center; - cursor: pointer; - transition: border-color .3s ease-in-out; - outline: none; + margin-bottom: var(--space-2); + font-weight: var(--font-medium); color: var(--text-primary); } -.compare-select-group select:focus { - border-color: var(--primary); - box-shadow: 0 0 5px rgba(13, 110, 253, .5) -} - -#compare-table-container { - display: none; - background: rgba(255, 255, 255, .9); - border-radius: var(--border-radius); - padding: 4px; - box-shadow: 0 4px 10px rgba(0, 0, 0, .2); +.select-wrapper { position: relative; } -.compare-header { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid var(--border-color); - padding-bottom: 10px; - margin-bottom: 15px; +.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; } -#close-compare { - border: 1px solid #0b5ed7; - border-radius: 8px; - background-color: inherit; - padding: 8px 16px; - cursor: pointer; - font-size: .9rem; +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); - transition: all .2s cubic-bezier(.4, 0, .2, 1); - float: right; - margin-left: auto; - margin-right: 10px; - margin-bottom: 10px; + 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 rgba(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); +} + +/* Comparison 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 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 */ +.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 */ +.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: 6px; + 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 */ +.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 Specific Styles */ +.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: white; + 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: rgba(16, 185, 129, 0.05); +} + +.comparison-row.provider1-only { + background: rgba(59, 130, 246, 0.05); +} + +.comparison-row.provider2-only { + background: rgba(168, 85, 247, 0.05); +} + +.comparison-row.neither-supported { + background: rgba(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: rgba(16, 185, 129, 0.1); + color: var(--success); +} + +.status-badge.provider1-only { + background: rgba(59, 130, 246, 0.1); + color: var(--info); +} + +.status-badge.provider2-only { + background: rgba(168, 85, 247, 0.1); + color: #8b5cf6; +} + +.status-badge.neither-supported { + background: rgba(239, 68, 68, 0.1); + color: var(--error); +} + +/* Close Button */ +#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 { - box-shadow: 0 4px 8px rgba(0, 0, 0, .15); + background: var(--error); + color: white; + border-color: var(--error); + transform: translateY(-1px); } -#close-compare:active { - box-shadow: 0 2px 4px rgba(0, 0, 0, .1); -} - -.loading { +/* Empty State */ +.empty-state { text-align: center; - padding: 15px; - color: var(--text-secondary); + padding: var(--space-16); } -main>* { - opacity: 0; - transform: translateY(20px); - animation: fadeIn .5s ease forwards; +.empty-state-icon { + font-size: var(--text-5xl); + margin-bottom: var(--space-4); } -@keyframes fadeIn { - to { - opacity: 1; - transform: translateY(0) - } -} - -[data-theme="dark"] tr:nth-child(even) { - background-color: rgba(255, 255, 255, .03); -} - -[data-theme="dark"] tr:hover { - background-color: rgba(59, 130, 246, .1); -} - -[data-theme="dark"] code, -[data-theme="dark"] .last-updated-date { - background-color: rgba(30, 41, 59, .8); - color: #e2e8f0; -} - -[data-theme="dark"] .search-input { - background-color: var(--bg-light); +.empty-state h3 { + margin: 0 0 var(--space-2); + font-size: var(--text-xl); color: var(--text-primary); } -[data-theme="dark"] #compare-table-container { - background: rgba(15, 23, 42, .9); +.empty-state p { + margin: 0; + color: var(--text-secondary); } -@media (max-width:768px) { +.warning-state { + text-align: center; + padding: var(--space-12); + background: rgba(245, 158, 11, 0.05); + border-radius: var(--radius-lg); + border: 1px solid rgba(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 Components */ +.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: rgba(59, 130, 246, 0.1); + border: 1px solid rgba(59, 130, 246, 0.2); + color: var(--info); +} + +.alert-icon { + flex-shrink: 0; + margin-top: 2px; +} + +/* Referral Section */ +.referral-section { + margin-top: var(--space-16); + padding: var(--space-8); + background: var(--bg-secondary); + border-radius: var(--radius-xl); + border: 1px solid var(--border-primary); +} + +.referral-section h3 { + margin: 0 0 var(--space-2); + text-align: center; + color: var(--text-primary); +} + +.referral-section p { + text-align: center; + color: var(--text-secondary); + margin-bottom: var(--space-6); +} + +.referral-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-4); +} + +.referral-card { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-2); + padding: var(--space-4); + background: var(--bg-primary); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + text-decoration: none; + transition: all var(--transition-fast); +} + +.referral-card:hover { + border-color: var(--primary); + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.service-name { + font-weight: var(--font-medium); + color: var(--text-primary); +} + +.referral-badge { + font-size: var(--text-xs); + color: var(--primary); + background: rgba(99, 102, 241, 0.1); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-md); +} + +/* Status Grid */ +.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-name { + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.service-status-indicator { + width: 12px; + height: 12px; + border-radius: var(--radius-full); + background: var(--success); + box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); +} + +.status-indicator.online { + background: var(--success); + box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); +} + +.status-card p { + margin: 0; + color: var(--text-secondary); + font-size: var(--text-sm); +} +/* Loading States */ +.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); } +} + +/* Notification System */ +.notification-container { + position: fixed; + top: var(--space-6); + right: var(--space-6); + z-index: var(--z-tooltip); + max-width: 400px; + width: 100%; +} + +.notification { + background: var(--bg-primary); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + margin-bottom: var(--space-3); + opacity: 0; + transform: translateX(100%); + transition: all var(--transition-normal); +} + +.notification--visible { + opacity: 1; + transform: translateX(0); +} + +.notification--dismissing { + opacity: 0; + transform: translateX(100%); +} + +.notification__content { + display: flex; + align-items: flex-start; + gap: var(--space-3); + padding: var(--space-4); +} + +.notification__icon { + flex-shrink: 0; + margin-top: 2px; +} + +.notification__message { + flex: 1; + font-size: var(--text-sm); + color: var(--text-primary); +} + +.notification__close { + background: none; + border: none; + color: var(--text-tertiary); + cursor: pointer; + padding: var(--space-1); + border-radius: var(--radius-md); + transition: all var(--transition-fast); + flex-shrink: 0; +} + +.notification__close:hover { + background: var(--bg-secondary); + color: var(--text-primary); +} + +.notification--success { + border-color: var(--success); +} + +.notification--success .notification__icon { + color: var(--success); +} + +.notification--error { + border-color: var(--error); +} + +.notification--error .notification__icon { + color: var(--error); +} + +.notification--warning { + border-color: var(--warning); +} + +.notification--warning .notification__icon { + color: var(--warning); +} + +.notification--info { + border-color: var(--info); +} + +.notification--info .notification__icon { + color: var(--info); +} + +/* 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; +} + +.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 Button */ +.back-to-top { + position: fixed; + bottom: var(--space-6); + right: var(--space-6); + width: 48px; + height: 48px; + background: var(--primary); + color: white; + 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-fixed); +} + +.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); +} + +/* Responsive Design */ +@media (max-width: 1024px) { .container { - padding: 15px + padding: 0 var(--space-4); } - - .site-header .container { - flex-direction: row; - justify-content: space-between; - align-items: center + + .comparison-stats { + gap: var(--space-4); } - - .site-title { - font-size: 1.25rem + + .comparison-stats .stat { + min-width: 80px; + padding: var(--space-2) var(--space-3); } +} - h2 { - font-size: 1.75rem +@media (max-width: 768px) { + .header-nav { + display: none; } - - h3 { - font-size: 1.25rem + + .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; + } + + .referral-grid { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + + /* Enhanced table responsiveness */ + .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 { - display: block; - overflow-x: auto; - white-space: nowrap; - border: none + font-size: var(--text-xs); + min-width: 500px; } - - th, td { - padding: .8rem 1rem; - font-size: .9rem; - min-width: 120px; - max-width: 300px; - white-space: normal; - word-wrap: break-word + + th, + td { + padding: var(--space-2) var(--space-3); } - + th:first-child, td:first-child { - border-left: 1px solid var(--border-color) + min-width: 120px; + max-width: 150px; + white-space: normal; + word-break: break-word; } - - th:last-child, - td:last-child { - border-right: 1px solid var(--border-color) + + /* Comparison table mobile adjustments */ + .comparison-header { + padding: var(--space-4); } - - tr:last-child td { - border-bottom: 1px solid var(--border-color) + + .comparison-header h3 { + font-size: var(--text-lg); } - - #compare-container { - flex-direction: column - } - - .compare-select-group { - min-width: auto - } - - .site-header .container { - padding: 0 1rem - } - - .site-footer { - padding: 1.5rem 0 - } - - .site-footer .container { + + .comparison-stats { flex-direction: column; - text-align: center + 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); + } + + /* Notification adjustments */ + .notification-container { + left: var(--space-4); + right: var(--space-4); + max-width: none; + } + + .notification { + transform: translateY(-100%); + } + + .notification--visible { + transform: translateY(0); + } + + .notification--dismissing { + transform: translateY(-100%); + } +} + +@media (max-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; + } +} + +/* Print Styles */ +@media print { + .site-header, + .hero-section, + .quick-nav, + .referral-section, + .site-footer, + .back-to-top, + .dark-mode-toggle, + .notification-container, + .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; + } +} + +/* Accessibility Improvements */ +@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; + } + + .notification { + transition: none; + } +} + +/* Focus Styles */ +:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* High Contrast Mode Support */ +@media (prefers-contrast: high) { + :root { + --border-primary: #000000; + --text-secondary: #000000; + } + + [data-theme="dark"] { + --border-primary: #ffffff; + --text-secondary: #ffffff; + } +} + +/* Loading Overlay */ +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(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: rgba(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); } \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js index 842d11b..884d96a 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,99 +1,486 @@ /** - * @fileoverview Main application file for Debrid Services Comparison - * Handles data fetching, table generation, search functionality, and comparison features + * @fileoverview Enhanced Debrid Services Comparison Application + * Modern, feature-rich JavaScript with performance optimizations and professional UX */ -// Utility to debounce frequent events -const debounce = (func, delay = 300) => { - let timeout; - return (...args) => { - clearTimeout(timeout); - timeout = setTimeout(() => func(...args), delay); - }; +// 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); + } + }); + }, { ...defaultOptions, ...options }); + + elements.forEach(el => observer.observe(el)); + } }; -// Class to manage table operations -class TableManager { - constructor(containerId, searchInputId, clearIconId) { - this.container = document.querySelector(`#${containerId}`); - this.searchInput = document.querySelector(`#${searchInputId}`); - this.clearIcon = document.querySelector(`#${clearIconId}`); - this._setupSearchFunctionality(); +// Enhanced notification system +class NotificationManager { + constructor() { + this.container = this.createContainer(); + this.notifications = new Set(); } - generateTable(data = {}) { - if (!Object.keys(data).length) { - this.container.innerHTML = '

No data available.

'; - return; - } + createContainer() { + const container = document.createElement('div'); + container.className = 'notification-container'; + container.setAttribute('aria-live', 'polite'); + document.body.appendChild(container); + return container; + } - const providers = Object.keys(data[Object.keys(data)[0]]); - this.container.innerHTML = ` - - - - - ${providers.map(provider => ``).join('')} - - - - ${Object.entries(data).map(([host, providerData]) => ` - - - ${providers.map(provider => ` - - `).join('')} - - `).join('')} - -
Service Name${provider}
${host} - ${providerData[provider] === 'yes' ? '✅' : '❌'} -
+ show(message, type = 'info', duration = 4000) { + const notification = document.createElement('div'); + const id = Date.now().toString(); + + notification.className = `notification notification--${type}`; + notification.setAttribute('role', 'alert'); + notification.innerHTML = ` +
+ + ${this.getIcon(type)} + + ${message} + +
`; - } - _setupSearchFunctionality() { - const handleSearch = debounce(() => { - const searchTerm = this.searchInput.value.toLowerCase(); - const rows = this.container.querySelectorAll('table tbody tr'); - this.clearIcon.style.display = searchTerm ? 'block' : 'none'; + this.container.appendChild(notification); + this.notifications.add(id); - rows.forEach(row => { - const serviceName = row.querySelector('td:first-child')?.textContent.toLowerCase(); - row.style.display = serviceName?.includes(searchTerm) ? '' : 'none'; - }); + // Auto dismiss + setTimeout(() => this.dismiss(notification, id), duration); + + // Manual dismiss + notification.querySelector('.notification__close').addEventListener('click', () => { + this.dismiss(notification, id); }); - this.searchInput?.addEventListener('input', handleSearch); - this.clearIcon?.addEventListener('click', () => { - this.searchInput.value = ''; - this.clearIcon.style.display = 'none'; - handleSearch(); + // Animate in + requestAnimationFrame(() => { + notification.classList.add('notification--visible'); + }); + + return id; + } + + getIcon(type) { + const icons = { + success: '', + error: '', + warning: '', + info: '' + }; + return icons[type] || icons.info; + } + + dismiss(notification, id) { + notification.classList.add('notification--dismissing'); + this.notifications.delete(id); + + setTimeout(() => { + if (notification.parentNode) { + this.container.removeChild(notification); + } + }, 300); + } +} + +// Enhanced loading manager +class LoadingManager { + constructor() { + this.activeLoaders = new Set(); + } + + 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'); + }); + + 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); + } + } + + hideAll() { + this.activeLoaders.forEach(id => { + const loader = document.querySelector(`[data-loader-id="${id}"]`); + if (loader) { + this.hide(loader.parentNode, id); + } }); } } -// Class to manage comparison operations +// Enhanced table manager with advanced features +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 + }; + + this.currentData = {}; + this.filteredData = {}; + this.currentSort = { column: null, direction: 'asc' }; + this.currentPage = 1; + + this._setupSearchFunctionality(); + this._setupKeyboardNavigation(); + } + + generateTable(data = {}) { + if (!Object.keys(data).length) { + this.container.innerHTML = '

No data available.

'; + return; + } + + this.currentData = data; + this.filteredData = { ...data }; + + const providers = Object.keys(data[Object.keys(data)[0]]); + const tableId = this.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() : ''} + `; + + this._setupTableFeatures(); + this._setupAccessibility(); + } + + _generateTableRows(data, providers) { + return Object.entries(data).map(([host, providerData]) => ` + + +
+ ${host} +
+ + ${providers.map(provider => ` + + + ${providerData[provider] === 'yes' ? + '' : + '' + } + + + `).join('')} + + `).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); + + this.searchInput?.addEventListener('input', handleSearch); + this.searchInput?.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + this._clearSearch(); + } + }); + + this.clearIcon?.addEventListener('click', () => { + this._clearSearch(); + }); + } + + _clearSearch() { + this.searchInput.value = ''; + this.filteredData = { ...this.currentData }; + this.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')); + } + + if (searchTerm) { + indicator.textContent = `Showing ${resultCount} of ${totalCount} services`; + indicator.style.display = 'block'; + } else { + 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'; + + this.currentSort = { column, direction: newDirection }; + + // Update header states + this.container.querySelectorAll('th.sortable').forEach(th => { + th.setAttribute('aria-sort', 'none'); + th.classList.remove('sorted-asc', 'sorted-desc'); + }); + + header.setAttribute('aria-sort', newDirection === 'asc' ? 'ascending' : 'descending'); + header.classList.add(`sorted-${newDirection}`); + + this._sortData(); + this._updateTableContent(); + } + + _sortData() { + const { column, direction } = this.currentSort; + const entries = Object.entries(this.filteredData); + + entries.sort(([hostA, dataA], [hostB, dataB]) => { + let valueA, valueB; + + 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; + } + }); + + this.filteredData = Object.fromEntries(entries); + } + + _updateTableContent() { + const tbody = this.container.querySelector('tbody'); + const providers = Object.keys(this.currentData[Object.keys(this.currentData)[0]]); + + 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); + } + }); + } + + _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 class ComparisonManager { constructor(containerId, select1Id, select2Id, data) { this.container = document.querySelector(`#${containerId}`); this.select1 = document.querySelector(`#${select1Id}`); this.select2 = document.querySelector(`#${select2Id}`); this.data = data; + this.isComparing = false; this._populateDropdowns(); this._setupEventListeners(); + this._setupKeyboardShortcuts(); } _populateDropdowns() { const providers = Object.keys(this.data[Object.keys(this.data)[0]]); - [this.select1, this.select2].forEach(select => { - providers.forEach(provider => { - const option = document.createElement('option'); - option.value = provider; - option.textContent = provider; - select.appendChild(option); - }); + + [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)); }); } @@ -101,110 +488,535 @@ class ComparisonManager { const provider1 = this.select1.value; const provider2 = this.select2.value; - if (!provider1 || !provider2) return; + if (!provider1 || !provider2) { + this._showEmptyState(); + return; + } + if (provider1 === provider2) { + this._showSameProviderWarning(); + return; + } + + this.isComparing = true; + const loaderId = loadingManager.show(this.container, 'Generating comparison...'); + + // Simulate processing time for better UX + setTimeout(() => { + this._renderComparisonTable(provider1, provider2); + loadingManager.hide(this.container, loaderId); + this._trackComparison(provider1, provider2); + }, 500); + } + + _renderComparisonTable(provider1, provider2) { + const comparisonData = this._calculateComparisonStats(provider1, provider2); + this.container.innerHTML = ` - - - - - - - - - - - ${Object.entries(this.data).map(([host, providerData]) => ` +
+

Comparing ${provider1} vs ${provider2}

+
+
+ Shared Support + ${comparisonData.shared} +
+
+ ${provider1} Only + ${comparisonData.provider1Only} +
+
+ ${provider2} Only + ${comparisonData.provider2Only} +
+
+
+ +
+
+ +
+ + + +
+ +
+
Service Name${provider1}${provider2}
+ - - - + + + + - `).join('')} - -
${host}${providerData[provider1] === 'yes' ? '✅' : '❌'}${providerData[provider2] === 'yes' ? '✅' : '❌'}Service Name${provider1}${provider2}Status
+ + + ${this._generateComparisonRows(provider1, provider2)} + + + + `; + + this.container.style.display = 'block'; + this._setupComparisonFeatures(); + + // Animate table in + requestAnimationFrame(() => { + this.container.classList.add('comparison-visible'); + }); + } + + _generateComparisonRows(provider1, provider2) { + return Object.entries(this.data).map(([host, providerData]) => { + const support1 = providerData[provider1] === 'yes'; + const support2 = providerData[provider2] === 'yes'; + + let statusClass = ''; + let statusText = ''; + + if (support1 && support2) { + statusClass = 'both-supported'; + statusText = 'Both'; + } else if (support1) { + statusClass = 'provider1-only'; + statusText = `${provider1} only`; + } else if (support2) { + statusClass = 'provider2-only'; + statusText = `${provider2} only`; + } else { + statusClass = 'neither-supported'; + statusText = 'Neither'; + } + + return ` + + ${host} + + + ${support1 ? + '' : + '' + } + + + + + ${support2 ? + '' : + '' + } + + + + ${statusText} + + + `; + }).join(''); + } + + _calculateComparisonStats(provider1, provider2) { + let shared = 0; + let provider1Only = 0; + let provider2Only = 0; + + Object.values(this.data).forEach(providerData => { + 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() { + // Close button + this.container.querySelector('#close-compare')?.addEventListener('click', () => { + this._closeComparison(); + }); + + // Filter functionality + this.container.querySelectorAll('input[name="comparison-filter"]').forEach(radio => { + radio.addEventListener('change', (e) => { + this._filterComparison(e.target.value); + }); + }); + } + + _filterComparison(filter) { + const rows = this.container.querySelectorAll('.comparison-row'); + + 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; + } + + row.style.display = show ? '' : 'none'; + }); + + // Update visible count + const visibleRows = this.container.querySelectorAll('.comparison-row:not([style*="display: none"])').length; + let indicator = this.container.querySelector('.filter-results'); + + if (!indicator) { + indicator = document.createElement('div'); + indicator.className = 'filter-results'; + this.container.querySelector('.table-wrapper').insertAdjacentElement('beforebegin', indicator); + } + + indicator.textContent = `Showing ${visibleRows} services`; + } + + _closeComparison() { + this.container.classList.add('comparison-hiding'); + + setTimeout(() => { + this.container.style.display = 'none'; + this.container.classList.remove('comparison-visible', 'comparison-hiding'); + this.isComparing = false; + this._showEmptyState(); + }, 300); + } + + _showEmptyState() { + this.container.innerHTML = ` +
+
⚖️
+

Ready to Compare

+

Select two services above to see a detailed comparison

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

Same Service Selected

+

Please select two different services to compare

+
`; this.container.style.display = 'block'; - this._setupCloseButton(); } _setupEventListeners() { [this.select1, this.select2].forEach(select => { - select?.addEventListener('change', () => this._generateCompareTable()); + select?.addEventListener('change', () => { + this._generateCompareTable(); + }); }); } - _setupCloseButton() { - this.container.querySelector('#close-compare')?.addEventListener('click', () => { - this.container.style.display = 'none'; + _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 + }); + } + } } -// Class to manage pricing operations +// Enhanced pricing manager class PricingManager { constructor(containerId) { this.container = document.querySelector(`#${containerId}-table-container`); + this.currentData = null; } generatePricingTable(data = {}) { if (!data.plans?.length) { - this.container.innerHTML = '

Error: Invalid pricing data structure.

'; + this.container.innerHTML = '

Error: Invalid pricing data structure.

'; return; } + 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 => ` +
+
Plans${service}
+ - + ${services.map(service => ` - + `).join('')} - `).join('')} - -
${plan.name}Plans${plan[service]} +
+ ${service} +
+
+ + + ${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('')} + + + `; + + 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); + this._highlightColumn(column); + }); + + cell.addEventListener('mouseleave', () => { + this._removeColumnHighlight(); + }); + }); + } + + _highlightColumn(columnIndex) { + const table = this.container.querySelector('table'); + const cells = table.querySelectorAll(`td:nth-child(${columnIndex + 1}), th:nth-child(${columnIndex + 1})`); + cells.forEach(cell => cell.classList.add('highlighted')); + } + + _removeColumnHighlight() { + const highlightedCells = this.container.querySelectorAll('.highlighted'); + highlightedCells.forEach(cell => cell.classList.remove('highlighted')); } } -// Main application initialization +// Performance monitor +class PerformanceMonitor { + constructor() { + this.metrics = { + loadStart: performance.now(), + domReady: null, + fullyLoaded: null + }; + } + + markDOMReady() { + this.metrics.domReady = performance.now(); + console.log(`DOM Ready: ${(this.metrics.domReady - this.metrics.loadStart).toFixed(2)}ms`); + } + + markFullyLoaded() { + this.metrics.fullyLoaded = performance.now(); + console.log(`Fully Loaded: ${(this.metrics.fullyLoaded - this.metrics.loadStart).toFixed(2)}ms`); + } + + getMetrics() { + return { + ...this.metrics, + totalLoadTime: this.metrics.fullyLoaded - this.metrics.loadStart, + domLoadTime: this.metrics.domReady - this.metrics.loadStart + }; + } +} + +// Global instances +const notificationManager = new NotificationManager(); +const loadingManager = new LoadingManager(); +const performanceMonitor = new PerformanceMonitor(); + +// Enhanced application initialization document.addEventListener('DOMContentLoaded', async () => { - const fileHostsTable = new TableManager('file-hosts-table-container', 'host-search-input', 'clear-host-search'); - const adultHostsTable = new TableManager('adult-hosts-table-container', 'adult-host-search-input', 'clear-adult-host-search'); - const pricingManager = new PricingManager('pricing'); + performanceMonitor.markDOMReady(); try { - const [fileHostsData, adultHostsData, pricingData] = await Promise.all([ - fetch('./json/file-hosts.json').then(res => res.json()), - fetch('./json/adult-hosts.json').then(res => res.json()), - fetch('./json/pricing.json').then(res => res.json()) + // 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'); + + // 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...'); + + // 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') ]); - fileHostsTable.generateTable(fileHostsData); - adultHostsTable.generateTable(adultHostsData); - pricingManager.generatePricingTable(pricingData); + // 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); + notificationManager.show('Failed to load file hosts data', 'error'); + console.error('File hosts error:', fileHostsData.reason); + } + + if (adultHostsData.status === 'fulfilled') { + loadingManager.hide('#adult-hosts-table-container', loaderId2); + adultHostsTable.generateTable(adultHostsData.value); + } else { + loadingManager.hide('#adult-hosts-table-container', loaderId2); + notificationManager.show('Failed to load adult hosts data', 'error'); + 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); + notificationManager.show('Failed to load pricing data', 'error'); + console.error('Pricing error:', pricingData.reason); + } + + // Setup URL-based comparison + setupURLComparison(); + + // Setup scroll animations + setupScrollAnimations(); + + // Setup offline detection + setupOfflineDetection(); + + performanceMonitor.markFullyLoaded(); + notificationManager.show('Application loaded successfully', 'success', 2000); - new ComparisonManager('compare-table-container', 'provider1', 'provider2', fileHostsData); } catch (error) { - console.error('Error initializing application:', error); - const errorMessage = '

Error loading data. Please try again later.

'; - fileHostsTable.container.innerHTML = errorMessage; - adultHostsTable.container.innerHTML = errorMessage; - pricingManager.container.innerHTML = errorMessage; + console.error('Critical error initializing application:', error); + notificationManager.show('Failed to initialize application', 'error'); + loadingManager.hideAll(); } }); + +// URL-based comparison feature +function setupURLComparison() { + const urlParams = new URLSearchParams(window.location.search); + const provider1 = urlParams.get('compare'); + const provider2 = urlParams.get('with'); + + if (provider1 && provider2) { + setTimeout(() => { + const select1 = document.querySelector('#provider1'); + const select2 = document.querySelector('#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); +} + +// Offline detection +function setupOfflineDetection() { + window.addEventListener('online', () => { + notificationManager.show('Connection restored', 'success', 3000); + }); + + window.addEventListener('offline', () => { + notificationManager.show('You are offline. Some features may not work.', 'warning', 5000); + }); +} + +// Enhanced error boundary +window.addEventListener('error', (event) => { + console.error('Global error:', event.error); + notificationManager.show('An unexpected error occurred', 'error'); +}); + +window.addEventListener('unhandledrejection', (event) => { + console.error('Unhandled promise rejection:', event.reason); + notificationManager.show('A network error occurred', 'error'); + event.preventDefault(); +}); \ No newline at end of file