From c9af19459c659a06d8bc402f11a7f556d96ad96d Mon Sep 17 00:00:00 2001 From: fynks <75840152+fynks@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:36:41 +0500 Subject: [PATCH] improved --- dist/css/styles.css | 703 +++++++++++++++++++++++++++++++----------- dist/index.html | 727 +++++++++++++++++++++++++++++++++++-------- dist/js/app.js | 735 +++++++++++++++++++++++++------------------- 3 files changed, 1553 insertions(+), 612 deletions(-) diff --git a/dist/css/styles.css b/dist/css/styles.css index 71d83a0..b0b833a 100644 --- a/dist/css/styles.css +++ b/dist/css/styles.css @@ -1,238 +1,575 @@ -/* 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);} +/* ============================================================================= + Debrid Services Comparison - Improved Complete CSS + - Unified card-first design for About, Speed Test, FAQ + - Accessible header/navigation, hero, buttons + - Tables, comparisons, search, referral, status, resources, notes + - Age verification modal, loading states, footer, utilities + ========================================================================== */ + +/* Root variables and theme */ +:root{ + color-scheme: light dark; + + /* Brand + feedback */ + --primary:#6366f1; + --primary-hover:#4f46e5; + --primary-light:#a5b4fc; + --primary-dark:#3730a3; + --success:#10b981; + --warning:#f59e0b; + --error:#ef4444; + --info:#3b82f6; + + /* Grays */ + --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 and background */ + --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; + + /* Borders and focus */ + --border-primary:var(--gray-200); + --border-secondary:var(--gray-300); + --border-focus:var(--primary); + + /* Radius */ + --radius-md:.375rem; + --radius-lg:.5rem; + --radius-xl:.75rem; + --radius-2xl:1rem; + --radius-full:9999px; + + /* Shadows */ + --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); + + /* Typography */ + --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; + + /* Spacing */ + --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; + + /* Layout */ + --container-max-width:1280px; + --header-height:4.5rem; + + /* Z-index */ + --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); +} + +/* Base and resets */ +*,::before,::after{box-sizing:border-box} +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'; + background-color:var(--bg-primary); + color:var(--text-primary); + -webkit-font-smoothing:antialiased; + -moz-osx-font-smoothing:grayscale; + text-rendering:optimizeLegibility; + min-height:100vh; margin:0; overflow-x:hidden; +} +img,svg,video,canvas{max-width:100%;height:auto} +a{color:var(--primary);text-decoration:none;transition:color .2s cubic-bezier(.4,0,.2,1)} +a:hover{color:var(--primary-hover);text-decoration:none} + +/* 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} + +/* Layout wrappers */ +.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} + +/* Skip link */ +.skip-link{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden} +.skip-link:focus{position:fixed;left:var(--space-4);top:var(--space-4);width:auto;height:auto;padding:var(--space-2) var(--space-3);background:var(--bg-primary);color:var(--text-primary);border:2px solid var(--border-focus);border-radius:var(--radius-lg);box-shadow:var(--shadow-md);z-index:9999} + +/* Header and navigation */ +.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 .2s,border-color .2s,backdrop-filter .3s,box-shadow .2s; +} +@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 .2s,transform .2s,text-shadow .2s; +} +.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 .2s,filter .2s} +.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 .2s,background-color .2s,transform .2s,box-shadow .2s,border-color .2s; +} +.nav-link::before{ + content:'';position:absolute;bottom:-2px;left:50%;width:0;height:2px;background:var(--primary); + border-radius:var(--radius-full);transition:width .2s,left .2s;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)} +.nav-link[aria-current="page"]{color:var(--primary)} +.nav-link[aria-current="page"]::before{width:70%} + +/* Dark mode toggle */ +.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 .2s,border-color .2s,transform .2s,box-shadow .2s,color .2s; 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 .2s,transform .2s} +.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 / .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;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} +[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)} + +/* 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;white-space:nowrap;position:relative;overflow:hidden;text-align:center;min-width:140px;justify-content:center; + transition:color .2s,background-color .2s,border-color .2s,transform .2s,box-shadow .2s,opacity .2s; + 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)} +input,select,textarea,button{accent-color:var(--primary)} + +/* Section framework */ +.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-16)} +.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} +.subsection{margin:var(--space-12) 0} +.subsection-title{margin:0 0 var(--space-4);font-size:var(--text-xl);font-weight:var(--font-bold)} + +/* Quick navigation cards */ +.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); + text-decoration:none;position:relative;overflow:hidden;box-shadow:var(--shadow-sm); + -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px); + transition:background-color .2s,border-color .2s,transform .2s,box-shadow .2s,opacity .2s; 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;} +.quick-nav-card::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--primary) 0%,transparent 100%);opacity:0;transition:opacity .2s;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 .2s,filter .2s} +.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} -/* Sections */ -.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;} +/* Unified status cards (also used for speed tiles) */ +.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 .2s,border-color .2s,transform .2s,box-shadow .2s;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)} +.speed-grid .status-card{display:flex;flex-direction:column;gap:.25rem;align-items:flex-start} +.speed-card .service-name{font-weight:var(--font-semibold);color:var(--text-primary)} +.speed-card .speed-test-label{font-size:var(--text-sm);color:var(--text-secondary)} -/* Tables */ +/* Chips and meta */ +.meta-chips{display:flex;gap:.5rem;justify-content:center;margin-top:var(--space-4);flex-wrap:wrap} +.chip{ + display:inline-flex;align-items:center;gap:.4rem;padding:.35rem .6rem;border-radius:999px; + background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--border-primary); + font-size:.75rem;font-weight:var(--font-medium) +} +.chip-info{color:var(--primary);background:rgb(99 102 241 / .10);border-color:rgb(99 102 241 / .25)} + +/* Stepper (ordered guidance) */ +.stepper{counter-reset:step;list-style:none;padding-left:0;margin:var(--space-6) 0} +.stepper li{position:relative;padding-left:2.25rem;margin-bottom:.75rem} +.stepper li::before{ + counter-increment:step;content:counter(step);position:absolute;left:0;top:.1rem;width:1.5rem;height:1.5rem; + display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--primary);color:#fff;font-size:.8rem;font-weight:700 +} + +/* Accordion (used in FAQ and Speed) */ +.accordion{border:1px solid var(--border-primary);border-radius:var(--radius-xl);background:var(--bg-primary);margin:var(--space-4) 0;overflow:hidden} +.accordion>summary{ + display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:var(--space-4); + font-weight:var(--font-semibold);cursor:pointer;list-style:none +} +.accordion>summary::-webkit-details-marker{display:none} +.accordion[open]>summary{background:var(--bg-secondary)} +.accordion .chevron{transition:transform .2s} +.accordion[open] .chevron{transform:rotate(180deg)} +.accordion-content{padding:0 var(--space-4) var(--space-4);color:var(--text-secondary)} +.bullet-list{padding:var(--space-4) var(--space-6);margin:0} +.bullet-list li{margin:.25rem 0} +.accordion>summary:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:.5rem} +.accordion>summary:hover{background:var(--bg-secondary)} + +/* Alerts */ +.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;border:1px solid var(--border-primary)} +.alert-info{background:rgb(59 130 246 / .1);border-color:rgb(59 130 246 / .2);color:var(--info)} +.alert-warning{background:rgb(245 158 11 / .1);border-color:rgb(245 158 11 / .2);color:var(--warning)} +.alert-icon{flex-shrink:0;margin-top:2px} + +/* Tables (data + pricing) */ .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); + 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) } -.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;} +.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:.05em;font-size:var(--text-xs); - position:sticky;top:0;z-index:var(--z-sticky); + 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);background-clip:padding-box;} -tr:hover{background:var(--bg-secondary);} -td{transition:background-color var(--transition-fast),color var(--transition-fast);} +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 .15s,color .15s} 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); + 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;} +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 .15s,color .15s,transform .15s} +.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} + +/* Pricing table highlight support */ +.pricing-table th.highlighted, +.pricing-table td.highlighted{background:var(--bg-secondary)} /* 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-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); + 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 .15s,box-shadow .15s,color .15s,background-color .15s; } -.search-input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgb(99 102 241 / .1);} +.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); + 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 .15s,color .15s,background-color .15s,transform .15s; } -.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);} +.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;} +/* Compare UI */ +.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); + 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 .15s,box-shadow .15s,color .15s,background-color .15s; } -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);} +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;} +#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);} +.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{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); + 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 .15s,background-color .15s,border-color .15s,transform .15s,box-shadow .15s; } -#close-compare:hover{background:var(--error);color:#fff;border-color:var(--error);transform:translateY(-1px);} +#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;} +.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)} -/* 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;} +.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)} + +/* 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;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:800px} +.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); + transition:background-color .2s,border-color .2s,transform .2s,box-shadow .2s,color .2s; } -.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);} +.referral-card:hover{border-color:var(--primary);transform:translateY(-2px);box-shadow:var(--shadow-md)} +.referral-card .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; +/* Important notes */ +.important-notes-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--space-6)} +.note-card{background:var(--bg-elevated);border:1px solid var(--border-primary);border-radius:var(--radius-lg);padding:var(--space-6)} +.note-icon{font-size:var(--text-2xl);margin-bottom:var(--space-3)} +.note-card h3{margin-bottom:var(--space-3);color:var(--text-primary)} +.note-card p{margin:0;color:var(--text-secondary)} + +/* Resources */ +.resources-categories{display:grid;gap:var(--space-12)} +.resource-category h3{margin-bottom:var(--space-6);color:var(--primary)} +.resource-list{display:grid;gap:var(--space-4)} +.resource-item{ + display:block;padding:var(--space-4);background:var(--bg-elevated);border:1px solid var(--border-primary);border-radius:var(--radius-lg); + text-decoration:none;transition:transform .2s,box-shadow .2s,border-color .2s; } -.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);} +.resource-item:hover{transform:translateY(-2px);box-shadow:var(--shadow-md);border-color:var(--primary)} +.resource-item strong{display:block;color:var(--text-primary);margin-bottom:var(--space-1)} +.resource-item span{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);} +/* Age verification modal */ +.age-verification-overlay{ + position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;z-index:9999;backdrop-filter:blur(5px) +} +.age-verification-modal{background:var(--bg-primary);border-radius:var(--radius-xl);padding:var(--space-8);max-width:500px;margin:var(--space-4);box-shadow:var(--shadow-lg);border:1px solid var(--border-primary)} +.age-verification-content{text-align:center} +.age-verification-content h3{color:var(--error);margin-bottom:var(--space-4)} +.age-verification-content p{margin-bottom:var(--space-4);color:var(--text-secondary)} +.age-verification-actions{display:flex;gap:var(--space-4);justify-content:center;margin-top:var(--space-6)} +@media (width <=768px){.age-verification-actions{flex-direction:column}} + +/* 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)}} -.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);} + +.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 .2s} +[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;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;} +.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 .15s} +.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 button */ .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; + 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 .2s,transform .2s,box-shadow .2s,opacity .2s,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);} +.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) */ +/* Responsive */ +@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)} + .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)} @@ -246,17 +583,27 @@ input,select,textarea,button{accent-color:var(--primary);} } @media (width <= 480px){ .section{padding:var(--space-12) 0} + .stat-item:not(:last-child)::after{display:none} .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} } -/* Coarse pointer */ -@media (pointer: coarse){ +/* Motion and contrast preferences */ +@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} +} + +/* Pointer coarse: disable hover transforms for touch */ +@media (pointer:coarse){ .nav-link:hover,.btn:hover,.quick-nav-card:hover,.status-card:hover{transform:none;box-shadow:none} } diff --git a/dist/index.html b/dist/index.html index 04adfd1..6aa9e91 100644 --- a/dist/index.html +++ b/dist/index.html @@ -3,12 +3,12 @@ - Debrid Services Comparison - Compare Pricing & Hosts - + + @@ -21,26 +21,33 @@ - - - + + - - - - - - - - + + + + + + + + - + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - + + @@ -106,8 +191,11 @@ Debrid Comparison + + + + + + + + -
-
- -

Loading adult hosts...

+
+
+ +

Loading adult hosts...

+
@@ -415,12 +674,270 @@
- DeepBird + Deepbrid

Check supported file hosts

+ + + + + + +
+
+
+

Frequently Asked Questions

+

Straight answers to common debrid questions

+
+ +
+
+

General

+ +
+ + What is a debrid service? + + +
+ They fetch files to their servers and give you fast, direct links with no ads or host limits. +
+
+ +
+ + Are debrid services legal? + + +
+ The services are legal; you’re responsible for what you download. +
+
+ +
+ + Can I use multiple services? + + +
+ Yes — power users combine providers for broader coverage and redundancy. +
+
+
+ +
+

Technical

+ +
+ + Why do speeds vary? + + +
+ Distance, congestion, host performance, ISP routing, and local setup. +
+
+ +
+ + Do services support torrents? + + +
+ Most offer torrent‑to‑direct‑download (cloud first, then direct link). +
+
+ +
+ + Are there download limits? + + +
+ Many market “unlimited,” but fair‑use or soft caps can apply. +
+
+
+ +
+

Accounts & Billing

+ +
+ + Can I share my account? + + +
+ Usually prohibited. Services may enforce IP/session limits. +
+
+ +
+ + Payment methods? + + +
+ Cards and PayPal are common; some support crypto (check each site). +
+
+
+ +
+

Security & Privacy

+ +
+ + Do services log downloads? + + +
+ Policies vary; many keep minimal technical logs. Check privacy pages. +
+
+ +
+ + Can my ISP see content? + + +
+ Over HTTPS they see connections/volume, not filenames. VPN adds privacy. +
+
+
+
+
+
+ + +
+
+
+

Important Notes

+

+ This project aims to provide accurate and up-to-date information, but the debrid service landscape is dynamic. Please keep the following in mind: +

+
+ +
+
+
👉
+

Services change frequently

+

Pricing, host support, refund policies, and features may be updated without notice. Always verify details on the official service websites before purchasing.

+
+ +
+
👉
+

Final cost may vary

+

Displayed prices are subject to exchange rates, regional taxes, or payment processing fees. Your actual charge may differ slightly.

+
+ +
+
👉
+

Data accuracy

+

While we strive for completeness, this comparison reflects community reports and public information. We do not guarantee uptime, speed, download success, or feature availability.

+
+ +
+
👉
+

No affiliation

+

This project is independent and not affiliated with AllDebrid, Real-Debrid, Premiumize, TorBox, LinkSnappy, Debrid-Link, Mega-Debrid, Deepbrid, or any other listed service.

+
+ +
+
👉
+

Use at your own discretion

+

Choosing a debrid service involves personal judgment. Test short-term plans first and review terms carefully.

+
+ +
+
👉
+

Legal responsibility

+

Debrid services are tools. You are responsible for complying with copyright laws and terms of use when accessing content.

+
+
+ + +
+
+ + +
+
+
+

Useful Resources and Community

+

Enhance your debrid experience with these trusted tools, guides, and communities

+
+ +
+ + +
+

👥 Community and Support

+
+
+ Reddit + Active discussions on r/Debrid, r/Piracy and service-specific threads +
+
+ Discord + Many services offer official Discord servers for real-time support and status updates +
+
+ GitHub Issues + Report inaccuracies or suggest improvements here — this project thrives on community input! +
+
+
+
+ +
@@ -464,69 +981,39 @@ + - - \ No newline at end of file diff --git a/dist/js/app.js b/dist/js/app.js index a17d6ad..1581625 100644 --- a/dist/js/app.js +++ b/dist/js/app.js @@ -1,53 +1,56 @@ /** - * @fileoverview Optimized Debrid Services Comparison Application - * Performance-focused implementation with modern ES6+ features + * Debrid Services Comparison - Improved App JS + * - Data loading (file hosts, adult hosts, pricing) + * - Tables, comparison, pricing interactions + * - Age verification overlay (focus trap + inert) + * - Back-to-top, nav highlight, animations, offline detection + * - Performance-minded DOM updates and event delegation */ -// Optimized utility module using singleton pattern +/* ============================== + Utils + ============================== */ 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); - } - + + if (callNow) func.apply(this, args); + timeoutId = setTimeout(() => { timeoutId = null; if (!immediate) func.apply(this, args); }, delay); }; - + debounceCache.set(func, debounced); return debounced; }, - throttle(func, limit) { - let inThrottle; + throttle(func, limit = 100) { + let ticking = false; return function(...args) { - if (!inThrottle) { + if (!ticking) { func.apply(this, args); - inThrottle = true; + ticking = true; requestAnimationFrame(() => { - setTimeout(() => inThrottle = false, limit); + setTimeout(() => { ticking = false; }, limit); }); } }; }, - // Optimized Intersection Observer with single instance animateOnScroll: (() => { let observer; return (elements, options = {}) => { + if (!('IntersectionObserver' in window)) return; if (!observer) { observer = new IntersectionObserver((entries) => { entries.forEach(entry => { @@ -68,7 +71,9 @@ const Utils = (() => { }; })(); -// Optimized LoadingManager with DOM fragment usage +/* ============================== + Loading Manager + ============================== */ class LoadingManager { constructor() { this.activeLoaders = new Map(); @@ -96,49 +101,46 @@ class LoadingManager { const targetEl = typeof target === 'string' ? document.querySelector(target) : target; if (!targetEl) return null; - const loaderId = `loader-${Date.now()}`; + const loaderId = `loader-${Date.now()}-${Math.random().toString(36).slice(2)}`; const loader = this.template.content.cloneNode(true).firstElementChild; - + loader.dataset.loaderId = loaderId; loader.querySelector('.loading-text').textContent = text; - - targetEl.style.position = 'relative'; + + const prevPos = getComputedStyle(targetEl).position; + if (prevPos === 'static' || !prevPos) 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) { const loader = this.activeLoaders.get(loaderId); if (!loader) return; - loader.classList.add('loading-overlay--hiding'); this.activeLoaders.delete(loaderId); - - setTimeout(() => loader.remove(), 300); + setTimeout(() => loader.remove(), 250); } hideAll() { - this.activeLoaders.forEach((loader, id) => { - this.hide(null, id); - }); + this.activeLoaders.forEach((_, id) => this.hide(null, id)); } } -// Optimized TableManager with virtual scrolling support +/* ============================== + Table Manager (file/adult hosts) + ============================== */ class TableManager { constructor(containerId, searchInputId, clearIconId, options = {}) { - // Cache DOM elements this.elements = { container: document.getElementById(containerId), searchInput: document.getElementById(searchInputId), clearIcon: document.getElementById(clearIconId) }; - + this.options = { sortable: true, filterable: true, pagination: false, itemsPerPage: 50, ...options }; this.state = { currentData: {}, @@ -146,11 +148,10 @@ class TableManager { currentSort: { column: null, direction: 'asc' }, currentPage: 1 }; - - // Pre-bind event handlers for better performance - this.handleSearch = Utils.debounce(this._performSearch.bind(this), 300); + + this.handleSearch = Utils.debounce(this._performSearch.bind(this), 250); this.handleSort = this._performSort.bind(this); - + this._init(); } @@ -161,13 +162,14 @@ class TableManager { if (e.key === 'Escape') this._clearSearch(); }); } - + if (this.elements.clearIcon) { this.elements.clearIcon.addEventListener('click', () => this._clearSearch()); } } generateTable(data = {}) { + if (!this.elements.container) return; if (!Object.keys(data).length) { this.elements.container.innerHTML = '

No data available.

'; return; @@ -175,37 +177,34 @@ class TableManager { this.state.currentData = data; this.state.filteredData = { ...data }; - - const providers = Object.keys(data[Object.keys(data)[0]]); + + const firstKey = Object.keys(data)[0]; + const providers = firstKey ? Object.keys(data[firstKey]) : []; const tableId = this.elements.container.id.replace('-container', ''); - - // 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(); } @@ -214,14 +213,14 @@ class TableManager { Service Name - + ${providers.map(provider => ` ${provider} - + @@ -231,7 +230,6 @@ class TableManager { } _generateTableRows(data, providers) { - // Use array methods for better performance const rows = []; for (const [host, providerData] of Object.entries(data)) { rows.push(` @@ -245,11 +243,10 @@ class TableManager { const isSupported = providerData[provider] === 'yes'; return ` - - ${isSupported ? - '' : - '' + + ${isSupported + ? '' + : '' } @@ -263,61 +260,58 @@ class TableManager { _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')) { + const target = e.target.closest('.sortable'); + if (!target) return; + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); - this.handleSort(e); + this.handleSort({ target }); } }); } } _performSearch() { - const searchTerm = this.elements.searchInput.value.toLowerCase().trim(); - - if (!searchTerm) { + const term = (this.elements.searchInput?.value || '').toLowerCase().trim(); + + if (!term) { this.state.filteredData = { ...this.state.currentData }; - this.elements.clearIcon.style.display = 'none'; + if (this.elements.clearIcon) 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) - ) + Object.entries(this.state.currentData).filter(([host]) => host.toLowerCase().includes(term)) ); - this.elements.clearIcon.style.display = 'block'; + if (this.elements.clearIcon) this.elements.clearIcon.style.display = 'block'; } - + this._updateTableContent(); - this._updateSearchResults(searchTerm); + this._updateSearchResults(term); } _clearSearch() { - this.elements.searchInput.value = ''; + if (this.elements.searchInput) this.elements.searchInput.value = ''; this.state.filteredData = { ...this.state.currentData }; - this.elements.clearIcon.style.display = 'none'; + if (this.elements.clearIcon) this.elements.clearIcon.style.display = 'none'; this._updateTableContent(); this._updateSearchResults(''); } _updateSearchResults(searchTerm) { + if (!this.elements.container) return; 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 if (indicator) { @@ -328,22 +322,21 @@ class TableManager { _performSort(event) { const header = event.target.closest('.sortable'); if (!header) return; - + const column = header.dataset.column; const { currentSort } = this.state; const newDirection = currentSort.column === column && currentSort.direction === 'asc' ? 'desc' : 'asc'; - + 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'); }); - + header.setAttribute('aria-sort', newDirection === 'asc' ? 'ascending' : 'descending'); header.classList.add(`sorted-${newDirection}`); - + this._sortData(); this._updateTableContent(); } @@ -351,32 +344,32 @@ class TableManager { _sortData() { const { column, direction } = this.state.currentSort; const entries = Object.entries(this.state.filteredData); - + entries.sort(([hostA, dataA], [hostB, dataB]) => { const valueA = column === 'service' ? hostA.toLowerCase() : (dataA[column] === 'yes' ? 1 : 0); const valueB = column === 'service' ? hostB.toLowerCase() : (dataB[column] === 'yes' ? 1 : 0); - + const comparison = valueA > valueB ? 1 : valueA < valueB ? -1 : 0; return direction === 'asc' ? comparison : -comparison; }); - + this.state.filteredData = Object.fromEntries(entries); } _updateTableContent() { const tbody = this.elements.container.querySelector('tbody'); if (!tbody) return; - - const providers = Object.keys(this.state.currentData[Object.keys(this.state.currentData)[0]] || {}); - - // Use requestAnimationFrame for smoother updates + const firstKey = Object.keys(this.state.currentData)[0]; + const providers = firstKey ? Object.keys(this.state.currentData[firstKey]) : []; requestAnimationFrame(() => { tbody.innerHTML = this._generateTableRows(this.state.filteredData, providers); }); } } -// Optimized ComparisonManager +/* ============================== + Comparison Manager + ============================== */ class ComparisonManager { constructor(containerId, select1Id, select2Id, data) { this.elements = { @@ -384,39 +377,30 @@ class ComparisonManager { 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(); - - if (this.elements.select1) { - this.elements.select1.addEventListener('change', this.handleCompare); - } - - if (this.elements.select2) { - this.elements.select2.addEventListener('change', this.handleCompare); - } - + + this.elements.select1?.addEventListener('change', this.handleCompare); + this.elements.select2?.addEventListener('change', this.handleCompare); + document.addEventListener('keydown', e => { - if (this.isComparing && e.key === 'Escape') { - this._closeComparison(); - } + 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(''); - + const firstKey = Object.keys(this.data)[0]; + const providers = firstKey ? Object.keys(this.data[firstKey]) : []; + const optionsHTML = '' + + providers.map(p => ``).join(''); + [this.elements.select1, this.elements.select2].forEach(select => { if (select) select.innerHTML = optionsHTML; }); @@ -430,7 +414,6 @@ class ComparisonManager { this._showEmptyState(); return; } - if (provider1 === provider2) { this._showSameProviderWarning(); return; @@ -439,7 +422,6 @@ class ComparisonManager { this.isComparing = true; const loaderId = loadingManager.show(this.elements.container, 'Generating comparison...'); - // Use RAF for smoother rendering requestAnimationFrame(() => { this._renderComparisonTable(provider1, provider2); loadingManager.hide(this.elements.container, loaderId); @@ -448,38 +430,27 @@ class ComparisonManager { _renderComparisonTable(provider1, provider2) { const stats = this._calculateComparisonStats(provider1, provider2); - - // Build DOM efficiently const fragment = document.createDocumentFragment(); const wrapper = document.createElement('div'); - + wrapper.innerHTML = `

Comparing ${provider1} vs ${provider2}

-
- Shared Support - ${stats.shared} -
-
- ${provider1} Only - ${stats.provider1Only} -
-
- ${provider2} Only - ${stats.provider2Only} -
+
Shared Support${stats.shared}
+
${provider1} Only${stats.provider1Only}
+
${provider2} Only${stats.provider2Only}
- +
`; - + fragment.appendChild(wrapper); - this.elements.container.innerHTML = ''; this.elements.container.appendChild(fragment); this.elements.container.style.display = 'block'; - this._attachComparisonEvents(); - + requestAnimationFrame(() => { this.elements.container.classList.add('comparison-visible'); }); @@ -527,76 +496,51 @@ class ComparisonManager { _generateComparisonRows(provider1, provider2) { const rows = []; - + const checkSvg = ''; + const crossSvg = ''; + for (const [host, providerData] of Object.entries(this.data)) { const support1 = providerData[provider1] === 'yes'; const support2 = providerData[provider2] === 'yes'; - - let statusClass, 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'; - } - const checkSvg = ''; - const crossSvg = ''; + let statusClass = 'neither-supported'; + let statusText = 'Neither'; + 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`; } rows.push(` ${host} - - ${support1 ? checkSvg : crossSvg} - + ${support1 ? checkSvg : crossSvg} - - ${support2 ? checkSvg : crossSvg} - - - - ${statusText} + ${support2 ? checkSvg : crossSvg} + ${statusText} `); } - return rows.join(''); } _calculateComparisonStats(provider1, provider2) { let shared = 0, provider1Only = 0, provider2Only = 0; - 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++; + const s1 = providerData[provider1] === 'yes'; + const s2 = providerData[provider2] === 'yes'; + if (s1 && s2) shared++; + else if (s1) provider1Only++; + else if (s2) provider2Only++; } - return { shared, provider1Only, provider2Only }; } _attachComparisonEvents() { - // Close button const closeBtn = this.elements.container.querySelector('#close-compare'); - if (closeBtn) { - closeBtn.addEventListener('click', () => this._closeComparison()); - } + closeBtn?.addEventListener('click', () => this._closeComparison()); - // Filter functionality with event delegation this.elements.container.addEventListener('change', e => { if (e.target.name === 'comparison-filter') { this._filterComparison(e.target.value); @@ -607,18 +551,16 @@ class ComparisonManager { _filterComparison(filter) { const rows = this.elements.container.querySelectorAll('.comparison-row'); let visibleCount = 0; - + rows.forEach(row => { const status = row.dataset.status; - let show = filter === 'all' || - (filter === 'both' && status === 'both-supported') || - (filter === 'different' && (status === 'provider1-only' || status === 'provider2-only')); - + const 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 + let indicator = this.elements.container.querySelector('.filter-results'); if (!indicator) { indicator = document.createElement('div'); @@ -626,25 +568,23 @@ class ComparisonManager { const tableWrapper = this.elements.container.querySelector('.table-wrapper'); tableWrapper.parentNode.insertBefore(indicator, tableWrapper); } - indicator.textContent = `Showing ${visibleCount} services`; } _closeComparison() { this.elements.container.classList.add('comparison-hiding'); - setTimeout(() => { this.elements.container.style.display = 'none'; this.elements.container.classList.remove('comparison-visible', 'comparison-hiding'); this.isComparing = false; this._showEmptyState(); - }, 300); + }, 250); } _showEmptyState() { this.elements.container.innerHTML = `
-
⚖️
+

Ready to Compare

Select two services above to see a detailed comparison

@@ -664,14 +604,20 @@ class ComparisonManager { } } -// Optimized PricingManager +/* ============================== + Pricing Manager + ============================== */ class PricingManager { constructor(containerId) { this.container = document.getElementById(`${containerId}-table-container`); this.currentData = null; + this._onMouseOver = this._onMouseOver.bind(this); + this._onMouseOut = this._onMouseOut.bind(this); + this._onMouseLeaveContainer = this._onMouseLeaveContainer.bind(this); } generatePricingTable(data = {}) { + if (!this.container) return; if (!data.plans?.length) { this.container.innerHTML = '

Error: Invalid pricing data structure.

'; return; @@ -679,18 +625,16 @@ class PricingManager { this.currentData = data; const services = Object.keys(data.plans[0]).filter(key => key !== 'name'); - - // 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 = ` @@ -704,8 +648,7 @@ class PricingManager { `).join('')} `; - - // Create body + const tbody = document.createElement('tbody'); tbody.innerHTML = data.plans.map((plan, index) => ` @@ -713,7 +656,6 @@ class PricingManager { ${services.map(service => { const price = plan[service]; const isNumeric = !isNaN(parseFloat(price)); - return `
@@ -725,146 +667,84 @@ class PricingManager { }).join('')} `).join(''); - + table.appendChild(thead); table.appendChild(tbody); wrapper.appendChild(table); fragment.appendChild(wrapper); - + this.container.innerHTML = ''; this.container.appendChild(fragment); - + this._setupPricingFeatures(); } _setupPricingFeatures() { - // 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); - } - }, true); - - this.container.addEventListener('mouseleave', e => { - if (e.target.classList.contains('price-cell')) { - this._removeColumnHighlight(); - } - }, true); + // Use mouseover/mouseout for delegation; mouseenter doesn't bubble + this.container.addEventListener('mouseover', this._onMouseOver); + this.container.addEventListener('mouseout', this._onMouseOut); + this.container.addEventListener('mouseleave', this._onMouseLeaveContainer); + } + + _onMouseOver(e) { + const cell = e.target.closest('.price-cell'); + if (!cell || !this.container.contains(cell)) return; + const index = Array.from(cell.parentNode.children).indexOf(cell); + if (index >= 0) this._highlightColumn(index + 1); // nth-child is 1-based + } + + _onMouseOut(e) { + const toElement = e.relatedTarget; + if (!toElement || !this.container.contains(toElement)) { + this._removeColumnHighlight(); + } + } + + _onMouseLeaveContainer() { + this._removeColumnHighlight(); } _highlightColumn(columnIndex) { - const cells = this.container.querySelectorAll(`td:nth-child(${columnIndex + 1}), th:nth-child(${columnIndex + 1})`); + const table = this.container.querySelector('table'); + if (!table) return; + // Plans column is 1; services start at 2; columnIndex is the index within row (including plan col) + const cells = table.querySelectorAll(`tbody td:nth-child(${columnIndex + 1}), thead th:nth-child(${columnIndex + 1})`); + this._removeColumnHighlight(); cells.forEach(cell => cell.classList.add('highlighted')); } _removeColumnHighlight() { - const highlightedCells = this.container.querySelectorAll('.highlighted'); - highlightedCells.forEach(cell => cell.classList.remove('highlighted')); + this.container.querySelectorAll('.highlighted').forEach(cell => cell.classList.remove('highlighted')); } } -// Simplified PerformanceMonitor +/* ============================== + Performance Monitor (light) + ============================== */ class PerformanceMonitor { - constructor() { - this.marks = new Map(); - this.mark('start'); - } - - mark(name) { - this.marks.set(name, performance.now()); - } - + constructor() { this.marks = new Map(); this.mark('start'); } + mark(name) { this.marks.set(name, performance.now()); } measure(name, startMark = 'start') { - const start = this.marks.get(startMark); + const start = this.marks.get(startMark) || performance.now(); const end = performance.now(); return end - start; } - - log(name, startMark = 'start') { - console.log(`${name}: ${this.measure(name, startMark).toFixed(2)}ms`); - } + log(name, startMark = 'start') { console.log(`${name}: ${this.measure(name, startMark).toFixed(2)}ms`); } } -// Global instances -const loadingManager = new LoadingManager(); -const performanceMonitor = new PerformanceMonitor(); - -// Optimized initialization -document.addEventListener('DOMContentLoaded', async () => { - performanceMonitor.mark('dom-ready'); - performanceMonitor.log('DOM Ready'); - - try { - // 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 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...') - ]; - - // 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 - 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); - } - }); - - // Setup additional features - setupURLComparison(); - setupScrollAnimations(); - setupOfflineDetection(); - - performanceMonitor.mark('fully-loaded'); - performanceMonitor.log('Fully Loaded'); - - } catch (error) { - console.error('Critical error:', error); - loadingManager.hideAll(); - } -}); - -// Optimized helper functions +/* ============================== + Feature setup + ============================== */ function setupURLComparison() { const params = new URLSearchParams(window.location.search); const provider1 = params.get('compare'); const provider2 = params.get('with'); - + if (provider1 && provider2) { requestAnimationFrame(() => { const select1 = document.getElementById('provider1'); const select2 = document.getElementById('provider2'); - + if (select1 && select2) { select1.value = provider1; select2.value = provider2; @@ -875,24 +755,251 @@ function setupURLComparison() { } function setupScrollAnimations() { - const animatedElements = document.querySelectorAll('.section, .card, .quick-nav-card'); - if (animatedElements.length) { - Utils.animateOnScroll(animatedElements); - } + const animatedElements = document.querySelectorAll('.section, .quick-nav-card, .status-card, .referral-card'); + if (animatedElements.length) Utils.animateOnScroll(animatedElements); } function setupOfflineDetection() { const updateOnlineStatus = () => { console.log(navigator.onLine ? 'Connection restored' : 'You are offline. Some features may not work.'); }; - window.addEventListener('online', updateOnlineStatus); window.addEventListener('offline', updateOnlineStatus); } -// Optimized error handling -window.addEventListener('error', e => console.error('Global error:', e.error), { passive: true }); +function setupBackToTop() { + const btn = document.getElementById('backToTop'); + if (!btn) return; + + const onScroll = Utils.throttle(() => { + if (window.pageYOffset > 300) btn.classList.add('visible'); + else btn.classList.remove('visible'); + }, 150); + + window.addEventListener('scroll', onScroll, { passive: true }); + btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); +} + +function setupNavHighlight() { + const links = Array.from(document.querySelectorAll('.header-nav .nav-link[href^="#"]')); + if (!('IntersectionObserver' in window) || !links.length) return; + + const sections = links + .map(a => document.querySelector(a.getAttribute('href'))) + .filter(Boolean); + + const map = new Map(); + links.forEach(a => map.set(a.getAttribute('href'), a)); + + const io = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const id = `#${entry.target.id}`; + const link = map.get(id); + if (!link) return; + if (entry.isIntersecting) { + links.forEach(l => l.removeAttribute('aria-current')); + link.setAttribute('aria-current', 'page'); + } + }); + }, { rootMargin: '-40% 0px -50% 0px', threshold: 0.1 }); + + sections.forEach(sec => io.observe(sec)); +} + +/* Age verification overlay with focus trap + inert */ +function setupAgeVerification() { + const overlay = document.getElementById('age-verification-overlay'); + const adultContent = document.getElementById('adult-content'); + const confirmBtn = document.getElementById('confirm-age'); + const denyBtn = document.getElementById('deny-age'); + const pageWrapper = document.querySelector('.page-wrapper'); + const adultSection = document.getElementById('adult-hosts'); + + if (!overlay || !adultSection) return; + + // Enhance semantics if not present + const modal = overlay.querySelector('.age-verification-modal'); + const titleEl = overlay.querySelector('.age-verification-content h3'); + + if (modal) { + modal.setAttribute('role', 'dialog'); + modal.setAttribute('aria-modal', 'true'); + if (titleEl) { + const titleId = 'age-title'; + titleEl.id = titleId; + modal.setAttribute('aria-labelledby', titleId); + } + } + + const setInert = (isInert) => { + if (!pageWrapper) return; + if ('inert' in pageWrapper) { + pageWrapper.inert = isInert; + } else { + pageWrapper.setAttribute('aria-hidden', String(isInert)); + } + }; + + const FOCUSABLE = 'a[href], area[href], button, input, select, textarea, details, summary, [tabindex]:not([tabindex="-1"])'; + let removeTrap = null; + const trapFocus = (container) => { + const nodes = Array.from(container.querySelectorAll(FOCUSABLE)) + .filter(el => !el.hasAttribute('disabled') && el.getAttribute('aria-hidden') !== 'true'); + const first = nodes[0]; + const last = nodes[nodes.length - 1]; + + const onKeydown = (e) => { + if (e.key === 'Escape') { + // Allow closing via deny to maintain your original flow + denyBtn?.click(); + } else if (e.key === 'Tab') { + if (nodes.length === 0) { e.preventDefault(); return; } + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); first.focus(); + } + } + }; + container.addEventListener('keydown', onKeydown); + first?.focus(); + return () => container.removeEventListener('keydown', onKeydown); + }; + + const openOverlay = () => { + overlay.style.display = 'flex'; + overlay.setAttribute('aria-hidden', 'false'); + setInert(true); + removeTrap = trapFocus(modal || overlay); + }; + + const closeOverlay = () => { + overlay.style.display = 'none'; + overlay.setAttribute('aria-hidden', 'true'); + setInert(false); + if (typeof removeTrap === 'function') removeTrap(); + }; + + // State + const ageConfirmed = localStorage.getItem('age-confirmed') === 'true'; + if (ageConfirmed) { + overlay.style.display = 'none'; + if (adultContent) adultContent.style.display = 'block'; + } + + confirmBtn?.addEventListener('click', () => { + localStorage.setItem('age-confirmed', 'true'); + if (adultContent) adultContent.style.display = 'block'; + closeOverlay(); + }); + + denyBtn?.addEventListener('click', () => { + document.getElementById('what-are-debrid')?.scrollIntoView({ behavior: 'smooth' }); + closeOverlay(); + }); + + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting && localStorage.getItem('age-confirmed') !== 'true') { + openOverlay(); + } + }); + }, { threshold: 0.1 }); + observer.observe(adultSection); + } +} + +/* ============================== + Global singletons + ============================== */ +const loadingManager = new LoadingManager(); +const performanceMonitor = new PerformanceMonitor(); + +/* ============================== + App bootstrap + ============================== */ +document.addEventListener('DOMContentLoaded', async () => { + performanceMonitor.mark('dom-ready'); + performanceMonitor.log('DOM Ready'); + + try { + 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'); + + 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...') + ]; + + 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} for ${url}`))) + ); + + const results = await Promise.allSettled(fetchPromises); + + results.forEach((result, index) => { + const containers = ['#file-hosts-table-container', '#adult-hosts-table-container', '#pricing-table-container']; + loadingManager.hide(containers[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 (${dataUrls[index]}):`, result.reason); + const el = document.querySelector(containers[index]); + if (el) el.innerHTML = `

Failed to load data: ${dataUrls[index]}

`; + } + }); + + // UI features + setupURLComparison(); + setupScrollAnimations(); + setupOfflineDetection(); + setupBackToTop(); + setupNavHighlight(); + setupAgeVerification(); + + performanceMonitor.mark('fully-loaded'); + performanceMonitor.log('Fully Loaded'); + + } catch (error) { + console.error('Critical error:', error); + loadingManager.hideAll(); + } +}); + +/* ============================== + Global error handling & SW + ============================== */ +window.addEventListener('error', e => { + console.error('Global error:', e.error || e.message || e); +}, { passive: true }); + window.addEventListener('unhandledrejection', e => { console.error('Unhandled promise rejection:', e.reason); - e.preventDefault(); -}, { passive: false }); \ No newline at end of file +}); + +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(err => { + console.warn('Service worker registration failed:', err); + }); + }); +} \ No newline at end of file