diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..ee400852 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# GitHub API Configuration (Optional) +# To avoid rate limits, you can add a GitHub Personal Access Token +# Create one at: https://github.com/settings/tokens +# No special permissions needed for public repositories +GITHUB_TOKEN=your_github_token_here + +# Firebase Configuration (if needed) +# FIREBASE_API_KEY=your_firebase_api_key +# FIREBASE_AUTH_DOMAIN=your_firebase_auth_domain +# FIREBASE_PROJECT_ID=your_firebase_project_id + +# Analytics Configuration +# GOOGLE_ANALYTICS_ID=your_google_analytics_id +# VERCEL_ANALYTICS_ID=your_vercel_analytics_id diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 855ff39f..0df941d5 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -20,8 +20,12 @@ const config: Config = { onBrokenLinks: "throw", onBrokenMarkdownLinks: "warn", - // Google Analytics + // Google Analytics and Theme Scripts scripts: [ + { + src: '/theme-init.js', + async: false, // Load synchronously to prevent flash + }, { src: 'https://www.googletagmanager.com/gtag/js?id=G-W02Z2VJYCR', async: true, @@ -69,6 +73,11 @@ const config: Config = { themeConfig: { image: "img/docusaurus-social-card.jpg", + colorMode: { + defaultMode: 'light', + disableSwitch: false, + respectPrefersColorScheme: false, + }, navbar: { title: "Recode Hive", logo: { diff --git a/src/components/FloatingContributors/index.tsx b/src/components/FloatingContributors/index.tsx index b25d5a41..cbfad002 100644 --- a/src/components/FloatingContributors/index.tsx +++ b/src/components/FloatingContributors/index.tsx @@ -29,11 +29,18 @@ const FloatingContributors: React.FC = () => { const fetchContributors = async () => { try { setLoading(true); - + // Fetch repositories from RecodeHive organization const reposResponse = await fetch('https://api.github.com/orgs/recodehive/repos?type=public&per_page=10&sort=updated'); + + // Check if the response is ok + if (!reposResponse.ok) { + console.warn(`GitHub API rate limit or error: ${reposResponse.status}`); + throw new Error(`GitHub API error: ${reposResponse.status}`); + } + const repos = await reposResponse.json(); - + if (!Array.isArray(repos)) { throw new Error('Invalid repos response'); } @@ -90,8 +97,9 @@ const FloatingContributors: React.FC = () => { setLoading(false); } catch (error) { - console.error('Error fetching contributors:', error); - + // Silently handle GitHub API errors (rate limits, etc.) + console.warn('Using fallback contributor data due to GitHub API limitations'); + // Fallback demo data const demoContributors: Contributor[] = [ { diff --git a/src/css/custom.css b/src/css/custom.css index 54bd2e0d..d3aea09f 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -6,6 +6,68 @@ /* You can override the default Infima variables here. */ @import "tailwindcss"; + +/* ===== INSTANT THEME TRANSITIONS ===== */ +html { + transition: background-color 0.15s ease, color 0.15s ease !important; +} + +body { + transition: background-color 0.15s ease, color 0.15s ease !important; +} + +/* Instant transitions for theme-sensitive elements */ +*, +*::before, +*::after { + transition: + background-color 0.15s ease, + color 0.15s ease, + border-color 0.15s ease, + box-shadow 0.15s ease !important; +} + +/* Very fast transitions for interactive elements */ +button, +.button, +a, +input, +select, +textarea { + transition: + all 0.1s ease !important; +} + +/* Instant navbar theme change */ +.navbar, +.navbar__inner, +.navbar__brand, +.navbar__item, +.navbar__link { + transition: + background-color 0.1s ease, + color 0.1s ease !important; +} + +/* Force instant theme updates for key elements */ +[data-theme='light'] .navbar { + background-color: #ffffff !important; + color: #1a202c !important; +} + +[data-theme='dark'] .navbar { + background-color: #121212 !important; + color: #ffffff !important; +} + +[data-theme='light'] .navbar__link { + color: #1a202c !important; +} + +[data-theme='dark'] .navbar__link { + color: #ffffff !important; +} + :root { --ifm-color-primary: #2e8555; --ifm-color-primary-dark: #29784c; @@ -18,6 +80,10 @@ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); --ifm-color-primary-text: white; --ifm-color-secondary-text: #edf2f7; + + /* Light theme defaults */ + --ifm-background-color: #ffffff; + --ifm-font-color-base: #000000; } /* For readability concerns, you should choose a lighter palette in dark mode. */ @@ -33,6 +99,10 @@ --ifm-color-primary-text: #ffffff; --ifm-color-secondary-text: #edf2f7; + /* Dark theme background and text */ + --ifm-background-color: #121212; + --ifm-font-color-base: #ffffff; + /* Standardized Dark Theme Colors */ --dark-bg-primary: #121212; --dark-bg-secondary: #1a1a1a; @@ -181,18 +251,30 @@ color: transparent; } -/* Light mode background and text fix - EXCLUDE community page */ -[data-theme='light'] body:not(:has(.community-page)) { - --ifm-background-color: #ffffff; /* white background */ - --ifm-font-color-base: #000000; /* black text */ - background-color: var(--ifm-background-color); - color: var(--ifm-font-color-base); +/* ===== THEME OVERRIDES - CLEAN APPROACH ===== */ + +/* Light theme - Clean background */ +[data-theme='light'] { + --ifm-background-color: #ffffff; + --ifm-font-color-base: #1a202c; + --ifm-card-background-color: #ffffff; +} + +[data-theme='light'] body { + background-color: var(--ifm-background-color) !important; + color: var(--ifm-font-color-base) !important; } -/* Dark mode overrides - EXCLUDE community page */ -[data-theme='dark'] body:not(:has(.community-page)) { - background-color: var(--dark-bg-primary); - color: var(--dark-text-primary); +/* Dark theme - Clean background */ +[data-theme='dark'] { + --ifm-background-color: #121212; + --ifm-font-color-base: #ffffff; + --ifm-card-background-color: #1a1a1a; +} + +[data-theme='dark'] body { + background-color: var(--ifm-background-color) !important; + color: var(--ifm-font-color-base) !important; } /* Global dark theme utilities */ @@ -253,17 +335,34 @@ color: var(--dark-text-muted) !important; } -/* Button dark theme */ -[data-theme='dark'] .button, -[data-theme='dark'] button { - background-color: var(--dark-bg-tertiary); - color: var(--dark-text-primary); - border-color: var(--dark-border); +/* ===== CLEAN BUTTON FIXES ===== */ + +/* Remove conflicting button styles - let Docusaurus handle it naturally */ +[data-theme='light'] .button--outline, +[data-theme='light'] .button--secondary { + background-color: transparent !important; + color: var(--ifm-font-color-base) !important; + border: 1px solid var(--ifm-color-emphasis-300) !important; +} + +[data-theme='dark'] .button--outline, +[data-theme='dark'] .button--secondary { + background-color: transparent !important; + color: var(--ifm-font-color-base) !important; + border: 1px solid var(--ifm-color-emphasis-300) !important; } -[data-theme='dark'] .button:hover, -[data-theme='dark'] button:hover { - background-color: var(--dark-card-hover-bg); +/* ===== MINIMAL THEME FIXES ===== */ + +/* Only fix specific problematic elements */ +[data-theme='light'] .card { + background-color: var(--ifm-card-background-color) !important; + color: var(--ifm-font-color-base) !important; +} + +[data-theme='dark'] .card { + background-color: var(--ifm-card-background-color) !important; + color: var(--ifm-font-color-base) !important; } /* Card components dark theme */ @@ -812,3 +911,53 @@ html { color: inherit; } +/* ===== STEP 3: BASIC FOOTER PROTECTION ===== */ +/* Prevent global dark theme container rule from affecting footer */ +[data-theme='dark'] .enhanced-footer .container { + background-color: transparent !important; +} + +/* ===== STEP 4: FOOTER BACKGROUND PROTECTION ===== */ +/* Ensure footer maintains its gradient background in dark mode */ +[data-theme='dark'] .enhanced-footer { + background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%) !important; + color: #e2e8f0 !important; +} + +/* ===== STEP 5: FOOTER SECTION PROTECTION ===== */ +/* Protect specific footer sections from global overrides */ +[data-theme='dark'] .enhanced-footer .footer-links-section { + background: rgba(0, 0, 0, 0.15) !important; +} + +[data-theme='dark'] .enhanced-footer .footer-bottom { + background: rgba(0, 0, 0, 0.2) !important; +} + +/* ===== STEP 6: MAXIMUM SPECIFICITY PROTECTION ===== */ +/* Override the exact problematic global rule with same specificity */ +[data-theme='dark'] body:not(:has(.community-page)) .enhanced-footer .container { + background-color: transparent !important; +} + +[data-theme='dark'] body:not(:has(.community-page)) .enhanced-footer { + background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%) !important; + color: #e2e8f0 !important; +} + +/* ===== STEP 7: COMPREHENSIVE ELEMENT PROTECTION ===== */ +/* Protect all footer elements from any global overrides */ +[data-theme='dark'] .enhanced-footer *, +[data-theme='dark'] .enhanced-footer .row, +[data-theme='dark'] .enhanced-footer .col, +[data-theme='dark'] .enhanced-footer div, +[data-theme='dark'] .enhanced-footer section { + background-color: transparent !important; +} + +/* Ensure text colors inherit properly */ +[data-theme='dark'] .enhanced-footer, +[data-theme='dark'] .enhanced-footer * { + color: inherit !important; +} + diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 360908ce..cd5e80ec 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -99,11 +99,15 @@ const DashboardContent: React.FC = () => { try { console.log('🔄 Fetching leaderboard data from RecodeHive GitHub API...'); - + // Fetch all repositories from RecodeHive organization const reposResponse = await fetch('https://api.github.com/orgs/recodehive/repos?type=public&per_page=100'); - + if (!reposResponse.ok) { + if (reposResponse.status === 403) { + console.warn('GitHub API rate limit exceeded. Using fallback data.'); + throw new Error('GitHub API rate limit exceeded'); + } throw new Error(`GitHub API request failed: ${reposResponse.status}`); } @@ -185,8 +189,8 @@ const DashboardContent: React.FC = () => { setLeaderboardData(transformedData); } catch (error) { - console.error('❌ Error fetching RecodeHive contributors data:', error); - setLeaderboardError(error.message); + console.warn('Using fallback leaderboard data due to GitHub API limitations'); + setLeaderboardError('GitHub API rate limit reached. Showing demo data.'); // Fallback demo data with similar structure console.log('📝 Loading demo data as fallback...'); diff --git a/src/services/githubService.ts b/src/services/githubService.ts index 666196ec..a7d50d10 100644 --- a/src/services/githubService.ts +++ b/src/services/githubService.ts @@ -61,15 +61,9 @@ class GitHubService { }); if (response.status === 403) { - // Rate limited, wait a bit - const resetTime = response.headers.get('X-RateLimit-Reset'); - if (resetTime) { - const waitTime = Math.max(0, parseInt(resetTime) * 1000 - Date.now()); - if (waitTime < 60000) { // Only wait if less than 1 minute - await new Promise(resolve => setTimeout(resolve, Math.min(waitTime, 5000))); - continue; - } - } + // Rate limited - don't retry, just throw error + console.warn('GitHub API rate limit exceeded'); + throw new Error('GitHub API rate limit exceeded'); } if (!response.ok) { @@ -152,6 +146,10 @@ class GitHubService { ); if (!response.ok) { + if (response.status === 403) { + console.warn('GitHub API rate limit exceeded while fetching repositories'); + throw new Error('GitHub API rate limit exceeded'); + } throw new Error(`Failed to fetch repositories: ${response.status}`); } diff --git a/src/theme/ColorModeToggle/index.tsx b/src/theme/ColorModeToggle/index.tsx new file mode 100644 index 00000000..65b0c9d8 --- /dev/null +++ b/src/theme/ColorModeToggle/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import {useColorMode} from '@docusaurus/theme-common'; + +export default function ColorModeToggle(): JSX.Element { + const {colorMode, setColorMode} = useColorMode(); + + const toggleColorMode = () => { + const newMode = colorMode === 'dark' ? 'light' : 'dark'; + + // INSTANT theme change - update all elements immediately + document.documentElement.setAttribute('data-theme', newMode); + document.body.setAttribute('data-theme', newMode); + document.body.className = document.body.className.replace(/theme-\w+/g, '') + ` theme-${newMode}`; + + // Force immediate CSS variable updates + if (newMode === 'light') { + document.documentElement.style.setProperty('--ifm-background-color', '#ffffff'); + document.documentElement.style.setProperty('--ifm-font-color-base', '#1a202c'); + document.body.style.backgroundColor = '#ffffff'; + document.body.style.color = '#1a202c'; + } else { + document.documentElement.style.setProperty('--ifm-background-color', '#121212'); + document.documentElement.style.setProperty('--ifm-font-color-base', '#ffffff'); + document.body.style.backgroundColor = '#121212'; + document.body.style.color = '#ffffff'; + } + + // Update Docusaurus state (this can be slower) + setColorMode(newMode); + localStorage.setItem('theme', newMode); + }; + + return ( + { + e.currentTarget.style.transform = 'scale(1.1)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)'; + }} + > + + {colorMode === 'dark' ? ( + + + + ) : ( + + + + )} + + + ); +} diff --git a/src/theme/Navbar/index.tsx b/src/theme/Navbar/index.tsx index 4b6953dc..fda3a757 100644 --- a/src/theme/Navbar/index.tsx +++ b/src/theme/Navbar/index.tsx @@ -2,7 +2,6 @@ import React, {type ReactNode} from 'react'; import Navbar from '@theme-original/Navbar'; import type NavbarType from '@theme/Navbar'; import type {WrapperProps} from '@docusaurus/types'; -import NavbarFirebaseAuthGithub from '@site/src/components/ui/NavbarFirebaseAuthGithub'; type Props = WrapperProps; @@ -10,7 +9,6 @@ export default function NavbarWrapper(props: Props): ReactNode { return ( <> - > ); } diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index 9ab5a6fd..dc237b3d 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -1,12 +1,22 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Analytics } from '@vercel/analytics/react'; // Default implementation, that you can customize export default function Root({children}) { + useEffect(() => { + // Ensure theme is properly set on client-side hydration + const savedTheme = localStorage.getItem('theme'); + // Force light as default, ignore system preference unless user has manually set a preference + const theme = savedTheme || 'light'; + + document.documentElement.setAttribute('data-theme', theme); + }, []); + return ( <> {children} - + {/* Only load analytics in production */} + {process.env.NODE_ENV === 'production' && } > ); } diff --git a/static/theme-init.js b/static/theme-init.js new file mode 100644 index 00000000..7c3316f9 --- /dev/null +++ b/static/theme-init.js @@ -0,0 +1,83 @@ +// Theme initialization script +(function() { + 'use strict'; + + // Function to set theme INSTANTLY + function setTheme(theme) { + // Immediate DOM updates + document.documentElement.setAttribute('data-theme', theme); + document.body.setAttribute('data-theme', theme); + document.body.className = document.body.className.replace(/theme-\w+/g, '') + ` theme-${theme}`; + + // Force immediate CSS variable updates + if (theme === 'light') { + document.documentElement.style.setProperty('--ifm-background-color', '#ffffff'); + document.documentElement.style.setProperty('--ifm-font-color-base', '#1a202c'); + document.body.style.backgroundColor = '#ffffff'; + document.body.style.color = '#1a202c'; + } else { + document.documentElement.style.setProperty('--ifm-background-color', '#121212'); + document.documentElement.style.setProperty('--ifm-font-color-base', '#ffffff'); + document.body.style.backgroundColor = '#121212'; + document.body.style.color = '#ffffff'; + } + + localStorage.setItem('theme', theme); + } + + // Function to get system preference + function getSystemTheme() { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + // Initialize theme - FORCE LIGHT FIRST TIME + function initializeTheme() { + try { + // Clear any existing theme to force light + if (!localStorage.getItem('theme')) { + localStorage.removeItem('theme'); + } + + const savedTheme = localStorage.getItem('theme'); + const theme = savedTheme || 'light'; + + // Force set the theme + document.documentElement.setAttribute('data-theme', theme); + document.body.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + + console.log('🌞 Theme initialized:', theme); + } catch (error) { + document.documentElement.setAttribute('data-theme', 'light'); + document.body.setAttribute('data-theme', 'light'); + } + } + + // Listen for system theme changes + function setupSystemThemeListener() { + try { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + mediaQuery.addEventListener('change', function(e) { + // Only update if no manual theme preference is saved + if (!localStorage.getItem('theme')) { + const newTheme = e.matches ? 'dark' : 'light'; + setTheme(newTheme); + console.log('🌙 System theme changed to:', newTheme); + } + }); + } catch (error) { + console.warn('Failed to setup system theme listener:', error); + } + } + + // Run immediately if DOM is ready, otherwise wait + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + initializeTheme(); + setupSystemThemeListener(); + }); + } else { + initializeTheme(); + setupSystemThemeListener(); + } +})();