diff --git a/css/base.css b/css/base.css index 33454e2..2172b69 100644 --- a/css/base.css +++ b/css/base.css @@ -9,7 +9,7 @@ --primary-hover: #0066cc; --primary-light: #e6f3ff; --surface: #ffffff; - --background: #f8fafc; + --background: #eef2f6; --background-light: #ffffff; --text: #000000; --text-secondary: #536471; @@ -55,8 +55,8 @@ --button-transition: 200ms ease-in-out; } -/* Dark Theme (Dim) */ -[data-theme="dim"] { +/* Dark Theme */ +[data-theme="dark"] { --surface: #15202b; --background: #1e2732; --background-light: #1a2634; @@ -154,7 +154,7 @@ a:hover { /* System Theme Detection */ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { - /* Default to dim theme when system prefers dark */ + /* Default to dark theme when system prefers dark */ --surface: #15202b; --background: #1e2732; --background-light: #1a2634; diff --git a/css/components/auth.css b/css/components/auth.css index 45e255d..8d9674e 100644 --- a/css/components/auth.css +++ b/css/components/auth.css @@ -66,6 +66,8 @@ .bsky-handle-input { background: var(--background); padding-left: 36px; + border: 1px solid rgba(0, 122, 255, 0.15); + transition: border-color 0.2s ease-in-out; } /* Error States */ diff --git a/css/components/cards.css b/css/components/cards.css index 70dfe29..b0cd3dc 100644 --- a/css/components/cards.css +++ b/css/components/cards.css @@ -15,7 +15,6 @@ padding: 24px; border: 1px solid var(--border); border-radius: 20px; - transition: all 0.2s ease-in-out; display: flex; align-items: flex-start; gap: 16px; @@ -33,7 +32,6 @@ height: 48px; background: var(--background); border-radius: 14px; - transition: transform 0.2s ease; } .landing-feature-card .feature-text { @@ -59,18 +57,6 @@ margin: 0; } -/* Landing Feature Card Hover Effects */ -.landing-feature-card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - border-color: var(--primary); - background: linear-gradient(to bottom right, var(--surface), var(--background)); -} - -.landing-feature-card:hover .feature-icon { - transform: scale(1.1); -} - /* Hover state for other cards */ .feature-card:hover, .context-card:hover { diff --git a/css/components/footer.css b/css/components/footer.css index 42b606b..2d81f65 100644 --- a/css/components/footer.css +++ b/css/components/footer.css @@ -111,7 +111,7 @@ .theme-toggle .moon-icon { margin-left: auto; - transform: translateX(2px); + transform: translateX(0px); } /* Hide emoji in dark mode */ diff --git a/css/components/landing.css b/css/components/landing.css index d7cb1d1..4235a52 100644 --- a/css/components/landing.css +++ b/css/components/landing.css @@ -1,166 +1,7 @@ -/* Split Layout for Landing Page */ -.split-layout { - display: flex; - height: 100vh; - background: var(--surface); - overflow: hidden; -} - -/* Branding Section (Left) */ -.branding-section { - flex: 0 0 var(--branding-width); - background: var(--background); - display: flex; - align-items: center; - justify-content: center; - padding: 32px; -} - -.branding-content { - text-align: center; - max-width: 420px; -} - -.logo { - margin-bottom: 24px; - filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1)); -} - -.logo img { - width: 245px; /* Half of original 490px width */ - height: auto; - display: block; - margin: 0 auto; -} - -.branding-content h1 { - font-size: 48px; - font-weight: 800; - color: var(--text); - margin-bottom: 16px; - letter-spacing: -0.02em; -} - -.tagline { - font-size: 20px; - line-height: 1.4; - color: var(--text-secondary); - margin: 0 auto; - max-width: 360px; -} - -/* Content Section (Right) */ -.content-section { - flex: 0 0 var(--content-width); - background: var(--surface); - display: flex; - flex-direction: column; - height: 100vh; -} - -.content-wrapper { - max-width: 720px; - margin: 0 auto; - padding: 32px; - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - height: 100%; - gap: 32px; -} - -/* Landing Content */ -.landing-content { - max-width: 800px; - margin: 0 auto; - padding: var(--spacing-xl); - text-align: center; - min-height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; - gap: var(--spacing-xl); -} - -.landing-header h1 { - font-size: 2.5rem; - margin-bottom: var(--spacing-md); -} - -.feature-card h3 { - color: var(--primary); - margin-bottom: var(--spacing-sm); -} - -/* Responsive Layout */ -@media (max-width: 1200px) { - .content-wrapper { - padding: 24px; - } -} - -@media (max-width: 1024px) { - .split-layout { - flex-direction: column; - height: auto; - min-height: 100vh; - } - - .branding-section, - .content-section { - flex: 0 0 auto; - width: 100%; - height: auto; - } - - .branding-section { - padding: 48px 24px; - } - - .content-wrapper { - padding: 32px 24px; - gap: 32px; - } - - .branding-content { - max-width: 100%; - } - - .tagline { - max-width: 480px; - } -} - -@media (max-width: 768px) { - .landing-content { - padding: var(--spacing-md); - } - - .logo img { - width: 200px; - } -} - -@media (max-width: 480px) { - .branding-section { - padding: 32px 20px; - } - - .content-wrapper { - padding: 24px 20px; - } - - .logo img { - width: 160px; - } - - .branding-content h1 { - font-size: 36px; - margin-bottom: 12px; - } - - .tagline { - font-size: 18px; - } -} +/* Landing Page Styles - Split into modular files */ +@import 'landing/layout.landing.css'; +@import 'landing/branding.landing.css'; +@import 'landing/features.landing.css'; +@import 'landing/auth.landing.css'; +@import 'landing/theme-toggle.landing.css'; +@import 'landing/responsive.landing.css'; diff --git a/css/components/landing/auth.landing.css b/css/components/landing/auth.landing.css new file mode 100644 index 0000000..fe6a71a --- /dev/null +++ b/css/components/landing/auth.landing.css @@ -0,0 +1,35 @@ +/* Auth Section */ +.bsky-connect { + padding: 32px; + background: var(--background); + border-radius: 12px; + margin-bottom: 96px; + position: relative; +} + +.bsky-connect:after { + content: ""; + position: absolute; + bottom: -48px; + left: 50%; + transform: translateX(-50%); + width: 64px; + height: 4px; + background: var(--primary); + border-radius: 2px; + opacity: 0.3; +} + +.sign-in-title { + font-size: 24px; + font-weight: 700; + margin-bottom: 24px; + color: var(--text); +} + +.bsky-auth-message { + color: var(--text-secondary); + margin-bottom: 16px; + font-size: 16px; + line-height: 1.5; +} diff --git a/css/components/landing/branding.landing.css b/css/components/landing/branding.landing.css new file mode 100644 index 0000000..fb04d39 --- /dev/null +++ b/css/components/landing/branding.landing.css @@ -0,0 +1,77 @@ +/* Branding Section (Left) */ +.branding-section { + flex: 0 0 var(--branding-width); + background: var(--background); + position: sticky; + top: 0; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 32px; +} + +.branding-content { + text-align: center; + max-width: 420px; +} + +.logo { + margin-bottom: 24px; + filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1)); +} + +.logo img { + width: 245px; + height: auto; + display: block; + margin: 0 auto; +} + +.branding-content h1 { + font-size: 48px; + font-weight: 800; + color: var(--text); + margin-bottom: 16px; + letter-spacing: -0.02em; +} + +.tagline { + font-size: 20px; + line-height: 1.4; + color: var(--text-secondary); + margin: 0 auto; + max-width: 360px; +} + +/* Built by Section */ +.built-by-section { + text-align: center; + margin-top: 64px; + padding-top: 32px; + border-top: 1px solid var(--border); +} + +.built-by-content { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; +} + +.built-by-content p { + font-size: 16px; + line-height: 1.5; + color: var(--text-secondary); + margin: 0; +} + +.built-by-content a { + color: var(--primary); + text-decoration: none; + transition: color 0.2s ease; +} + +.built-by-content a:hover { + color: var(--primary-hover); +} diff --git a/css/components/landing/features.landing.css b/css/components/landing/features.landing.css new file mode 100644 index 0000000..e534131 --- /dev/null +++ b/css/components/landing/features.landing.css @@ -0,0 +1,178 @@ +/* Feature Grid */ +.feature-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + margin-bottom: 32px; +} + +.landing-feature-card { + background: var(--background); + padding: 24px; + border-radius: 12px; + display: flex; + align-items: flex-start; + gap: 16px; +} + +.feature-icon { + font-size: 24px; + line-height: 1; +} + +.feature-text h3 { + font-size: 18px; + font-weight: 600; + margin-bottom: 8px; + color: var(--text); +} + +.feature-text p { + font-size: 14px; + line-height: 1.5; + color: var(--text-secondary); + margin: 0; +} + +/* Detailed Features Section */ +.detailed-features { + padding-top: 0; +} + +.section-intro { + text-align: center; + margin-bottom: 48px; + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +.section-intro h2 { + font-size: 36px; + font-weight: 800; + color: var(--text); + margin-bottom: 16px; + letter-spacing: -0.02em; +} + +.section-intro p { + font-size: 18px; + line-height: 1.6; + color: var(--text-secondary); +} + +.feature-blocks { + display: flex; + flex-direction: column; + gap: 48px; +} + +.feature-block { + display: flex; + flex-direction: column; + gap: 32px; + opacity: 0; + transform: translateY(20px); + animation: fadeInUp 0.6s ease forwards; +} + +.feature-block:nth-child(1) { animation-delay: 0.1s; } +.feature-block:nth-child(2) { animation-delay: 0.2s; } +.feature-block:nth-child(3) { animation-delay: 0.3s; } +.feature-block:nth-child(4) { animation-delay: 0.4s; } + +@keyframes fadeInUp { + to { + opacity: 1; + transform: translateY(0); + } +} + +.feature-image { + border-radius: 12px; + overflow: hidden; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + background: var(--background); + aspect-ratio: 16 / 9; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + transition: background-image 0.3s ease; + position: relative; +} + +/* Add loading state */ +.feature-image.loading::before { + content: ''; + position: absolute; + inset: 0; + background-color: var(--background); + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0% { opacity: 0.6; } + 50% { opacity: 0.8; } + 100% { opacity: 0.6; } +} + +/* Error state styling */ +.feature-image.image-load-error { + background-color: var(--background); + border: 2px dashed var(--border-color); +} + +.feature-image.image-load-error::after { + content: '⚠️ Image failed to load'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--text); + font-size: 14px; + text-align: center; + padding: 8px 16px; + background-color: var(--background); + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.feature-description { + color: var(--text); +} + +.feature-description h3 { + font-size: 24px; + font-weight: 700; + margin-bottom: 16px; + color: var(--text); +} + +.feature-description p { + font-size: 16px; + line-height: 1.6; + color: var(--text-secondary); + margin-bottom: 16px; +} + +.feature-description ul { + list-style: none; + padding: 0; + margin: 16px 0; +} + +.feature-description li { + font-size: 16px; + line-height: 1.6; + color: var(--text-secondary); + margin-bottom: 8px; + padding-left: 24px; + position: relative; +} + +.feature-description li:before { + content: "•"; + position: absolute; + left: 8px; + color: var(--primary); +} diff --git a/css/components/landing/layout.landing.css b/css/components/landing/layout.landing.css new file mode 100644 index 0000000..1dc978e --- /dev/null +++ b/css/components/landing/layout.landing.css @@ -0,0 +1,21 @@ +/* Split Layout for Landing Page */ +.split-layout { + display: flex; + min-height: 100vh; + background: var(--surface); +} + +/* Content Section (Right) */ +.content-section { + flex: 0 0 var(--content-width); + background: var(--surface); + min-height: 100vh; + overflow-y: auto; +} + +.content-wrapper { + max-width: 720px; + margin: 0 auto; + padding: 24px 32px; + width: 100%; +} diff --git a/css/components/landing/responsive.landing.css b/css/components/landing/responsive.landing.css new file mode 100644 index 0000000..00277bd --- /dev/null +++ b/css/components/landing/responsive.landing.css @@ -0,0 +1,159 @@ +/* Responsive Layout */ +@media (max-width: 1200px) { + .content-wrapper { + padding: 32px 24px; + } +} + +@media (max-width: 1024px) { + .split-layout { + flex-direction: column; + } + + .branding-section { + position: relative; + flex: 0 0 auto; + width: 100%; + height: auto; + min-height: 50vh; + padding: 48px 24px; + } + + .content-section { + flex: 0 0 auto; + width: 100%; + } + + .content-wrapper { + padding: 32px 24px; + } + + .branding-content { + max-width: 100%; + } + + .tagline { + max-width: 480px; + } + + .feature-grid { + grid-template-columns: 1fr; + } + + .feature-blocks { + gap: 64px; + } + + .section-intro { + margin-bottom: 48px; + } + + .bsky-connect { + margin-bottom: 80px; + } + + .bsky-connect:after { + bottom: -40px; + width: 48px; + } + + .built-by-section { + margin-top: 48px; + } +} + +@media (max-width: 768px) { + .logo img { + width: 200px; + } + + .section-intro h2 { + font-size: 32px; + } + + .section-intro p { + font-size: 16px; + } + + .feature-description h3 { + font-size: 20px; + } + + .feature-description p, + .feature-description li { + font-size: 15px; + } + + .feature-blocks { + gap: 48px; + } + + .feature-block { + gap: 24px; + } + + .built-by-section { + margin-top: 40px; + padding-top: 24px; + } + + .built-by-content { + flex-direction: column; + gap: 12px; + } +} + +@media (max-width: 480px) { + .branding-section { + padding: 32px 20px; + min-height: auto; + } + + .content-wrapper { + padding: 24px 20px; + } + + .logo img { + width: 160px; + } + + .branding-content h1 { + font-size: 36px; + margin-bottom: 12px; + } + + .tagline { + font-size: 18px; + } + + .feature-blocks { + gap: 40px; + } + + .section-intro { + margin-bottom: 40px; + } + + .section-intro h2 { + font-size: 28px; + } + + .bsky-connect { + margin-bottom: 64px; + padding: 24px; + } + + .bsky-connect:after { + bottom: -32px; + width: 40px; + } + + .built-by-section { + margin-top: 32px; + padding-top: 20px; + } + + .built-by-content p { + font-size: 14px; + } +} diff --git a/css/components/landing/theme-toggle.landing.css b/css/components/landing/theme-toggle.landing.css new file mode 100644 index 0000000..f9bc376 --- /dev/null +++ b/css/components/landing/theme-toggle.landing.css @@ -0,0 +1,71 @@ +/* Theme Toggle Switch */ +.theme-toggle { + position: relative; + width: 64px; + height: 32px; + border-radius: 50px; + border: none; + background: none; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: space-between; + padding: 4px 8px; + outline: none; + background-color: var(--background); + border: 2px solid var(--border); + transition: all 0.3s ease; + margin-left: 8px; +} + +.theme-toggle:hover { + border-color: var(--primary); +} + +.theme-toggle::before { + content: ""; + position: absolute; + left: 4px; + width: 24px; + height: 24px; + border-radius: 50%; + background: var(--primary); + transition: transform 0.3s ease, background-color 0.3s ease; +} + +.theme-toggle.dark::before { + transform: translateX(28px); +} + +.theme-toggle .toggle-icon { + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + color: var(--text); + font-size: 14px; + line-height: 1; + width: 20px; + height: 20px; + position: relative; +} + +.theme-toggle .sun-icon { + margin-right: auto; + transform: translateX(-2px); +} + +.theme-toggle .moon-icon { + margin-left: auto; + transform: translateX(0px); +} + +/* Hide emoji in dark mode */ +.theme-toggle.dark .sun-icon { + opacity: 0.5; +} + +/* Hide emoji in light mode */ +.theme-toggle:not(.dark) .moon-icon { + opacity: 0.5; +} diff --git a/css/components/scrollbars.css b/css/components/scrollbars.css index be332e4..6676fc6 100644 --- a/css/components/scrollbars.css +++ b/css/components/scrollbars.css @@ -1,57 +1,38 @@ -/* Dark mode scrollbar styles */ -[data-theme="dark"] .categories-sidebar, -[data-theme="dark"] .categories-grid, -[data-theme="dark"] .keywords-section, -[data-theme="dark"] .context-builder, -[data-theme="dim"] .categories-sidebar, -[data-theme="dim"] .categories-grid, -[data-theme="dim"] .keywords-section, -[data-theme="dim"] .context-builder { - scrollbar-width: auto; - scrollbar-color: #3f3f4f #1e1e2e; +[data-theme="dark"].categories-sidebar, +[data-theme="dark"].categories-grid, +[data-theme="dark"].keywords-section, +[data-theme="dark"].context-builder { + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); + scrollbar-width: thin; } -[data-theme="dark"] .categories-sidebar::-webkit-scrollbar, -[data-theme="dark"] .categories-grid::-webkit-scrollbar, -[data-theme="dark"] .keywords-section::-webkit-scrollbar, -[data-theme="dark"] .context-builder::-webkit-scrollbar, -[data-theme="dim"] .categories-sidebar::-webkit-scrollbar, -[data-theme="dim"] .categories-grid::-webkit-scrollbar, -[data-theme="dim"] .keywords-section::-webkit-scrollbar, -[data-theme="dim"] .context-builder::-webkit-scrollbar { - width: 14px; +[data-theme="dark"].categories-sidebar::-webkit-scrollbar, +[data-theme="dark"].categories-grid::-webkit-scrollbar, +[data-theme="dark"].keywords-section::-webkit-scrollbar, +[data-theme="dark"].context-builder::-webkit-scrollbar { + width: 8px; + height: 8px; } -[data-theme="dark"] .categories-sidebar::-webkit-scrollbar-track, -[data-theme="dark"] .categories-grid::-webkit-scrollbar-track, -[data-theme="dark"] .keywords-section::-webkit-scrollbar-track, -[data-theme="dark"] .context-builder::-webkit-scrollbar-track, -[data-theme="dim"] .categories-sidebar::-webkit-scrollbar-track, -[data-theme="dim"] .categories-grid::-webkit-scrollbar-track, -[data-theme="dim"] .keywords-section::-webkit-scrollbar-track, -[data-theme="dim"] .context-builder::-webkit-scrollbar-track { - background: #1e1e2e; +[data-theme="dark"].categories-sidebar::-webkit-scrollbar-track, +[data-theme="dark"].categories-grid::-webkit-scrollbar-track, +[data-theme="dark"].keywords-section::-webkit-scrollbar-track, +[data-theme="dark"].context-builder::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: 4px; } -[data-theme="dark"] .categories-sidebar::-webkit-scrollbar-thumb, -[data-theme="dark"] .categories-grid::-webkit-scrollbar-thumb, -[data-theme="dark"] .keywords-section::-webkit-scrollbar-thumb, -[data-theme="dark"] .context-builder::-webkit-scrollbar-thumb, -[data-theme="dim"] .categories-sidebar::-webkit-scrollbar-thumb, -[data-theme="dim"] .categories-grid::-webkit-scrollbar-thumb, -[data-theme="dim"] .keywords-section::-webkit-scrollbar-thumb, -[data-theme="dim"] .context-builder::-webkit-scrollbar-thumb { - background: #3f3f4f; +[data-theme="dark"].categories-sidebar::-webkit-scrollbar-thumb, +[data-theme="dark"].categories-grid::-webkit-scrollbar-thumb, +[data-theme="dark"].keywords-section::-webkit-scrollbar-thumb, +[data-theme="dark"].context-builder::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); border-radius: 4px; } -[data-theme="dark"] .categories-sidebar::-webkit-scrollbar-thumb:hover, -[data-theme="dark"] .categories-grid::-webkit-scrollbar-thumb:hover, -[data-theme="dark"] .keywords-section::-webkit-scrollbar-thumb:hover, -[data-theme="dark"] .context-builder::-webkit-scrollbar-thumb:hover, -[data-theme="dim"] .categories-sidebar::-webkit-scrollbar-thumb:hover, -[data-theme="dim"] .categories-grid::-webkit-scrollbar-thumb:hover, -[data-theme="dim"] .keywords-section::-webkit-scrollbar-thumb:hover, -[data-theme="dim"] .context-builder::-webkit-scrollbar-thumb:hover { - background: #57576a; +[data-theme="dark"].categories-sidebar::-webkit-scrollbar-thumb:hover, +[data-theme="dark"].categories-grid::-webkit-scrollbar-thumb:hover, +[data-theme="dark"].keywords-section::-webkit-scrollbar-thumb:hover, +[data-theme="dark"].context-builder::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); } diff --git a/css/components/slider.css b/css/components/slider.css index 410a8ac..5526b30 100644 --- a/css/components/slider.css +++ b/css/components/slider.css @@ -81,11 +81,11 @@ } /* Dark Theme Adjustments */ -[data-theme="dim"] .filter-card:hover { +[data-theme="dark"] .filter-card:hover { background: var(--background); } -[data-theme="dim"] .filter-card.active { +[data-theme="dark"] .filter-card.active { background: var(--primary); border-color: var(--primary); } diff --git a/images/screenshots/dark-advanced-mode.png b/images/screenshots/dark-advanced-mode.png new file mode 100644 index 0000000..2e59244 Binary files /dev/null and b/images/screenshots/dark-advanced-mode.png differ diff --git a/images/screenshots/dark-search.png b/images/screenshots/dark-search.png new file mode 100644 index 0000000..67b8f97 Binary files /dev/null and b/images/screenshots/dark-search.png differ diff --git a/images/screenshots/dark-simple-mode.png b/images/screenshots/dark-simple-mode.png new file mode 100644 index 0000000..3954523 Binary files /dev/null and b/images/screenshots/dark-simple-mode.png differ diff --git a/images/screenshots/light-advanced-mode.png b/images/screenshots/light-advanced-mode.png new file mode 100644 index 0000000..8f8cb26 Binary files /dev/null and b/images/screenshots/light-advanced-mode.png differ diff --git a/images/screenshots/light-search.png b/images/screenshots/light-search.png new file mode 100644 index 0000000..e2c8e6a Binary files /dev/null and b/images/screenshots/light-search.png differ diff --git a/images/screenshots/light-simple-mode.png b/images/screenshots/light-simple-mode.png new file mode 100644 index 0000000..0545148 Binary files /dev/null and b/images/screenshots/light-simple-mode.png differ diff --git a/js/components/landing-page.js b/js/components/landing-page.js index 53d8c57..1054771 100644 --- a/js/components/landing-page.js +++ b/js/components/landing-page.js @@ -1,6 +1,10 @@ class LandingPage extends HTMLElement { constructor() { super(); + // Store preloaded images + this.imageCache = new Map(); + // Store theme observer + this.themeObserver = null; } connectedCallback() { @@ -72,15 +76,159 @@ class LandingPage extends HTMLElement { + + +
+
+

How It Works

+

Take control of your Bluesky experience with our intuitive filtering system

+
+ +
+
+
+
+

Start with Simple Mode

+

Quickly filter content across major topics like politics, healthcare, and global affairs. Choose what you want to see—and what you don't—with just a few clicks.

+
+
+ +
+
+
+

Extensive Categories

+

Select from over 20 content categories, from climate to international coverage. Each category comes pre-populated with carefully curated keywords, continuously updated to reflect current events.

+
+
+ +
+
+
+

Advanced Control

+

Need more control? Switch to Advanced Mode for direct access to over 1,400 keywords. Fine-tune your filters with individual toggles or bulk actions.

+
+
+ +
+
+

Perfect Balance

+

Choose your perfect balance with four filtering levels:

+
    +
  • Minimal for light touch filtering
  • +
  • Moderate for balanced content management
  • +
  • Extensive for comprehensive filtering
  • +
  • Complete for maximum control
  • +
+

Changes take effect instantly on your feed, and you can adjust your settings anytime.

+
+
+
+
+ + +
+
+

Made with 🤖 and Sponsor on GitHub by Chrissy LeMaire

+ +
+
`; + // Initialize theme-aware images after component is mounted + this.initThemeAwareImages(); + + // Listen for theme changes + this.themeObserver = (event) => this.updateThemeAwareImages(event?.detail?.theme); + document.addEventListener('themeChanged', this.themeObserver); + // Check for auth errors after component is mounted this.checkAuthErrors(); } + disconnectedCallback() { + // Clean up event listeners and cache + if (this.themeObserver) { + document.removeEventListener('themeChanged', this.themeObserver); + } + this.imageCache.clear(); + } + + async initThemeAwareImages() { + const images = this.querySelectorAll('.theme-aware-image'); + const preloadPromises = []; + + // Preload all images + images.forEach(img => { + const lightSrc = img.dataset.lightSrc; + const darkSrc = img.dataset.darkSrc; + + if (lightSrc && !this.imageCache.has(lightSrc)) { + preloadPromises.push(this.preloadImage(lightSrc)); + } + if (darkSrc && !this.imageCache.has(darkSrc)) { + preloadPromises.push(this.preloadImage(darkSrc)); + } + }); + + try { + await Promise.all(preloadPromises); + this.updateThemeAwareImages(); + } catch (error) { + console.error('Error preloading images:', error); + } + } + + async preloadImage(src) { + if (!src || this.imageCache.has(src)) return; + + try { + const img = new Image(); + const loadPromise = new Promise((resolve, reject) => { + img.onload = () => resolve(src); + img.onerror = () => reject(new Error(`Failed to load image: ${src}`)); + }); + + img.src = src; + await loadPromise; + this.imageCache.set(src, true); + } catch (error) { + console.error(`Error preloading image ${src}:`, error); + // Cache the failure to avoid repeated attempts + this.imageCache.set(src, false); + } + } + + updateThemeAwareImages(theme = null) { + if (!theme) { + theme = document.documentElement.getAttribute('data-theme'); + } + const isDarkMode = theme === 'dark'; + + requestAnimationFrame(() => { + this.querySelectorAll('.theme-aware-image').forEach(async (img) => { + const src = isDarkMode ? img.dataset.darkSrc : img.dataset.lightSrc; + + // Skip if image hasn't been preloaded or failed to preload + if (!this.imageCache.has(src)) { + await this.preloadImage(src); + } + + if (this.imageCache.get(src)) { + img.style.backgroundImage = `url('${src}')`; + } else { + // Use fallback image or add error class + img.classList.add('image-load-error'); + } + }); + }); + } + checkAuthErrors() { const error = sessionStorage.getItem('auth_error'); const errorDescription = sessionStorage.getItem('auth_error_description'); diff --git a/js/handlers/themeHandlers.js b/js/handlers/themeHandlers.js index 2d76dd3..f6522cc 100644 --- a/js/handlers/themeHandlers.js +++ b/js/handlers/themeHandlers.js @@ -2,21 +2,63 @@ import { loadAppearanceSettings, saveAppearanceSettings } from '../settings/appe export function handleFooterThemeToggle() { const settings = loadAppearanceSettings(); - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - const isDark = settings.colorMode === 'dark' || (settings.colorMode === 'system' && prefersDark); const html = document.documentElement; const currentTheme = html.getAttribute('data-theme'); - // Toggle between light and dark - const newColorMode = currentTheme === 'dim' ? 'light' : 'dark'; - settings.colorMode = newColorMode; + // Toggle between light and dark themes + const newTheme = currentTheme === 'light' ? 'dark' : 'light'; + settings.colorMode = newTheme; // Save and apply the new settings saveAppearanceSettings(settings); - // Update footer toggle state - const toggle = document.getElementById('footer-theme-toggle'); - if (toggle) { - toggle.classList.toggle('dark', newColorMode === 'dark'); - } + // Apply theme immediately + html.setAttribute('data-theme', newTheme); + + // Update all theme toggles + const toggles = document.querySelectorAll('.theme-toggle'); + toggles.forEach(toggle => { + toggle.classList.toggle('dark', newTheme === 'dark'); + }); + + // Dispatch theme change event + document.dispatchEvent(new CustomEvent('themeChanged', { + detail: { theme: newTheme } + })); } + +// Initialize theme on page load +document.addEventListener('DOMContentLoaded', () => { + const settings = loadAppearanceSettings(); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const theme = settings.colorMode === 'dark' || (settings.colorMode === 'system' && prefersDark) ? 'dark' : 'light'; + + document.documentElement.setAttribute('data-theme', theme); + + // Update toggle states + const toggles = document.querySelectorAll('.theme-toggle'); + toggles.forEach(toggle => { + toggle.classList.toggle('dark', theme === 'dark'); + }); +}); + +// Add system theme change listener +const systemThemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); +systemThemeQuery.addEventListener('change', (e) => { + const settings = loadAppearanceSettings(); + if (settings.colorMode === 'system') { + const theme = e.matches ? 'dark' : 'light'; + document.documentElement.setAttribute('data-theme', theme); + + // Update toggle states + const toggles = document.querySelectorAll('.theme-toggle'); + toggles.forEach(toggle => { + toggle.classList.toggle('dark', theme === 'dark'); + }); + + // Dispatch theme change event + document.dispatchEvent(new CustomEvent('themeChanged', { + detail: { theme } + })); + } +}); diff --git a/js/settings/appearanceSettings.js b/js/settings/appearanceSettings.js index d7bae35..c045e4e 100644 --- a/js/settings/appearanceSettings.js +++ b/js/settings/appearanceSettings.js @@ -1,6 +1,5 @@ const DEFAULT_APPEARANCE = { colorMode: 'system', - darkTheme: 'dim', font: 'system', fontSize: 'default' }; @@ -45,29 +44,22 @@ export function applyAppearanceSettings(settings = null) { const html = document.documentElement; const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - // Store current UI state - const advancedMode = document.getElementById('advanced-mode'); - const wasAdvancedHidden = advancedMode ? advancedMode.classList.contains('hidden') : true; - // Apply theme - let theme = 'light'; - if (settings.colorMode === 'dark' || (settings.colorMode === 'system' && prefersDark)) { - theme = 'dim'; - } + const theme = settings.colorMode === 'dark' || (settings.colorMode === 'system' && prefersDark) ? 'dark' : 'light'; // Apply theme immediately html.setAttribute('data-theme', theme); - // Update UI state - if (advancedMode) { - advancedMode.classList.toggle('hidden', wasAdvancedHidden); - } - // Update footer toggle state const footerToggle = document.getElementById('footer-theme-toggle'); if (footerToggle) { - const isDark = theme === 'dim'; - footerToggle.classList.toggle('dark', isDark); + footerToggle.classList.toggle('dark', theme === 'dark'); + } + + // Update landing page toggle state + const landingToggle = document.getElementById('landing-theme-toggle'); + if (landingToggle) { + landingToggle.classList.toggle('dark', theme === 'dark'); } // Apply font settings @@ -78,6 +70,11 @@ export function applyAppearanceSettings(settings = null) { // Apply font scale using CSS variable html.style.setProperty('--font-scale', FONT_SCALES[settings.fontSize]); + // Dispatch theme change event + document.dispatchEvent(new CustomEvent('themeChanged', { + detail: { theme } + })); + updateAppearanceUI(settings); } diff --git a/js/themeInit.js b/js/themeInit.js index aa23e67..4f19f90 100644 --- a/js/themeInit.js +++ b/js/themeInit.js @@ -11,12 +11,12 @@ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (settings.colorMode === 'system') { - theme = prefersDark ? 'dim' : 'light'; + theme = prefersDark ? 'dark' : 'light'; } else if (settings.colorMode === 'dark') { - theme = 'dim'; + theme = 'dark'; } } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - theme = 'dim'; + theme = 'dark'; } // Apply theme immediately @@ -29,7 +29,7 @@ // Set initial footer toggle state const footerToggle = document.getElementById('footer-theme-toggle'); if (footerToggle) { - footerToggle.classList.toggle('dark', theme === 'dim'); + footerToggle.classList.toggle('dark', theme === 'dark'); } }); @@ -39,7 +39,7 @@ if (currentSettings) { const settings = JSON.parse(currentSettings); if (settings.colorMode === 'system') { - const newTheme = e.matches ? 'dim' : 'light'; + const newTheme = e.matches ? 'dark' : 'light'; html.setAttribute('data-theme', newTheme); // Update footer toggle @@ -59,9 +59,9 @@ let newTheme = 'light'; if (settings.colorMode === 'system') { - newTheme = prefersDark ? 'dim' : 'light'; + newTheme = prefersDark ? 'dark' : 'light'; } else if (settings.colorMode === 'dark') { - newTheme = 'dim'; + newTheme = 'dark'; } html.setAttribute('data-theme', newTheme); @@ -69,7 +69,7 @@ // Update footer toggle const footerToggle = document.getElementById('footer-theme-toggle'); if (footerToggle) { - footerToggle.classList.toggle('dark', newTheme === 'dim'); + footerToggle.classList.toggle('dark', newTheme === 'dark'); } } });