Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>π Leaderboard of Leaderboards</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| /* βββ RESET & VARIABLES βββ */ | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| /* ββ LIGHT (default) ββ */ | |
| :root { | |
| --bg: #F7F6F2; | |
| --surface: #FFFFFF; | |
| --card: #FFFFFF; | |
| --border: #E4E2DA; | |
| --accent: #D4A017; | |
| --accent2: #E8541A; | |
| --text: #1A1A1A; | |
| --muted: #888880; | |
| --green: #16A34A; | |
| --blue: #2563EB; | |
| --pink: #DB2777; | |
| --radius: 10px; | |
| --shadow: 0 1px 4px rgba(0,0,0,0.08); | |
| --grid-color: rgba(180,160,0,0.06); | |
| } | |
| /* ββ NIGHT (dark) ββ */ | |
| html.night { | |
| --bg: #080808; | |
| --surface: #111111; | |
| --card: #161616; | |
| --border: #222222; | |
| --accent: #F5C518; | |
| --accent2: #FF6B35; | |
| --text: #EEEEEE; | |
| --muted: #666666; | |
| --green: #4ADE80; | |
| --blue: #60A5FA; | |
| --pink: #F472B6; | |
| --shadow: 0 1px 6px rgba(0,0,0,0.4); | |
| --grid-color: rgba(245,197,24,0.03); | |
| } | |
| /* ββ Smooth theme transition ββ */ | |
| body, .hero, .controls, .space-card, .podium-card, .stat-item, | |
| .api-sort-toggle, .sort-select, #searchInput { | |
| transition: background 0.3s ease, border-color 0.3s ease, color 0.2s ease, box-shadow 0.3s ease; | |
| } | |
| html { scroll-behavior: smooth; } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: 'DM Sans', sans-serif; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* βββ BACKGROUND GRID βββ */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background-image: | |
| linear-gradient(var(--grid-color) 1px, transparent 1px), | |
| linear-gradient(90deg, var(--grid-color) 1px, transparent 1px); | |
| background-size: 40px 40px; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| /* βββ HERO HEADER βββ */ | |
| .hero { | |
| position: relative; | |
| z-index: 1; | |
| padding: 3rem 2rem 2rem; | |
| text-align: center; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .hero-badge { | |
| display: inline-block; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.65rem; | |
| letter-spacing: 3px; | |
| text-transform: uppercase; | |
| color: var(--accent); | |
| border: 1px solid rgba(245,197,24,0.3); | |
| padding: 4px 14px; | |
| border-radius: 99px; | |
| margin-bottom: 1rem; | |
| animation: fadeDown 0.6s ease both; | |
| } | |
| .hero h1 { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: clamp(3rem, 8vw, 6.5rem); | |
| letter-spacing: 2px; | |
| line-height: 0.95; | |
| color: var(--text); | |
| animation: fadeDown 0.7s 0.1s ease both; | |
| } | |
| .hero h1 span { | |
| color: var(--accent); | |
| display: block; | |
| } | |
| .hero-sub { | |
| margin-top: 1rem; | |
| font-size: 0.9rem; | |
| color: var(--muted); | |
| font-family: 'DM Mono', monospace; | |
| animation: fadeDown 0.7s 0.2s ease both; | |
| } | |
| /* βββ STATS BAR βββ */ | |
| .stats-bar { | |
| position: relative; | |
| z-index: 1; | |
| display: flex; | |
| justify-content: center; | |
| gap: 1.5rem; | |
| padding: 1.5rem 2rem; | |
| flex-wrap: wrap; | |
| border-bottom: 1px solid var(--border); | |
| animation: fadeDown 0.7s 0.3s ease both; | |
| } | |
| .stat-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 2px; | |
| } | |
| .stat-num { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 2rem; | |
| color: var(--accent); | |
| line-height: 1; | |
| } | |
| .stat-label { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.6rem; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| } | |
| .stat-divider { | |
| width: 1px; | |
| height: 40px; | |
| background: var(--border); | |
| align-self: center; | |
| } | |
| /* βββ CONTROLS βββ */ | |
| .controls { | |
| position: sticky; | |
| top: 0; | |
| z-index: 10; | |
| display: flex; | |
| gap: 1rem; | |
| padding: 1.2rem 2rem; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| border-bottom: 1px solid var(--border); | |
| background: rgba(247,246,242,0.92); | |
| backdrop-filter: blur(12px); | |
| } | |
| html.night .controls { | |
| background: rgba(17,17,17,0.92); | |
| } | |
| .search-wrap { | |
| flex: 1; | |
| min-width: 200px; | |
| position: relative; | |
| } | |
| .search-wrap svg { | |
| position: absolute; | |
| left: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: var(--muted); | |
| width: 16px; | |
| height: 16px; | |
| } | |
| #searchInput { | |
| width: 100%; | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| color: var(--text); | |
| padding: 0.6rem 1rem 0.6rem 2.4rem; | |
| border-radius: var(--radius); | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.8rem; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| #searchInput:focus { border-color: var(--accent); } | |
| #searchInput::placeholder { color: var(--muted); } | |
| .filter-pills { | |
| display: flex; | |
| gap: 6px; | |
| flex-wrap: wrap; | |
| } | |
| .pill { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.65rem; | |
| padding: 5px 12px; | |
| border-radius: 99px; | |
| border: 1px solid var(--border); | |
| background: transparent; | |
| color: var(--muted); | |
| cursor: pointer; | |
| transition: all 0.15s; | |
| white-space: nowrap; | |
| } | |
| .pill:hover { border-color: var(--accent); color: var(--accent); } | |
| .pill.active { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 500; } | |
| .sort-select { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| color: var(--text); | |
| padding: 0.55rem 0.8rem; | |
| border-radius: var(--radius); | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.72rem; | |
| outline: none; | |
| cursor: pointer; | |
| } | |
| .refresh-btn { | |
| background: var(--accent); | |
| color: var(--bg); | |
| border: none; | |
| padding: 0.55rem 1rem; | |
| border-radius: var(--radius); | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.72rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: opacity 0.2s; | |
| white-space: nowrap; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .refresh-btn:hover { opacity: 0.85; } | |
| .refresh-btn.loading .spin { animation: spin 1s linear infinite; } | |
| /* βββ API SORT TOGGLE βββ */ | |
| .api-sort-toggle { | |
| display: flex; | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| flex-shrink: 0; | |
| } | |
| .api-sort-btn { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.68rem; | |
| font-weight: 500; | |
| padding: 0.55rem 1rem; | |
| border: none; | |
| background: transparent; | |
| color: var(--muted); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| transition: all 0.15s; | |
| white-space: nowrap; | |
| } | |
| .api-sort-btn:first-child { border-right: 1px solid var(--border); } | |
| .api-sort-btn.active { background: var(--accent); color: var(--bg); } | |
| .api-sort-btn:not(.active):hover { color: var(--text); } | |
| .trend-dot { | |
| width: 6px; height: 6px; | |
| border-radius: 50%; | |
| background: currentColor; | |
| flex-shrink: 0; | |
| } | |
| .api-sort-btn.active .trend-dot { animation: pulse 1.4s ease infinite; } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.5; transform: scale(1.5); } | |
| } | |
| /* βββ THEME TOGGLE βββ */ | |
| .theme-btn { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.72rem; | |
| font-weight: 500; | |
| padding: 0.55rem 0.9rem; | |
| border-radius: var(--radius); | |
| border: 1px solid var(--border); | |
| background: var(--card); | |
| color: var(--muted); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| flex-shrink: 0; | |
| transition: all 0.2s; | |
| } | |
| .theme-btn:hover { border-color: var(--accent); color: var(--text); } | |
| .theme-btn .theme-icon { font-size: 1rem; line-height: 1; } | |
| /* βββ MAIN GRID βββ */ | |
| .main { | |
| position: relative; | |
| z-index: 1; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 1.5rem 2rem 4rem; | |
| } | |
| .results-info { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.7rem; | |
| color: var(--muted); | |
| margin-bottom: 1.2rem; | |
| } | |
| .results-info strong { color: var(--accent); } | |
| /* βββ TABLE HEADER βββ */ | |
| .table-header { | |
| display: grid; | |
| grid-template-columns: 52px 1fr 90px 130px 70px 90px; | |
| gap: 0.5rem; | |
| padding: 0.6rem 1rem; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.6rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1.5px; | |
| color: var(--muted); | |
| border-bottom: 1px solid var(--border); | |
| margin-bottom: 0.4rem; | |
| } | |
| /* βββ SPACE CARD βββ */ | |
| .space-list { display: flex; flex-direction: column; gap: 4px; } | |
| .space-card { | |
| display: grid; | |
| grid-template-columns: 52px 1fr 90px 130px 70px 90px; | |
| gap: 0.5rem; | |
| align-items: center; | |
| padding: 0.9rem 1rem; | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| transition: all 0.18s ease; | |
| cursor: pointer; | |
| text-decoration: none; | |
| color: inherit; | |
| opacity: 0; | |
| transform: translateY(6px); | |
| animation: cardIn 0.35s ease forwards; | |
| } | |
| .space-card:hover { | |
| border-color: rgba(212,160,23,0.5); | |
| background: var(--surface); | |
| transform: translateX(3px); | |
| box-shadow: -3px 0 0 var(--accent), var(--shadow); | |
| } | |
| /* Rank */ | |
| .rank-cell { | |
| text-align: center; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| min-width: 52px; | |
| gap: 2px; | |
| } | |
| .rank-num { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 1.3rem; | |
| line-height: 1; | |
| color: var(--muted); | |
| } | |
| .rank-num.top1 { color: var(--accent); font-size: 1.6rem; } | |
| .global-rank { | |
| font-size: 0.6rem; | |
| font-family: 'DM Mono', monospace; | |
| color: var(--muted); | |
| margin-top: 3px; | |
| letter-spacing: -0.01em; | |
| white-space: nowrap; | |
| text-align: center; | |
| background: rgba(0,0,0,0.06); | |
| border-radius: 4px; | |
| padding: 1px 5px; | |
| font-weight: 600; | |
| } | |
| html.night .global-rank { background: rgba(255,255,255,0.08); } | |
| .rank-num.top2 { color: #C0C0C0; font-size: 1.4rem; } | |
| .rank-num.top3 { color: #CD7F32; font-size: 1.4rem; } | |
| /* Space info */ | |
| .space-info { min-width: 0; } | |
| .space-name { | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| color: var(--text); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| margin-bottom: 3px; | |
| } | |
| .space-name .org { | |
| color: var(--muted); | |
| font-weight: 300; | |
| } | |
| .space-desc { | |
| font-size: 0.72rem; | |
| color: var(--muted); | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| font-family: 'DM Mono', monospace; | |
| line-height: 1.5; | |
| } | |
| /* Likes */ | |
| .likes-cell { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.8rem; | |
| color: var(--accent); | |
| font-weight: 500; | |
| } | |
| .likes-cell svg { width: 13px; height: 13px; flex-shrink: 0; } | |
| /* Likes bar */ | |
| .likes-bar-wrap { | |
| width: 100%; | |
| height: 3px; | |
| background: var(--border); | |
| border-radius: 99px; | |
| margin-top: 3px; | |
| overflow: hidden; | |
| } | |
| .likes-bar { | |
| height: 100%; | |
| background: var(--accent); | |
| border-radius: 99px; | |
| transition: width 0.6s ease; | |
| } | |
| /* Category */ | |
| .cat-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.62rem; | |
| padding: 3px 8px; | |
| border-radius: 4px; | |
| white-space: nowrap; | |
| border: 1px solid; | |
| } | |
| /* SDK */ | |
| .sdk-badge { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.62rem; | |
| color: var(--muted); | |
| text-align: center; | |
| } | |
| /* Visit btn */ | |
| .visit-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.65rem; | |
| padding: 5px 10px; | |
| border-radius: 6px; | |
| border: 1px solid var(--border); | |
| color: var(--muted); | |
| background: transparent; | |
| text-decoration: none; | |
| transition: all 0.15s; | |
| white-space: nowrap; | |
| } | |
| .visit-btn:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| background: rgba(245,197,24,0.05); | |
| } | |
| /* βββ TOP 3 PODIUM βββ */ | |
| .podium { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .podium-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 1.2rem; | |
| text-align: center; | |
| position: relative; | |
| overflow: hidden; | |
| text-decoration: none; | |
| color: inherit; | |
| display: block; | |
| transition: transform 0.2s; | |
| } | |
| .podium-card:hover { transform: translateY(-3px); } | |
| .podium-card.gold { border-color: rgba(245,197,24,0.5); } | |
| .podium-card.silver { border-color: rgba(192,192,192,0.4); } | |
| .podium-card.bronze { border-color: rgba(205,127,50,0.4); } | |
| .podium-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; right: 0; | |
| height: 3px; | |
| } | |
| .podium-card.gold::before { background: var(--accent); } | |
| .podium-card.silver::before { background: #C0C0C0; } | |
| .podium-card.bronze::before { background: #CD7F32; } | |
| .podium-medal { | |
| font-size: 2rem; | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| } | |
| .podium-name { | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| color: var(--text); | |
| word-break: break-all; | |
| margin-bottom: 0.4rem; | |
| } | |
| .podium-likes { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 1.5rem; | |
| color: var(--accent); | |
| } | |
| .podium-likes-label { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.6rem; | |
| color: var(--muted); | |
| display: block; | |
| } | |
| /* βββ CATEGORY COLORS βββ */ | |
| .cat--llm { color: #60A5FA; border-color: rgba(96,165,250,0.25); background: rgba(96,165,250,0.07); } | |
| .cat--code { color: #4ADE80; border-color: rgba(74,222,128,0.25); background: rgba(74,222,128,0.07); } | |
| .cat--multi { color: #F472B6; border-color: rgba(244,114,182,0.25); background: rgba(244,114,182,0.07); } | |
| .cat--agent { color: #FB923C; border-color: rgba(251,146,60,0.25); background: rgba(251,146,60,0.07); } | |
| .cat--agi { color: #F5C518; border-color: rgba(245,197,24,0.35); background: rgba(245,197,24,0.08); } | |
| .cat--chat { color: #A78BFA; border-color: rgba(167,139,250,0.25); background: rgba(167,139,250,0.07); } | |
| .cat--science { color: #34D399; border-color: rgba(52,211,153,0.25); background: rgba(52,211,153,0.07); } | |
| .cat--gen { color: #F87171; border-color: rgba(248,113,113,0.25); background: rgba(248,113,113,0.07); } | |
| .cat--safety { color: #FCD34D; border-color: rgba(252,211,77,0.25); background: rgba(252,211,77,0.07); } | |
| .cat--etc { color: var(--muted); border-color: var(--border); background: transparent; } | |
| /* βββ LOADING βββ */ | |
| .loading-screen { | |
| position: fixed; | |
| inset: 0; | |
| background: var(--bg); | |
| z-index: 100; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 1rem; | |
| transition: opacity 0.5s, visibility 0.5s; | |
| } | |
| .loading-screen.hidden { opacity: 0; visibility: hidden; } | |
| .loading-logo { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 4rem; | |
| color: var(--accent); | |
| letter-spacing: 3px; | |
| } | |
| .loading-bar { | |
| width: 200px; | |
| height: 2px; | |
| background: var(--border); | |
| border-radius: 99px; | |
| overflow: hidden; | |
| } | |
| .loading-bar-inner { | |
| height: 100%; | |
| background: var(--accent); | |
| border-radius: 99px; | |
| animation: loadBar 1.8s ease-in-out infinite; | |
| } | |
| .loading-text { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.7rem; | |
| color: var(--muted); | |
| } | |
| /* βββ EMPTY / ERROR βββ */ | |
| .empty-state { | |
| text-align: center; | |
| padding: 4rem 2rem; | |
| color: var(--muted); | |
| } | |
| .empty-state .icon { font-size: 3rem; margin-bottom: 1rem; } | |
| .empty-state p { font-family: 'DM Mono', monospace; font-size: 0.8rem; } | |
| /* βββ FOOTER βββ */ | |
| .footer { | |
| position: relative; | |
| z-index: 1; | |
| text-align: center; | |
| padding: 2rem; | |
| border-top: 1px solid var(--border); | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.65rem; | |
| color: var(--muted); | |
| } | |
| .footer a { color: var(--accent); text-decoration: none; } | |
| /* βββ ANIMATIONS βββ */ | |
| @keyframes fadeDown { | |
| from { opacity: 0; transform: translateY(-12px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes cardIn { | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| @keyframes loadBar { | |
| 0% { width: 0%; margin-left: 0; } | |
| 50% { width: 70%; margin-left: 0; } | |
| 100% { width: 0%; margin-left: 100%; } | |
| } | |
| /* βββ RESPONSIVE βββ */ | |
| @media (max-width: 900px) { | |
| .table-header { display: none; } | |
| .space-card { | |
| grid-template-columns: 40px 1fr 70px; | |
| grid-template-rows: auto auto; | |
| } | |
| .space-card .likes-cell { order: 3; } | |
| .space-card .cat-badge, | |
| .space-card .sdk-badge, | |
| .space-card .visit-btn { display: none; } | |
| .space-desc { display: none; } | |
| .podium { grid-template-columns: 1fr; } | |
| } | |
| @media (max-width: 600px) { | |
| .hero h1 { font-size: 3rem; } | |
| .controls { flex-direction: column; } | |
| .main { padding: 1rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Loading screen --> | |
| <div class="loading-screen" id="loadingScreen"> | |
| <div class="loading-logo">LoL</div> | |
| <div class="loading-bar"><div class="loading-bar-inner"></div></div> | |
| <div class="loading-text">Connecting to HuggingFace API...</div> | |
| </div> | |
| <!-- Hero --> | |
| <header class="hero"> | |
| <div class="hero-badge">π€ HuggingFace Spaces Β· Live Data</div> | |
| <h1>Leaderboard<span>of Leaderboards</span></h1> | |
| <p class="hero-sub">Ranking the most popular AI benchmark leaderboards on HuggingFace Β· Sorted by Likes</p> | |
| </header> | |
| <!-- Stats --> | |
| <div class="stats-bar" id="statsBar"> | |
| <div class="stat-item"> | |
| <span class="stat-num" id="statTotal">β</span> | |
| <span class="stat-label">Total Leaderboards</span> | |
| </div> | |
| <div class="stat-divider"></div> | |
| <div class="stat-item"> | |
| <span class="stat-num" id="statLikes">β</span> | |
| <span class="stat-label">Total Likes</span> | |
| </div> | |
| <div class="stat-divider"></div> | |
| <div class="stat-item"> | |
| <span class="stat-num" id="statTop">β</span> | |
| <span class="stat-label">Top Likes</span> | |
| </div> | |
| <div class="stat-divider"></div> | |
| <div class="stat-item"> | |
| <span class="stat-num" id="statUpdated">β</span> | |
| <span class="stat-label">Last Fetched</span> | |
| </div> | |
| </div> | |
| <!-- Controls --> | |
| <div class="controls" id="controls"> | |
| <div class="search-wrap"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/> | |
| </svg> | |
| <input type="text" id="searchInput" placeholder="Search by space name or description..." autocomplete="off"/> | |
| </div> | |
| <!-- API Sort Toggle: Trending vs Likes (re-fetches) --> | |
| <div class="api-sort-toggle"> | |
| <button class="api-sort-btn active" id="btnTrending" onclick="setApiSort('trending')"> | |
| <span class="trend-dot"></span> Trending | |
| </button> | |
| <button class="api-sort-btn" id="btnLikes" onclick="setApiSort('likes')"> | |
| β€οΈ Most Liked | |
| </button> | |
| </div> | |
| <div class="filter-pills" id="filterPills"> | |
| <button class="pill active" data-cat="all">All</button> | |
| <button class="pill" data-cat="llm">π§ LLM</button> | |
| <button class="pill" data-cat="code">π» Coding</button> | |
| <button class="pill" data-cat="multi">ποΈ Multimodal</button> | |
| <button class="pill" data-cat="agent">π€ Agents</button> | |
| <button class="pill" data-cat="agi">π AGI</button> | |
| <button class="pill" data-cat="chat">π¬ Chat</button> | |
| <button class="pill" data-cat="science">π¬ Reasoning</button> | |
| <button class="pill" data-cat="gen">π¨ Generative</button> | |
| <button class="pill" data-cat="safety">π‘οΈ Safety</button> | |
| </div> | |
| <select class="sort-select" id="sortSelect"> | |
| <option value="api">β Secondary Sort β</option> | |
| <option value="name">π€ Name AβZ</option> | |
| <option value="updated">π Recently Updated</option> | |
| </select> | |
| <button class="refresh-btn" id="refreshBtn" onclick="fetchData(true)"> | |
| <svg class="spin" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> | |
| <path d="M21 2v6h-6M3 12a9 9 0 0115-6.7L21 8M3 22v-6h6M21 12a9 9 0 01-15 6.7L3 16"/> | |
| </svg> | |
| Refresh | |
| </button> | |
| <button class="theme-btn" id="themeBtn" onclick="toggleTheme()"> | |
| <span class="theme-icon" id="themeIcon">π</span> | |
| <span id="themeLabel">Night</span> | |
| </button> | |
| </div> | |
| <!-- Main content --> | |
| <main class="main"> | |
| <div class="results-info" id="resultsInfo"></div> | |
| <!-- Top 3 podium --> | |
| <div class="podium" id="podium"></div> | |
| <!-- Table header --> | |
| <div class="table-header"> | |
| <span>Rank</span> | |
| <span>Space / Description</span> | |
| <span>β€οΈ Likes</span> | |
| <span>Category</span> | |
| <span>SDK</span> | |
| <span>Visit</span> | |
| </div> | |
| <!-- Cards --> | |
| <div class="space-list" id="spaceList"></div> | |
| </main> | |
| <footer class="footer"> | |
| Data source: <a href="https://huggingface.co/api/spaces" target="_blank">HuggingFace Hub API</a> | |
| Β· | |
| Built by <a href="https://huggingface.co/FINAL-Bench" target="_blank">FINAL-Bench</a> | |
| Β· | |
| <span id="footerTime"></span> | |
| </footer> | |
| <script> | |
| // βββ CONFIG βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const HF_API = 'https://huggingface.co/api/spaces'; | |
| const LIMIT = 100; | |
| const CATEGORIES = { | |
| llm: { label: 'π§ LLM', cls: 'cat--llm', keys: ['open-llm','lm-evaluation','llm-bench','language-model','text-generation','instruct','chat','gpt','llama','mistral','gemma','qwen','phi'] }, | |
| code: { label: 'π» Coding', cls: 'cat--code', keys: ['code','human-eval','coding','swe','bigcode','programming','coder','leetcode','benchmark-code'] }, | |
| multi: { label: 'ποΈ Multimodal', cls: 'cat--multi', keys: ['vision','multimodal','vqa','vlm','mmmu','image-understanding','visual','mmbench'] }, | |
| agent: { label: 'π€ Agents', cls: 'cat--agent', keys: ['agent','tool-use','function-call','web-agent','task','swe-bench','agentic','worldcup','tournament','smol'] }, | |
| agi: { label: 'π AGI / Meta', cls: 'cat--agi', keys: ['agi','final','frontier','meta','general','arc','gpqa','mmlu','hellaswag','winogrande'] }, | |
| chat: { label: 'π¬ Chat / Pref', cls: 'cat--chat', keys: ['arena','chatbot','preference','reward','rlhf','dpo','alignment','mt-bench','lmarena'] }, | |
| science: { label: 'π¬ Reasoning', cls: 'cat--science', keys: ['math','reasoning','science','stem','logic','math-bench','gsm','minerva','physic'] }, | |
| gen: { label: 'π¨ Generative', cls: 'cat--gen', keys: ['image-gen','text-to-image','video','diffusion','stable','dalle','midjourney','flux','text2image','generation','tts','speech','voice','music'] }, | |
| safety: { label: 'π‘οΈ Safety', cls: 'cat--safety', keys: ['safety','bias','toxic','harm','red-team','trustworthy','honest','truthful','hallucin'] }, | |
| }; | |
| const MEDALS = ['π₯','π₯','π₯']; | |
| // βββ CURATED DESCRIPTION DATABASE βββββββββββββββββββββββββββββββ | |
| // Overrides empty/weak API descriptions for well-known leaderboards | |
| const DESC_DB = { | |
| // ββ LLM General ββ | |
| 'open-llm-leaderboard/open_llm_leaderboard': | |
| 'The #1 open LLM benchmark by HuggingFace. Evaluates on IFEval, BBH, MATH, GPQA, MUSR, MMLU-Pro. The gold standard for ranking open-source models.', | |
| 'HuggingFaceH4/open_llm_leaderboard': | |
| 'Original Open LLM Leaderboard (v1) by HuggingFace. Evaluated on ARC, HellaSwag, TruthfulQA, MMLU. Retired but historically significant.', | |
| 'lmarena-ai/arena-leaderboard': | |
| 'LMArena (formerly Chatbot Arena) official leaderboard. ELO ratings computed from 1M+ human preference votes. Crowdsourced ground truth for LLM quality.', | |
| 'lmarena-ai/chatbot-arena-leaderboard': | |
| 'Chatbot Arena leaderboard by LMSYS/UC Berkeley. Ranks models via ELO from blind pairwise human votes. Covers MT-Bench and MMLU alongside Arena scores.', | |
| 'lmarena-ai/chatbot-arena': | |
| 'Live Chatbot Arena by lmarena-ai. Compare any two LLMs head-to-head and vote on the winner. Powers the most human-validated LLM ranking on the internet.', | |
| 'ArtificialAnalysis/LLM-Performance-Leaderboard': | |
| 'ArtificialAnalysis LLM benchmark: quality, speed, price, and context length across GPT-4, Claude, Gemini, Llama, and 60+ models. Best for cost-performance decisions.', | |
| 'allenai/WildBench': | |
| 'WildBench v2 by AI2. Evaluates LLMs on 1024 challenging real-world tasks with GPT-4-as-judge, using win-rate over GPT-4-turbo as the scoring baseline.', | |
| // ββ Coding ββ | |
| 'bigcode/bigcodebench-leaderboard': | |
| 'BigCodeBench: 1,140 function-level coding tasks across 139 libraries. Tests real-world tool use and multi-library composition. Pass@1 with greedy decoding.', | |
| 'bigcode/bigcode-models-leaderboard': | |
| 'Big Code Models Leaderboard by BigCode. Ranks open-source code LLMs on HumanEval and MultiPL-E across 18 programming languages. Includes throughput benchmarks.', | |
| 'livecodebench/leaderboard': | |
| 'LiveCodeBench: contamination-free coding eval using problems from LeetCode, AtCoder, and CodeForces with release-date filtering. Tests code gen, self-repair, and execution.', | |
| 'EvalPlus/leaderboard': | |
| 'EvalPlus leaderboard: rigorous HumanEval+ and MBPP+ evaluations with 80Γ more test cases than the originals. Exposes model weaknesses hidden by weak test suites.', | |
| // ββ Embedding / Retrieval ββ | |
| 'mteb/leaderboard': | |
| 'MTEB: Massive Text Embedding Benchmark. 58 datasets Γ 112 languages Γ 8 task types (retrieval, clustering, classification, STSβ¦). The definitive embedding model ranking.', | |
| // ββ Multimodal ββ | |
| 'ArtificialAnalysis/Text-to-Image-Leaderboard': | |
| 'ArtificialAnalysis Image Arena & Leaderboard. Ranks text-to-image models (DALL-E 3, Midjourney, Flux, SD3β¦) by quality, speed, and price from human preference votes.', | |
| 'ArtificialAnalysis/Video-Generation-Arena-Leaderboard': | |
| 'ArtificialAnalysis Video Arena. Ranks text-to-video and image-to-video models (Sora, Runway, Klingβ¦) by quality and human preference votes.', | |
| // ββ Speech / Audio ββ | |
| 'ArtificialAnalysis/Speech-Arena-Leaderboard': | |
| 'ArtificialAnalysis TTS Arena. Ranks text-to-speech systems (ElevenLabs, OpenAI TTS, PlayHTβ¦) by naturalness and quality from human preference votes.', | |
| 'ArtificialAnalysis/Music-Arena-Leaderboard': | |
| 'ArtificialAnalysis Music Arena. Community votes rank AI music generators (Suno, Udio, Google MusicLMβ¦) on creativity, quality, and coherence.', | |
| 'TTS-AGI/TTS-Arena': | |
| 'TTS Arena: blind pairwise voting leaderboard for text-to-speech models. ELO-rated from thousands of human votes. The Chatbot Arena equivalent for voice AI.', | |
| // ββ Chat / Preference / RLHF ββ | |
| 'allenai/reward-bench': | |
| 'RewardBench by AI2: evaluates reward models across Chat, Chat-Hard, Safety, and Reasoning categories. Essential for RLHF / DPO practitioners choosing reward models.', | |
| // ββ AGI / Meta ββ | |
| 'FINAL-Bench/all-bench-leaderboard': | |
| 'ALL Bench Leaderboard by FINAL-Bench. Aggregates 91 AI models across multiple benchmarks into a unified metacognitive ranking. Meta-evaluation of AGI progress.', | |
| 'FINAL-Bench/Leaderboard': | |
| 'FINAL Bench Metacognitive Leaderboard. World\'s first benchmark measuring AI self-correction ability. 100 tasks Γ 15 domains Γ 8 TICOS types. HuggingFace Global Top 5 dataset.', | |
| // ββ Small / Efficient Models ββ | |
| 'ginigen-ai/smol-worldcup': | |
| 'Smol AI WorldCup by Ginigen AI. Tournament platform for small models (β€8B params). Auto-scored with FINAL Bench. Open competition with live leaderboard and public dataset.', | |
| }; | |
| // βββ STATE ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| let allData = []; | |
| let filtered = []; | |
| let activecat = 'all'; | |
| let sortMode = 'api'; // 'api' | 'name' | 'updated' | |
| let apiSort = 'trending'; // 'trending' | 'likes' β triggers re-fetch | |
| let searchQ = ''; | |
| let maxLikes = 1; | |
| // βββ API SORT TOGGLE βββββββββββββββββββββββββββββββββββββββββββββ | |
| function setApiSort(mode) { | |
| if (apiSort === mode) return; | |
| apiSort = mode; | |
| document.getElementById('btnTrending').classList.toggle('active', mode === 'trending'); | |
| document.getElementById('btnLikes').classList.toggle('active', mode === 'likes'); | |
| // Reset secondary sort to api order | |
| document.getElementById('sortSelect').value = 'api'; | |
| sortMode = 'api'; | |
| fetchData(true); | |
| } | |
| // βββ CATEGORY DETECTION βββββββββββββββββββββββββββββββββββββββββ | |
| function detectCategory(id, tags, desc) { | |
| const text = (id + ' ' + (tags||[]).join(' ') + ' ' + (desc||'')).toLowerCase(); | |
| for (const [key, cat] of Object.entries(CATEGORIES)) { | |
| if (cat.keys.some(k => text.includes(k))) return key; | |
| } | |
| return 'etc'; | |
| } | |
| // βββ FETCH DATA ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function fetchData(forceRefresh = false) { | |
| const btn = document.getElementById('refreshBtn'); | |
| btn.classList.add('loading'); | |
| showLoading(true); | |
| try { | |
| const params = new URLSearchParams({ | |
| search: 'leaderboard', | |
| limit: LIMIT, | |
| full: 'true', | |
| direction: '-1', | |
| }); | |
| // Trending = sort=trendingScore&direction=-1 | |
| // Most Liked = sort=likes&direction=-1 | |
| params.set('sort', apiSort === 'trending' ? 'trendingScore' : 'likes'); | |
| const res = await fetch(`${HF_API}?${params}`); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const json = await res.json(); | |
| // νμ ν¬ν¨ν μ€νμ΄μ€ (κ²μμ΄μ μ μ‘νλ κ²λ€) | |
| // β κ°λ³ /api/spaces/:id λ trendingScore λ―Έλ°ν β author κ²μμΌλ‘ trendingScore ν보 | |
| const PINNED_SPACES = [ | |
| { id: 'ginigen-ai/smol-worldcup', author: 'ginigen-ai', name: 'smol-worldcup' }, | |
| { id: 'lmarena-ai/chatbot-arena', author: 'lmarena-ai', name: 'chatbot-arena' }, | |
| { id: 'TTS-AGI/TTS-Arena', author: 'TTS-AGI', name: 'TTS-Arena' }, | |
| ]; | |
| const pinnedResults = await Promise.all(PINNED_SPACES.map(async ({ id, author, name }) => { | |
| // μ΄λ―Έ λ©μΈ κ²μμ ν¬ν¨λ κ²½μ° μ€ν΅ | |
| if (json.some(s => s.id === id)) return null; | |
| try { | |
| // author κ²μμΌλ‘ trendingScore ν¬ν¨λ λ°μ΄ν° ν보 | |
| const r = await fetch( | |
| `${HF_API}?author=${author}&search=${name}&sort=trendingScore&direction=-1&limit=5&full=true` | |
| ); | |
| if (!r.ok) return null; | |
| const arr = await r.json(); | |
| return arr.find(s => s.id === id) || null; | |
| } catch { return null; } | |
| })); | |
| // μ€λ³΅ μ κ±° ν λ³ν© | |
| const existingIds = new Set(json.map(s => s.id)); | |
| const toMerge = pinnedResults.filter(s => s && !existingIds.has(s.id)); | |
| let merged = [...json, ...toMerge]; | |
| // μ λ ¬ | |
| if (apiSort === 'likes') { | |
| merged.sort((a, b) => (b.likes || 0) - (a.likes || 0)); | |
| } else { | |
| merged.sort((a, b) => (b.trendingScore ?? -1) - (a.trendingScore ?? -1)); | |
| } | |
| // ββ κΈλ‘λ² μ€μκ° μμ λ§΅ κ΅¬μΆ ββββββββββββββββββββββββββββββββββ | |
| let globalRankMap = {}; | |
| try { | |
| const gSort = apiSort === 'trending' ? 'trendingScore' : 'likes'; | |
| const gRes = await fetch(`${HF_API}?sort=${gSort}&direction=-1&limit=1000`); | |
| if (gRes.ok) { | |
| const gList = await gRes.json(); | |
| gList.forEach((s, i) => { globalRankMap[s.id] = i + 1; }); | |
| } | |
| } catch (e) { /* μ€ν¨ μ 무μ */ } | |
| allData = merged.map((s) => { | |
| const id = s.id || ''; | |
| const tags = s.tags || []; | |
| const card = s.cardData || {}; | |
| const apiDesc = card.short_description || s.shortDescription || ''; | |
| const desc = DESC_DB[id] || apiDesc; | |
| return { | |
| id, | |
| likes: s.likes || 0, | |
| sdk: s.sdk || '', | |
| updated: (s.lastModified || '').slice(0, 10), | |
| desc, | |
| tags, | |
| category: detectCategory(id, tags, desc), | |
| url: `https://huggingface.co/spaces/${id}`, | |
| globalRank: globalRankMap[id] || null, | |
| }; | |
| }); | |
| maxLikes = Math.max(...allData.map(d => d.likes), 1); | |
| updateStats(); | |
| applyFilters(); | |
| renderPodium(); | |
| document.getElementById('footerTime').textContent = | |
| 'Last updated: ' + new Date().toLocaleString('en-US'); | |
| } catch (e) { | |
| document.getElementById('spaceList').innerHTML = ` | |
| <div class="empty-state"> | |
| <div class="icon">β οΈ</div> | |
| <p>Failed to load data.<br>${e.message}</p> | |
| </div>`; | |
| console.error(e); | |
| } finally { | |
| btn.classList.remove('loading'); | |
| showLoading(false); | |
| } | |
| } | |
| // βββ STATS βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function updateStats() { | |
| const total = allData.length; | |
| const totalLikes = allData.reduce((s, d) => s + d.likes, 0); | |
| const topLikes = maxLikes; | |
| animateNum('statTotal', total); | |
| animateNum('statLikes', totalLikes); | |
| animateNum('statTop', topLikes); | |
| document.getElementById('statUpdated').textContent = | |
| new Date().toLocaleTimeString('en-US', {hour:'2-digit', minute:'2-digit'}); | |
| } | |
| function animateNum(id, target) { | |
| const el = document.getElementById(id); | |
| const duration = 800, fps = 60; | |
| const steps = duration / (1000 / fps); | |
| let current = 0; | |
| const inc = target / steps; | |
| const timer = setInterval(() => { | |
| current = Math.min(current + inc, target); | |
| el.textContent = Math.round(current).toLocaleString(); | |
| if (current >= target) clearInterval(timer); | |
| }, 1000 / fps); | |
| } | |
| // βββ FILTERS βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function applyFilters() { | |
| filtered = allData.filter(d => { | |
| if (activecat !== 'all' && d.category !== activecat) return false; | |
| if (searchQ) { | |
| const q = searchQ.toLowerCase(); | |
| if (!d.id.toLowerCase().includes(q) && !d.desc.toLowerCase().includes(q)) return false; | |
| } | |
| return true; | |
| }); | |
| // Sort | |
| if (sortMode === 'name') filtered.sort((a,b) => a.id.localeCompare(b.id)); | |
| else if (sortMode === 'updated') filtered.sort((a,b) => b.updated.localeCompare(a.updated)); | |
| // 'api': preserve the order returned by HF API (trending or likes) | |
| renderList(); | |
| updateResultsInfo(); | |
| } | |
| function updateResultsInfo() { | |
| const el = document.getElementById('resultsInfo'); | |
| const modeLabel = apiSort === 'trending' ? 'π₯ Trending' : 'β€οΈ Most Liked'; | |
| el.innerHTML = `Showing <strong>${filtered.length}</strong> of ${allData.length} leaderboards Β· ${modeLabel}`; | |
| } | |
| // βββ PODIUM ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function renderPodium() { | |
| const top3 = allData.slice(0, 3); | |
| const colors = ['gold','silver','bronze']; | |
| const podium = document.getElementById('podium'); | |
| podium.innerHTML = top3.map((d, i) => { | |
| const parts = d.id.split('/'); | |
| const name = parts[parts.length - 1]; | |
| return ` | |
| <a class="podium-card ${colors[i]}" href="${d.url}" target="_blank"> | |
| <span class="podium-medal">${MEDALS[i]}</span> | |
| <div class="podium-name">${d.id}</div> | |
| <div class="podium-likes">${d.likes.toLocaleString()}</div> | |
| <span class="podium-likes-label">β€οΈ Likes</span> | |
| ${d.desc ? `<div style="font-size:0.7rem;color:var(--muted);margin-top:0.5rem;font-family:'DM Mono',monospace">${d.desc.slice(0,60)}${d.desc.length>60?'β¦':''}</div>` : ''} | |
| </a>`; | |
| }).join(''); | |
| } | |
| // βββ RENDER LIST βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function renderList() { | |
| const list = document.getElementById('spaceList'); | |
| if (filtered.length === 0) { | |
| list.innerHTML = `<div class="empty-state"><div class="icon">π</div><p>No results found.</p></div>`; | |
| return; | |
| } | |
| list.innerHTML = filtered.slice(0, 80).map((d, idx) => { | |
| const rank = idx + 1; | |
| const parts = d.id.split('/'); | |
| const org = parts.length > 1 ? parts[0] : ''; | |
| const name = parts[parts.length - 1]; | |
| const cat = CATEGORIES[d.category] || { label: 'π Other', cls: 'cat--etc' }; | |
| const likesPct = Math.round((d.likes / maxLikes) * 100); | |
| const sdkIcon = { gradio:'ποΈ', streamlit:'π', docker:'π³', static:'π' }[d.sdk] || 'β'; | |
| const rankCls = rank === 1 ? 'top1' : rank === 2 ? 'top2' : rank === 3 ? 'top3' : ''; | |
| const delay = Math.min(idx * 0.025, 1.5); | |
| return ` | |
| <a class="space-card" href="${d.url}" target="_blank" style="animation-delay:${delay}s"> | |
| <div class="rank-cell"> | |
| <div class="rank-num ${rankCls}">${rank <= 3 ? MEDALS[rank-1] : rank}</div> | |
| ${d.globalRank | |
| ? `<div class="global-rank">HF #${d.globalRank.toLocaleString()}</div>` | |
| : `<div class="global-rank" style="opacity:0.3">β</div>`} | |
| </div> | |
| <div class="space-info"> | |
| <div class="space-name"> | |
| ${org ? `<span class="org">${org}/</span>` : ''}${name} | |
| </div> | |
| ${d.desc | |
| ? `<div class="space-desc">${d.desc}</div>` | |
| : `<div class="space-desc" style="color:#333">No description</div>`} | |
| </div> | |
| <div class="likes-cell" style="flex-direction:column;align-items:flex-start;gap:3px"> | |
| <span style="display:flex;align-items:center;gap:4px"> | |
| <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg> | |
| ${d.likes.toLocaleString()} | |
| </span> | |
| <div class="likes-bar-wrap" style="width:80px"> | |
| <div class="likes-bar" style="width:${likesPct}%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <span class="cat-badge ${cat.cls}">${cat.label}</span> | |
| </div> | |
| <div class="sdk-badge">${sdkIcon} ${d.sdk || 'β'}</div> | |
| <span class="visit-btn">Visit β</span> | |
| </a>`; | |
| }).join(''); | |
| } | |
| // βββ EVENT LISTENERS βββββββββββββββββββββββββββββββββββββββββββββ | |
| document.getElementById('filterPills').addEventListener('click', e => { | |
| const btn = e.target.closest('.pill'); | |
| if (!btn) return; | |
| document.querySelectorAll('.pill').forEach(p => p.classList.remove('active')); | |
| btn.classList.add('active'); | |
| activecat = btn.dataset.cat; | |
| applyFilters(); | |
| }); | |
| document.getElementById('searchInput').addEventListener('input', e => { | |
| searchQ = e.target.value; | |
| applyFilters(); | |
| }); | |
| document.getElementById('sortSelect').addEventListener('change', e => { | |
| sortMode = e.target.value; // 'api' | 'name' | 'updated' | |
| applyFilters(); | |
| }); | |
| // βββ INIT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| fetchData(); | |
| function showLoading(show) { | |
| const el = document.getElementById('loadingScreen'); | |
| if (show) el.classList.remove('hidden'); | |
| else setTimeout(() => el.classList.add('hidden'), 400); | |
| } | |
| // βββ THEME βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function toggleTheme() { | |
| const isNight = document.documentElement.classList.toggle('night'); | |
| document.getElementById('themeIcon').textContent = isNight ? 'βοΈ' : 'π'; | |
| document.getElementById('themeLabel').textContent = isNight ? 'Light' : 'Night'; | |
| localStorage.setItem('lol-theme', isNight ? 'night' : 'light'); | |
| } | |
| // Apply saved theme on load (default: light) | |
| (function() { | |
| const saved = localStorage.getItem('lol-theme'); | |
| if (saved === 'night') { | |
| document.documentElement.classList.add('night'); | |
| // update button after DOM loads | |
| document.addEventListener('DOMContentLoaded', () => { | |
| document.getElementById('themeIcon').textContent = 'βοΈ'; | |
| document.getElementById('themeLabel').textContent = 'Light'; | |
| }); | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> |