Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>AETHER | Atmospheric Dashboard</title>
- <!-- Google Fonts: Outfit & Inter -->
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
- <style>
- /* -------------------------------------------------------------
- 1. DESIGN SYSTEM & CUSTOM PROPERTIES
- ------------------------------------------------------------- */
- :root {
- /* Base Colors */
- --bg-base: #070a13;
- --bg-surface: rgba(17, 24, 43, 0.65);
- --bg-surface-hover: rgba(26, 35, 62, 0.85);
- --border-glow: rgba(255, 255, 255, 0.08);
- --border-glow-highlight: rgba(255, 255, 255, 0.15);
- --text-primary: #f8fafc;
- --text-secondary: #94a3b8;
- --text-muted: #64748b;
- /* Active Theme Accent Colors (Transitioned dynamically) */
- --theme-color: var(--color-sun);
- --theme-glow: var(--glow-sun);
- /* Weather Accents */
- --color-sun: #eab308;
- --color-sun-glow: rgba(234, 179, 8, 0.15);
- --color-wind: #2dd4bf;
- --color-wind-glow: rgba(45, 212, 191, 0.12);
- --color-rain: #6366f1;
- --color-rain-glow: rgba(99, 102, 241, 0.15);
- --color-snow: #38bdf8;
- --color-snow-glow: rgba(56, 189, 248, 0.15);
- /* Typography */
- --font-display: 'Outfit', sans-serif;
- --font-body: 'Inter', sans-serif;
- /* Animation Timings */
- --transition-slow: 0.6s cubic-bezier(0.25, 1, 0.5, 1);
- --transition-normal: 0.3s cubic-bezier(0.25, 1, 0.5, 1);
- --transition-fast: 0.15s cubic-bezier(0.25, 1, 0.5, 1);
- }
- /* -------------------------------------------------------------
- 2. RESET & BASE STYLING
- ------------------------------------------------------------- */
- *, *::before, *::after {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- }
- body {
- background-color: var(--bg-base);
- color: var(--text-primary);
- font-family: var(--font-body);
- min-height: 100vh;
- overflow-x: hidden;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- position: relative;
- }
- /* Ambient background gradient glow */
- .bg-glow {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 80vw;
- height: 80vh;
- background: radial-gradient(circle, var(--theme-glow) 0%, transparent 70%);
- filter: blur(140px);
- z-index: 0;
- pointer-events: none;
- transition: background var(--transition-slow);
- will-change: background;
- }
- /* -------------------------------------------------------------
- 3. LAYOUT & CONTAINER
- ------------------------------------------------------------- */
- .dashboard-container {
- width: 100%;
- max-width: 1160px;
- padding: 2.5rem 1.5rem;
- z-index: 1;
- display: flex;
- flex-direction: column;
- gap: 2rem;
- }
- /* Header styling */
- .dashboard-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 1.5rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- padding-bottom: 1.5rem;
- }
- .brand {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- }
- .brand-logo {
- width: 32px;
- height: 32px;
- position: relative;
- }
- .brand-logo-ring {
- position: absolute;
- inset: 0;
- border: 3px solid var(--theme-color);
- border-radius: 50%;
- box-shadow: 0 0 15px var(--theme-color);
- transition: border-color var(--transition-slow), box-shadow var(--transition-slow);
- }
- .brand-logo-dot {
- position: absolute;
- width: 8px;
- height: 8px;
- background-color: var(--text-primary);
- border-radius: 50%;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- animation: pulse-dot 2s ease-in-out infinite alternate;
- }
- @keyframes pulse-dot {
- 0% { transform: translate(-50%, -50%) scale(0.8); opacity: 0.5; }
- 100% { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }
- }
- .brand h1 {
- font-family: var(--font-display);
- font-size: 1.6rem;
- font-weight: 800;
- letter-spacing: 0.15em;
- text-transform: uppercase;
- background: linear-gradient(135deg, var(--text-primary), var(--text-secondary));
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- }
- /* Segmented switch controller */
- .switcher-control {
- display: flex;
- background: rgba(10, 15, 30, 0.8);
- border: 1px solid rgba(255, 255, 255, 0.06);
- border-radius: 99px;
- padding: 4px;
- position: relative;
- }
- .switcher-btn {
- background: none;
- border: none;
- color: var(--text-secondary);
- font-family: var(--font-display);
- font-size: 0.85rem;
- font-weight: 600;
- letter-spacing: 0.05em;
- text-transform: uppercase;
- padding: 0.6rem 1.4rem;
- cursor: pointer;
- border-radius: 99px;
- z-index: 2;
- transition: color var(--transition-normal);
- display: flex;
- align-items: center;
- gap: 0.5rem;
- }
- .switcher-btn:hover {
- color: var(--text-primary);
- }
- .switcher-btn.active {
- color: var(--bg-base);
- }
- /* Absolute sliding pill element */
- .switcher-pill {
- position: absolute;
- top: 4px;
- left: 4px;
- height: calc(100% - 8px);
- width: 90px; /* Set dynamically */
- background: var(--theme-color);
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
- border-radius: 99px;
- z-index: 1;
- transition: left var(--transition-normal), width var(--transition-normal), background var(--transition-slow);
- }
- /* -------------------------------------------------------------
- 4. HERO DISPLAY SECTION (GRID LAYOUT)
- ------------------------------------------------------------- */
- .hero-section {
- display: grid;
- grid-template-columns: 1.4fr 1fr;
- gap: 2rem;
- }
- @media (max-width: 920px) {
- .hero-section {
- grid-template-columns: 1fr;
- }
- }
- /* Glassmorphic base card styling */
- .glass-card {
- background: var(--bg-surface);
- backdrop-filter: blur(16px);
- -webkit-backdrop-filter: blur(16px);
- border: 1px solid var(--border-glow);
- border-radius: 24px;
- box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.35);
- position: relative;
- overflow: hidden;
- transition: border var(--transition-slow), box-shadow var(--transition-slow);
- }
- /* Hero main weather display card */
- .hero-weather-card {
- display: flex;
- flex-direction: column;
- min-height: 420px;
- }
- /* Large dynamic animation screen at the top of Hero Card */
- .weather-showcase {
- height: 250px;
- position: relative;
- background: linear-gradient(180deg, rgba(10, 15, 30, 0.3) 0%, transparent 100%);
- overflow: hidden;
- border-bottom: 1px solid rgba(255, 255, 255, 0.03);
- }
- /* Interactive overlay layers for animations */
- .animation-layer {
- position: absolute;
- inset: 0;
- opacity: 0;
- visibility: hidden;
- pointer-events: none;
- transition: opacity var(--transition-slow), visibility var(--transition-slow);
- }
- .animation-layer.active {
- opacity: 1;
- visibility: visible;
- }
- /* Weather details details area */
- .weather-info-panel {
- padding: 1.5rem 2rem;
- display: flex;
- justify-content: space-between;
- align-items: flex-end;
- flex-grow: 1;
- }
- .weather-main-data {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
- }
- .location-details {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- color: var(--text-secondary);
- font-size: 0.9rem;
- font-weight: 500;
- }
- .location-details svg {
- width: 14px;
- height: 14px;
- fill: currentColor;
- }
- .temperature-wrapper {
- display: flex;
- align-items: flex-start;
- margin: 0.25rem 0;
- }
- .temperature-value {
- font-family: var(--font-display);
- font-size: 4.8rem;
- font-weight: 700;
- line-height: 0.95;
- background: linear-gradient(180deg, var(--text-primary) 30%, var(--text-secondary) 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- letter-spacing: -0.02em;
- }
- .temperature-unit {
- font-family: var(--font-display);
- font-size: 2.2rem;
- font-weight: 400;
- color: var(--text-secondary);
- margin-top: 0.4rem;
- }
- .weather-condition-text {
- font-family: var(--font-display);
- font-size: 1.3rem;
- font-weight: 600;
- letter-spacing: 0.02em;
- color: var(--text-primary);
- display: flex;
- align-items: center;
- gap: 0.6rem;
- }
- .weather-condition-text::before {
- content: '';
- display: inline-block;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: var(--theme-color);
- box-shadow: 0 0 10px var(--theme-color);
- transition: background-color var(--transition-slow), box-shadow var(--transition-slow);
- }
- .weather-extreme-details {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- gap: 0.5rem;
- text-align: right;
- }
- .weather-extreme-highlow {
- color: var(--text-secondary);
- font-size: 0.9rem;
- font-weight: 500;
- }
- .weather-extreme-highlow span.high {
- color: var(--text-primary);
- font-weight: 600;
- }
- .weather-extreme-label {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.08);
- padding: 0.35rem 0.75rem;
- border-radius: 99px;
- font-size: 0.75rem;
- font-weight: 600;
- letter-spacing: 0.05em;
- text-transform: uppercase;
- color: var(--text-secondary);
- }
- /* -------------------------------------------------------------
- 5. METRICS SUB-GRID (HERO RIGHT PANEL)
- ------------------------------------------------------------- */
- .metrics-panel {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-template-rows: 1fr 1fr;
- gap: 1.2rem;
- }
- .metric-card {
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- min-height: 140px;
- }
- .metric-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- color: var(--text-secondary);
- }
- .metric-title {
- font-size: 0.8rem;
- font-weight: 600;
- letter-spacing: 0.05em;
- text-transform: uppercase;
- }
- .metric-icon {
- width: 22px;
- height: 22px;
- color: var(--text-secondary);
- transition: color var(--transition-slow);
- }
- .metric-card:hover .metric-icon {
- color: var(--theme-color);
- }
- .metric-value-container {
- margin-top: 1rem;
- }
- .metric-value {
- font-family: var(--font-display);
- font-size: 1.8rem;
- font-weight: 700;
- color: var(--text-primary);
- }
- .metric-desc {
- font-size: 0.75rem;
- color: var(--text-muted);
- margin-top: 0.25rem;
- font-weight: 500;
- }
- /* Graphic elements inside metrics for premium look */
- .metric-bar-bg {
- width: 100%;
- height: 4px;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 99px;
- margin-top: 0.5rem;
- overflow: hidden;
- }
- .metric-bar-fill {
- height: 100%;
- background: var(--theme-color);
- border-radius: 99px;
- width: 50%;
- transition: width var(--transition-slow), background var(--transition-slow);
- }
- /* -------------------------------------------------------------
- 6. SIDE-BY-SIDE LIVE CARDS (4 COLUMNS GRID)
- ------------------------------------------------------------- */
- .side-by-side-section {
- display: flex;
- flex-direction: column;
- gap: 1.2rem;
- }
- .section-title {
- font-family: var(--font-display);
- font-size: 1.1rem;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- color: var(--text-secondary);
- display: flex;
- align-items: center;
- gap: 0.75rem;
- }
- .section-title::after {
- content: '';
- flex-grow: 1;
- height: 1px;
- background: rgba(255, 255, 255, 0.06);
- }
- .mini-cards-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 1.5rem;
- }
- @media (max-width: 820px) {
- .mini-cards-grid {
- grid-template-columns: repeat(2, 1fr);
- }
- }
- @media (max-width: 480px) {
- .mini-cards-grid {
- grid-template-columns: 1fr;
- }
- }
- /* Small live animated card */
- .mini-card {
- cursor: pointer;
- display: flex;
- flex-direction: column;
- min-height: 250px;
- transition: transform var(--transition-normal), border var(--transition-slow), box-shadow var(--transition-slow);
- }
- .mini-card:hover {
- transform: translateY(-6px);
- border-color: rgba(255, 255, 255, 0.15);
- box-shadow: 0 15px 35px 0 rgba(0, 0, 0, 0.45);
- }
- .mini-card.active {
- border-color: var(--card-accent-color);
- box-shadow: 0 8px 30px 0 var(--card-accent-glow);
- }
- .mini-card.active::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 4px;
- background-color: var(--card-accent-color);
- }
- /* Assign accent colors explicitly to mini-cards */
- .mini-card.theme-sun {
- --card-accent-color: var(--color-sun);
- --card-accent-glow: var(--color-sun-glow);
- }
- .mini-card.theme-wind {
- --card-accent-color: var(--color-wind);
- --card-accent-glow: var(--color-wind-glow);
- }
- .mini-card.theme-rain {
- --card-accent-color: var(--color-rain);
- --card-accent-glow: var(--color-rain-glow);
- }
- .mini-card.theme-snow {
- --card-accent-color: var(--color-snow);
- --card-accent-glow: var(--color-snow-glow);
- }
- /* Miniature animation showcase */
- .mini-showcase {
- height: 140px;
- position: relative;
- overflow: hidden;
- background: linear-gradient(180deg, rgba(10, 15, 30, 0.2) 0%, transparent 100%);
- border-bottom: 1px solid rgba(255, 255, 255, 0.03);
- }
- .mini-info {
- padding: 1.2rem;
- display: flex;
- flex-direction: column;
- gap: 0.2rem;
- flex-grow: 1;
- justify-content: flex-end;
- }
- .mini-label {
- font-family: var(--font-display);
- font-size: 1.05rem;
- font-weight: 700;
- color: var(--text-primary);
- }
- .mini-flex {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 0.2rem;
- }
- .mini-desc {
- font-size: 0.75rem;
- color: var(--text-secondary);
- font-weight: 500;
- }
- .mini-temp {
- font-family: var(--font-display);
- font-size: 1.4rem;
- font-weight: 700;
- color: var(--text-primary);
- }
- /* -------------------------------------------------------------
- 7. ANIMATION SPECIFICS: ☀️ SUNNY STATE
- ------------------------------------------------------------- */
- .sunny-glow {
- position: absolute;
- top: -20px;
- right: -20px;
- width: 180px;
- height: 180px;
- background: radial-gradient(circle, rgba(234, 179, 8, 0.25) 0%, transparent 70%);
- border-radius: 50%;
- }
- .mini-showcase .sunny-glow {
- width: 120px;
- height: 120px;
- top: -15px;
- right: -15px;
- }
- .sun-orb-container {
- position: absolute;
- top: 55px;
- right: 70px;
- width: 80px;
- height: 80px;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .mini-showcase .sun-orb-container {
- top: 30px;
- right: 35px;
- transform: scale(0.65);
- }
- /* Golden center sphere */
- .sun-core {
- width: 50px;
- height: 50px;
- background: radial-gradient(circle, #fef08a 0%, #f97316 85%);
- border-radius: 50%;
- box-shadow: 0 0 25px #f97316, 0 0 50px rgba(234, 179, 8, 0.4);
- animation: sun-pulse 4s ease-in-out infinite alternate;
- z-index: 3;
- }
- @keyframes sun-pulse {
- 0% { transform: scale(0.92); box-shadow: 0 0 20px #f97316, 0 0 40px rgba(234, 179, 8, 0.3); }
- 100% { transform: scale(1.08); box-shadow: 0 0 35px #f97316, 0 0 70px rgba(234, 179, 8, 0.6); }
- }
- /* Spinning geometric halos */
- .sun-rays-inner, .sun-rays-outer {
- position: absolute;
- top: 50%;
- left: 50%;
- transform-origin: center center;
- }
- .sun-rays-inner {
- width: 110px;
- height: 110px;
- margin-top: -55px;
- margin-left: -55px;
- animation: rotate-cw 24s linear infinite;
- z-index: 2;
- }
- .sun-rays-outer {
- width: 140px;
- height: 140px;
- margin-top: -70px;
- margin-left: -70px;
- animation: rotate-ccw 36s linear infinite;
- z-index: 1;
- }
- @keyframes rotate-cw {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- @keyframes rotate-ccw {
- from { transform: rotate(360deg); }
- to { transform: rotate(0deg); }
- }
- /* Floating hot particles */
- .solar-flare {
- position: absolute;
- bottom: 0;
- width: 4px;
- height: 4px;
- background: radial-gradient(circle, rgba(254, 240, 138, 0.8) 0%, rgba(249, 115, 22, 0) 100%);
- border-radius: 50%;
- pointer-events: none;
- animation: rise-and-fade var(--speed) linear infinite;
- animation-delay: var(--delay);
- left: var(--left);
- opacity: var(--opacity);
- }
- @keyframes rise-and-fade {
- 0% {
- transform: translateY(0) translateX(0) scale(0.5);
- opacity: 0;
- }
- 20% { opacity: var(--opacity); }
- 90% { opacity: 0.3; }
- 100% {
- transform: translateY(-160px) translateX(var(--drift)) scale(var(--scale));
- opacity: 0;
- }
- }
- /* -------------------------------------------------------------
- 8. ANIMATION SPECIFICS: 🍃 WINDY STATE
- ------------------------------------------------------------- */
- .wind-glow {
- position: absolute;
- top: -30px;
- left: -20px;
- width: 220px;
- height: 220px;
- background: radial-gradient(circle, rgba(45, 212, 191, 0.15) 0%, transparent 70%);
- border-radius: 50%;
- }
- /* Curved wind vectors flowing across screen */
- .wind-vector-svg {
- position: absolute;
- inset: 0;
- width: 100%;
- height: 100%;
- }
- .wind-path {
- fill: none;
- stroke: rgba(255, 255, 255, 0.15);
- stroke-linecap: round;
- stroke-dasharray: 60 180;
- animation: wind-dash var(--speed) linear infinite;
- animation-delay: var(--delay);
- }
- @keyframes wind-dash {
- 0% { stroke-dashoffset: 240; stroke: rgba(255, 255, 255, 0); }
- 10% { stroke: rgba(255, 255, 255, 0.18); }
- 80% { stroke: rgba(255, 255, 255, 0.18); }
- 100% { stroke-dashoffset: 0; stroke: rgba(255, 255, 255, 0); }
- }
- /* Swaying Tree at the bottom right corner */
- .tree-wrapper {
- position: absolute;
- bottom: -15px;
- right: 25px;
- width: 110px;
- height: 150px;
- z-index: 2;
- }
- .mini-showcase .tree-wrapper {
- width: 70px;
- height: 95px;
- right: 15px;
- bottom: -10px;
- }
- .swaying-tree-svg {
- width: 100%;
- height: 100%;
- transform-origin: bottom center;
- animation: tree-flex 5s ease-in-out infinite alternate;
- }
- .tree-canopy-top {
- transform-origin: 50px 30px;
- animation: canopy-flex-3 3.5s ease-in-out infinite alternate;
- }
- .tree-canopy-mid {
- transform-origin: 50px 52px;
- animation: canopy-flex-2 4.2s ease-in-out infinite alternate;
- }
- .tree-canopy-bot {
- transform-origin: 50px 75px;
- animation: canopy-flex-1 5s ease-in-out infinite alternate;
- }
- /* Tree sway keyframes */
- @keyframes tree-flex {
- 0% { transform: rotate(-1.5deg); }
- 100% { transform: rotate(5.5deg) skewX(2deg); }
- }
- @keyframes canopy-flex-1 {
- 0% { transform: rotate(-2deg); }
- 100% { transform: rotate(4.5deg) translateX(1px); }
- }
- @keyframes canopy-flex-2 {
- 0% { transform: rotate(-3.5deg); }
- 100% { transform: rotate(7deg) translateX(2px); }
- }
- @keyframes canopy-flex-3 {
- 0% { transform: rotate(-5deg); }
- 100% { transform: rotate(9deg) translateX(3px); }
- }
- /* Floating flying leaves */
- .drifting-leaf {
- position: absolute;
- width: 12px;
- height: 12px;
- fill: rgba(45, 212, 191, 0.4);
- pointer-events: none;
- animation: float-leaf var(--speed) linear infinite;
- animation-delay: var(--delay);
- left: var(--left);
- top: -20px;
- }
- .mini-showcase .drifting-leaf {
- width: 8px;
- height: 8px;
- }
- @keyframes float-leaf {
- 0% {
- transform: translate(-10%, 0) rotate(0deg) rotateY(0deg);
- opacity: 0;
- }
- 10% { opacity: 0.8; }
- 90% { opacity: 0.8; }
- 100% {
- transform: translate(var(--drift-x), 260px) rotate(420deg) rotateY(1080deg);
- opacity: 0;
- }
- }
- /* -------------------------------------------------------------
- 9. ANIMATION SPECIFICS: 🌧️ RAINY STATE
- ------------------------------------------------------------- */
- .rainy-glow {
- position: absolute;
- top: -40px;
- right: -20px;
- width: 240px;
- height: 240px;
- background: radial-gradient(circle, rgba(99, 102, 241, 0.16) 0%, transparent 70%);
- border-radius: 50%;
- }
- /* Rapid rain streaks */
- .rain-container {
- position: absolute;
- inset: 0;
- width: 100%;
- height: 100%;
- }
- .raindrop {
- position: absolute;
- width: 1.2px;
- background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(165, 180, 252, var(--opacity)) 100%);
- pointer-events: none;
- animation: raindrop-fall var(--speed) linear infinite;
- animation-delay: var(--delay);
- left: var(--left);
- top: -40px;
- height: var(--length);
- }
- @keyframes raindrop-fall {
- 0% {
- transform: translateY(0) skewX(-16deg);
- }
- 100% {
- transform: translateY(300px) skewX(-16deg);
- }
- }
- /* Puddle splash ripples at the bottom */
- .splash-container {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 25px;
- pointer-events: none;
- }
- .splash-ripple {
- position: absolute;
- bottom: 6px;
- width: 14px;
- height: 4px;
- border: 1px solid rgba(165, 180, 252, 0.4);
- border-radius: 50%;
- animation: ripple-expansion 1.2s ease-out infinite;
- animation-delay: var(--delay);
- left: var(--left);
- transform-origin: center center;
- }
- @keyframes ripple-expansion {
- 0% {
- transform: scale(0.2);
- opacity: 0;
- }
- 10% {
- opacity: 0.7;
- }
- 80% {
- opacity: 0.15;
- }
- 100% {
- transform: scale(1.6);
- opacity: 0;
- }
- }
- /* Moody Storm Sheet Lightning Overlay */
- .lightning-flash {
- position: absolute;
- inset: 0;
- background-color: rgba(238, 242, 255, 0.95);
- mix-blend-mode: overlay;
- pointer-events: none;
- opacity: 0;
- z-index: 10;
- animation: lightning-strike 10s ease-out infinite;
- }
- @keyframes lightning-strike {
- 0%, 94%, 96%, 100% { opacity: 0; }
- 94.8% { opacity: 0.9; }
- 95.2% { opacity: 0.25; }
- 95.6% { opacity: 0.95; }
- }
- /* -------------------------------------------------------------
- 10. ANIMATION SPECIFICS: ❄️ SNOWY STATE
- ------------------------------------------------------------- */
- .snowy-glow {
- position: absolute;
- top: -30px;
- left: -20px;
- width: 220px;
- height: 220px;
- background: radial-gradient(circle, rgba(56, 189, 248, 0.15) 0%, transparent 70%);
- border-radius: 50%;
- }
- .snow-container {
- position: absolute;
- inset: 0;
- width: 100%;
- height: 100%;
- }
- /* Falling snowflakes with horizontal sway */
- .snowflake {
- position: absolute;
- background-color: rgba(255, 255, 255, 0.9);
- border-radius: 50%;
- pointer-events: none;
- animation: snow-fall var(--speed) linear infinite;
- animation-delay: var(--delay);
- left: var(--left);
- top: -15px;
- width: var(--size);
- height: var(--size);
- opacity: var(--opacity);
- filter: blur(0.4px);
- }
- @keyframes snow-fall {
- 0% {
- transform: translateY(0) translateX(0);
- }
- 50% {
- transform: translateY(140px) translateX(var(--sway));
- }
- 100% {
- transform: translateY(280px) translateX(0);
- }
- }
- /* Fluffy layered snow accumulating banks at the bottom */
- .snow-drifts-svg {
- position: absolute;
- bottom: -4px;
- left: -5%;
- width: 110%;
- height: 35px;
- z-index: 5;
- filter: drop-shadow(0 -4px 8px rgba(56, 189, 248, 0.12));
- transition: transform var(--transition-slow), opacity var(--transition-slow);
- }
- .snow-bank {
- fill: #ffffff;
- transition: fill var(--transition-slow);
- }
- .snow-bank-back {
- fill: #e0f2fe;
- opacity: 0.85;
- }
- /* -------------------------------------------------------------
- 11. DYNAMIC CONTROLS & TRANSITIONS (STATE MANAGEMENT)
- ------------------------------------------------------------- */
- /* Dynamic active indicators on weather panel */
- .weather-extreme-label.glow-active {
- animation: state-glow 2s infinite alternate;
- }
- @keyframes state-glow {
- 0% { box-shadow: 0 0 6px var(--theme-color); border-color: var(--theme-color); }
- 100% { box-shadow: 0 0 16px var(--theme-color); border-color: var(--theme-color); }
- }
- /* Smooth text changes */
- .value-transition {
- animation: fade-in-value 0.35s cubic-bezier(0.25, 1, 0.5, 1);
- }
- @keyframes fade-in-value {
- from { opacity: 0; transform: translateY(4px); filter: blur(2px); }
- to { opacity: 1; transform: translateY(0); filter: blur(0); }
- }
- </style>
- </head>
- <body>
- <!-- Backdrop dynamic ambient light glowing behind the UI -->
- <div class="bg-glow" id="ambientGlow"></div>
- <div class="dashboard-container">
- <!-- HEADER -->
- <header class="dashboard-header">
- <div class="brand">
- <div class="brand-logo">
- <div class="brand-logo-ring"></div>
- <div class="brand-logo-dot"></div>
- </div>
- <h1>Aether</h1>
- </div>
- <!-- Segmented selection buttons -->
- <div class="switcher-control" id="switcherControl">
- <div class="switcher-pill" id="switcherPill"></div>
- <button class="switcher-btn active" data-weather="sun">Sunny</button>
- <button class="switcher-btn" data-weather="wind">Windy</button>
- <button class="switcher-btn" data-weather="rain">Rainy</button>
- <button class="switcher-btn" data-weather="snow">Snowy</button>
- </div>
- </header>
- <!-- HERO DISPLAY (MAIN CARD + SUB METRICS) -->
- <main class="hero-section">
- <!-- HERO WEATHER CARD -->
- <div class="glass-card hero-weather-card" id="heroCard">
- <!-- Interactive Showcase Canvas -->
- <div class="weather-showcase" id="heroShowcase">
- <!-- SUNNY STAGE LAYERS -->
- <div class="animation-layer active" data-weather="sun">
- <div class="sunny-glow"></div>
- <div class="sun-orb-container">
- <div class="sun-core"></div>
- <!-- Inner rays (spoke design) -->
- <svg class="sun-rays-inner" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg">
- <g stroke="#fef08a" stroke-width="2.5" stroke-linecap="round">
- <line x1="80" y1="15" x2="80" y2="28" />
- <line x1="80" y1="132" x2="80" y2="145" />
- <line x1="15" y1="80" x2="28" y2="80" />
- <line x1="132" y1="80" x2="145" y2="80" />
- <line x1="34" y1="34" x2="44" y2="44" />
- <line x1="116" y1="116" x2="126" y2="122" />
- <line x1="116" y1="34" x2="126" y2="44" />
- <line x1="34" y1="116" x2="44" y2="122" />
- </g>
- </svg>
- <!-- Outer rays (glow rings) -->
- <svg class="sun-rays-outer" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg">
- <g stroke="#f97316" stroke-width="2" stroke-dasharray="3 5" fill="none" opacity="0.6">
- <circle cx="80" cy="80" r="54" />
- <circle cx="80" cy="80" r="66" />
- </g>
- </svg>
- </div>
- <!-- Dynamic particle container (populated via JS) -->
- <div class="particle-container showcase-particles" data-type="sun"></div>
- </div>
- <!-- WINDY STAGE LAYERS -->
- <div class="animation-layer" data-weather="wind">
- <div class="wind-glow"></div>
- <!-- Spline wind currents -->
- <svg class="wind-vector-svg" viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg">
- <path class="wind-path" d="M15 20 Q55 5 105 25 T195 20" stroke-width="1.8" var-speed="2s" style="--speed: 2.2s; --delay: 0s;" />
- <path class="wind-path" d="M25 55 Q75 75 125 50 T195 60" stroke-width="1.5" style="--speed: 2.8s; --delay: -0.6s;" />
- <path class="wind-path" d="M15 90 Q65 70 115 95 T185 85" stroke-width="2" style="--speed: 2.4s; --delay: -0.3s;" />
- </svg>
- <!-- Organic Pine tree silhouette -->
- <div class="tree-wrapper">
- <svg class="swaying-tree-svg" viewBox="0 0 100 130" xmlns="http://www.w3.org/2000/svg">
- <!-- Trunk -->
- <path d="M47 130 L48 95 Q48 90 52 85 L52 130 Z" fill="#2d3748" />
- <!-- Layered Canopy -->
- <g class="tree-canopy-bot">
- <path d="M15 95 Q50 68 85 95 Q50 82 15 95 Z" fill="#0d9488" opacity="0.85" />
- </g>
- <g class="tree-canopy-mid">
- <path d="M22 72 Q50 48 78 72 Q50 60 22 72 Z" fill="#14b8a6" opacity="0.9" />
- </g>
- <g class="tree-canopy-top">
- <path d="M30 48 Q50 25 70 48 Q50 39 30 48 Z" fill="#2dd4bf" opacity="0.98" />
- </g>
- </svg>
- </div>
- <!-- Dynamic particle container (populated via JS) -->
- <div class="particle-container showcase-particles" data-type="wind"></div>
- </div>
- <!-- RAINY STAGE LAYERS -->
- <div class="animation-layer" data-weather="rain">
- <div class="rainy-glow"></div>
- <!-- Lightning flashes overlay -->
- <div class="lightning-flash"></div>
- <!-- Core falling raindrop streaks -->
- <div class="rain-container showcase-particles" data-type="rain"></div>
- <!-- Floor splatters -->
- <div class="splash-container" id="heroSplashes"></div>
- </div>
- <!-- SNOWY STAGE LAYERS -->
- <div class="animation-layer" data-weather="snow">
- <div class="snowy-glow"></div>
- <!-- Core falling snowflake shapes -->
- <div class="snow-container showcase-particles" data-type="snow"></div>
- <!-- Frost accumulated banks -->
- <svg class="snow-drifts-svg" viewBox="0 0 200 40" xmlns="http://www.w3.org/2000/svg">
- <path class="snow-bank snow-bank-back" d="M-10 40 L-10 18 Q40 5 95 18 T210 15 L210 40 Z" />
- <path class="snow-bank" d="M-10 40 L-10 23 Q50 12 110 26 T210 20 L210 40 Z" />
- </svg>
- </div>
- </div>
- <!-- Meta readings panel -->
- <div class="weather-info-panel">
- <div class="weather-main-data">
- <div class="location-details">
- <!-- Inline Location Pin SVG -->
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
- <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
- </svg>
- <span id="locVal">St. Moritz, Switzerland</span>
- </div>
- <div class="temperature-wrapper">
- <span class="temperature-value" id="tempVal">24</span>
- <span class="temperature-unit">°C</span>
- </div>
- <div class="weather-condition-text" id="condVal">Clear & Sunny</div>
- </div>
- <div class="weather-extreme-details">
- <div class="weather-extreme-highlow" id="highlowVal">
- H: <span class="high">26°</span> L: 18°
- </div>
- <div class="weather-extreme-label glow-active" id="labelVal">Perfect</div>
- </div>
- </div>
- </div>
- <!-- METRICS SUB-GRID -->
- <div class="metrics-panel">
- <!-- Wind speed -->
- <div class="glass-card metric-card">
- <div class="metric-header">
- <span class="metric-title">Wind Speed</span>
- <!-- Wind icon -->
- <svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2"></path>
- </svg>
- </div>
- <div class="metric-value-container">
- <div class="metric-value" id="windMetric">6 km/h</div>
- <div class="metric-desc" id="windMetricDesc">Breeze • Calm gusting</div>
- <div class="metric-bar-bg"><div class="metric-bar-fill" id="windBar" style="width: 15%;"></div></div>
- </div>
- </div>
- <!-- Humidity -->
- <div class="glass-card metric-card">
- <div class="metric-header">
- <span class="metric-title">Humidity</span>
- <!-- Droplet icon -->
- <svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M12 22a7 7 0 0 0 7-7c0-4.3-7-11-7-11S5 10.7 5 15a7 7 0 0 0 7 7z"></path>
- </svg>
- </div>
- <div class="metric-value-container">
- <div class="metric-value" id="humidMetric">45%</div>
- <div class="metric-desc" id="humidMetricDesc">Comfortable and dry</div>
- <div class="metric-bar-bg"><div class="metric-bar-fill" id="humidBar" style="width: 45%;"></div></div>
- </div>
- </div>
- <!-- UV Index -->
- <div class="glass-card metric-card">
- <div class="metric-header">
- <span class="metric-title">UV Index</span>
- <!-- Sun icon -->
- <svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <circle cx="12" cy="12" r="4"></circle>
- <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path>
- </svg>
- </div>
- <div class="metric-value-container">
- <div class="metric-value" id="uvMetric">7 (High)</div>
- <div class="metric-desc" id="uvMetricDesc">SPF 30+ recommended</div>
- <div class="metric-bar-bg"><div class="metric-bar-fill" id="uvBar" style="width: 70%;"></div></div>
- </div>
- </div>
- <!-- Air Quality -->
- <div class="glass-card metric-card">
- <div class="metric-header">
- <span class="metric-title">Air Quality</span>
- <!-- Leaf icon -->
- <svg class="metric-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 3.58-1 8.9A6.99 6.99 0 0 1 11 20z"></path>
- <path d="M19 2c-2.26 4.33-5.27 7.14-8 8"></path>
- </svg>
- </div>
- <div class="metric-value-container">
- <div class="metric-value" id="aqiMetric">35</div>
- <div class="metric-desc" id="aqiMetricDesc">Excellent atmospheric quality</div>
- <div class="metric-bar-bg"><div class="metric-bar-fill" id="aqiBar" style="width: 35%;"></div></div>
- </div>
- </div>
- </div>
- </main>
- <!-- SIDE-BY-SIDE LIVE CARDS (4 COLUMNS GRID) -->
- <section class="side-by-side-section">
- <h2 class="section-title">Atmospheric Overview</h2>
- <div class="mini-cards-grid">
- <!-- SUNNY PREVIEW CARD -->
- <div class="glass-card mini-card theme-sun active" data-target="sun">
- <div class="mini-showcase">
- <div class="sunny-glow"></div>
- <div class="sun-orb-container">
- <div class="sun-core"></div>
- <!-- Mini Spoke design -->
- <svg class="sun-rays-inner" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg">
- <g stroke="#fef08a" stroke-width="3" stroke-linecap="round">
- <line x1="80" y1="20" x2="80" y2="35" />
- <line x1="80" y1="125" x2="80" y2="140" />
- <line x1="20" y1="80" x2="35" y2="80" />
- <line x1="125" y1="80" x2="140" y2="80" />
- </g>
- </svg>
- </div>
- <!-- Mini particles -->
- <div class="particle-container mini-particles" data-type="sun"></div>
- </div>
- <div class="mini-info">
- <span class="mini-label">Sunny</span>
- <div class="mini-flex">
- <span class="mini-desc">Clear skies</span>
- <span class="mini-temp">24°C</span>
- </div>
- </div>
- </div>
- <!-- WINDY PREVIEW CARD -->
- <div class="glass-card mini-card theme-wind" data-target="wind">
- <div class="mini-showcase">
- <div class="wind-glow"></div>
- <svg class="wind-vector-svg" viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg">
- <path class="wind-path" d="M15 35 Q65 15 115 40 T185 30" stroke-width="1.8" style="--speed: 2s; --delay: 0s;" />
- <path class="wind-path" d="M25 75 Q75 55 125 80 T185 70" stroke-width="1.5" style="--speed: 2.5s; --delay: -0.5s;" />
- </svg>
- <div class="tree-wrapper">
- <svg class="swaying-tree-svg" viewBox="0 0 100 130" xmlns="http://www.w3.org/2000/svg">
- <path d="M47 130 L48 95 Q48 90 52 85 L52 130 Z" fill="#2d3748" />
- <g class="tree-canopy-bot"><path d="M15 95 Q50 68 85 95 Q50 82 15 95 Z" fill="#0d9488" opacity="0.85" /></g>
- <g class="tree-canopy-mid"><path d="M22 72 Q50 48 78 72 Q50 60 22 72 Z" fill="#14b8a6" opacity="0.9" /></g>
- <g class="tree-canopy-top"><path d="M30 48 Q50 25 70 48 Q50 39 30 48 Z" fill="#2dd4bf" opacity="0.98" /></g>
- </svg>
- </div>
- <div class="particle-container mini-particles" data-type="wind"></div>
- </div>
- <div class="mini-info">
- <span class="mini-label">Windy</span>
- <div class="mini-flex">
- <span class="mini-desc">Fresh breeze</span>
- <span class="mini-temp">17°C</span>
- </div>
- </div>
- </div>
- <!-- RAINY PREVIEW CARD -->
- <div class="glass-card mini-card theme-rain" data-target="rain">
- <div class="mini-showcase">
- <div class="rainy-glow"></div>
- <div class="rain-container mini-particles" data-type="rain"></div>
- <div class="splash-container" id="miniRainSplashes"></div>
- </div>
- <div class="mini-info">
- <span class="mini-label">Rainy</span>
- <div class="mini-flex">
- <span class="mini-desc">Heavy showers</span>
- <span class="mini-temp">12°C</span>
- </div>
- </div>
- </div>
- <!-- SNOWY PREVIEW CARD -->
- <div class="glass-card mini-card theme-snow" data-target="snow">
- <div class="mini-showcase">
- <div class="snowy-glow"></div>
- <div class="snow-container mini-particles" data-type="snow"></div>
- <svg class="snow-drifts-svg" viewBox="0 0 200 40" xmlns="http://www.w3.org/2000/svg">
- <path class="snow-bank" d="M-10 40 L-10 25 Q50 15 110 28 T210 22 L210 40 Z" />
- </svg>
- </div>
- <div class="mini-info">
- <span class="mini-label">Snowy</span>
- <div class="mini-flex">
- <span class="mini-desc">Frosty drifts</span>
- <span class="mini-temp">-2°C</span>
- </div>
- </div>
- </div>
- </div>
- </section>
- </div>
- <!-- -------------------------------------------------------------
- 12. INTERACTIVE JAVASCRIPT CODE
- ------------------------------------------------------------- -->
- <script>
- // 1. Weather Theme Data Structure
- const weatherData = {
- sun: {
- themeColor: '#eab308',
- themeGlow: 'rgba(234, 179, 8, 0.15)',
- loc: 'St. Moritz, Switzerland',
- temp: '24',
- cond: 'Clear & Sunny',
- highlow: 'H: <span class="high">26°</span> L: 18°',
- label: 'Perfect Day',
- metrics: {
- wind: '6 km/h', windDesc: 'Gentle breeze • Northeast', windFill: 12,
- humid: '42%', humidDesc: 'Comfortable and crisp', humidFill: 42,
- uv: '8 (Very High)', uvDesc: 'High risk • Wear sunscreen', uvFill: 80,
- aqi: '32', aqiDesc: 'Excellent atmospheric purity', aqiFill: 32
- }
- },
- wind: {
- themeColor: '#2dd4bf',
- themeGlow: 'rgba(45, 212, 191, 0.12)',
- loc: 'Chicago, Illinois',
- temp: '17',
- cond: 'Gusty & Gale Forces',
- highlow: 'H: <span class="high">19°</span> L: 13°',
- label: 'Fresh Air',
- metrics: {
- wind: '38 km/h', windDesc: 'Strong gale • Northwest', windFill: 65,
- humid: '55%', humidDesc: 'Moderate moisture level', humidFill: 55,
- uv: '3 (Moderate)', uvDesc: 'Low danger • SPF 15 ok', uvFill: 30,
- aqi: '18', aqiDesc: 'Outstanding air freshness', aqiFill: 18
- }
- },
- rain: {
- themeColor: '#6366f1',
- themeGlow: 'rgba(99, 102, 241, 0.15)',
- loc: 'Bergen, Norway',
- temp: '12',
- cond: 'Heavy Downpour',
- highlow: 'H: <span class="high">14°</span> L: 9°',
- label: 'Storm Alert',
- metrics: {
- wind: '22 km/h', windDesc: 'Modest squalls • Southward', windFill: 35,
- humid: '94%', humidDesc: 'Extremely saturated atmosphere', humidFill: 94,
- uv: '1 (Low)', uvDesc: 'Negligible threat level', uvFill: 10,
- aqi: '42', aqiDesc: 'Very clean washed air quality', aqiFill: 42
- }
- },
- snow: {
- themeColor: '#38bdf8',
- themeGlow: 'rgba(56, 189, 248, 0.15)',
- loc: 'Sapporo, Hokkaido',
- temp: '-2',
- cond: 'Powdering & Snowdrift',
- highlow: 'H: <span class="high">0°</span> L: -6°',
- label: 'Frosty Cold',
- metrics: {
- wind: '14 km/h', windDesc: 'Chill drifting gust • North', windFill: 24,
- humid: '86%', humidDesc: 'Frosty and heavily frozen', humidFill: 86,
- uv: '0 (None)', uvDesc: 'No safety measure required', uvFill: 0,
- aqi: '22', aqiDesc: 'Pristine mountain conditions', aqiFill: 22
- }
- }
- };
- // 2. DOM Selectors
- const body = document.body;
- const ambientGlow = document.getElementById('ambientGlow');
- const heroCard = document.getElementById('heroCard');
- const switcherPill = document.getElementById('switcherPill');
- const switcherBtns = document.querySelectorAll('.switcher-btn');
- const miniCards = document.querySelectorAll('.mini-card');
- // Hero textual display nodes
- const locVal = document.getElementById('locVal');
- const tempVal = document.getElementById('tempVal');
- const condVal = document.getElementById('condVal');
- const highlowVal = document.getElementById('highlowVal');
- const labelVal = document.getElementById('labelVal');
- // Hero metrics nodes
- const windMetric = document.getElementById('windMetric');
- const windMetricDesc = document.getElementById('windMetricDesc');
- const windBar = document.getElementById('windBar');
- const humidMetric = document.getElementById('humidMetric');
- const humidMetricDesc = document.getElementById('humidMetricDesc');
- const humidBar = document.getElementById('humidBar');
- const uvMetric = document.getElementById('uvMetric');
- const uvMetricDesc = document.getElementById('uvMetricDesc');
- const uvBar = document.getElementById('uvBar');
- const aqiMetric = document.getElementById('aqiMetric');
- const aqiMetricDesc = document.getElementById('aqiMetricDesc');
- const aqiBar = document.getElementById('aqiBar');
- // 3. Dynamic Particle Generators
- function createDynamicParticles(container, type, count) {
- container.innerHTML = '';
- for (let i = 0; i < count; i++) {
- const particle = document.createElement('div');
- if (type === 'sun') {
- particle.className = 'solar-flare';
- particle.style.setProperty('--left', Math.random() * 100 + '%');
- particle.style.setProperty('--delay', Math.random() * -6 + 's');
- particle.style.setProperty('--speed', (Math.random() * 3 + 3) + 's');
- particle.style.setProperty('--drift', (Math.random() * 40 - 20) + 'px');
- particle.style.setProperty('--scale', Math.random() * 0.7 + 0.3);
- particle.style.setProperty('--opacity', Math.random() * 0.45 + 0.15);
- }
- else if (type === 'wind') {
- // Drifting leaves
- particle.className = 'drifting-leaf';
- // Draw leaf path
- particle.innerHTML = `<svg viewBox="0 0 24 24"><path d="M17 8c.98 0 1.86.3 2.58.82L17.4 11.2c-.88-.73-2.14-.95-3.23-.52a3.86 3.86 0 0 0-2.3 2.3c-.43 1.09-.2 2.35.53 3.23l-2.38 2.18c-.52-.72-.82-1.6-.82-2.58C9.2 11.48 12.68 8 17 8z" /></svg>`;
- particle.style.setProperty('--left', Math.random() * 85 + '%');
- particle.style.setProperty('--delay', Math.random() * -10 + 's');
- particle.style.setProperty('--speed', (Math.random() * 4 + 3.5) + 's');
- particle.style.setProperty('--drift-x', (Math.random() * 60 + 40) + 'px');
- }
- else if (type === 'rain') {
- particle.className = 'raindrop';
- particle.style.setProperty('--left', Math.random() * 100 + '%');
- particle.style.setProperty('--delay', Math.random() * -4 + 's');
- particle.style.setProperty('--speed', (Math.random() * 0.8 + 0.7) + 's');
- particle.style.setProperty('--length', (Math.random() * 15 + 18) + 'px');
- particle.style.setProperty('--opacity', Math.random() * 0.5 + 0.15);
- }
- else if (type === 'snow') {
- particle.className = 'snowflake';
- particle.style.setProperty('--left', Math.random() * 100 + '%');
- particle.style.setProperty('--delay', Math.random() * -10 + 's');
- particle.style.setProperty('--speed', (Math.random() * 2.5 + 2.5) + 's');
- particle.style.setProperty('--size', (Math.random() * 4.5 + 1.5) + 'px');
- particle.style.setProperty('--sway', (Math.random() * 50 - 25) + 'px');
- particle.style.setProperty('--opacity', Math.random() * 0.55 + 0.35);
- }
- container.appendChild(particle);
- }
- }
- // Puddle ripple generators
- function initSplashes(container, count) {
- container.innerHTML = '';
- for (let i = 0; i < count; i++) {
- const ripple = document.createElement('div');
- ripple.className = 'splash-ripple';
- ripple.style.setProperty('--left', Math.random() * 92 + 4 + '%');
- ripple.style.setProperty('--delay', Math.random() * -2 + 's');
- container.appendChild(ripple);
- }
- }
- // 4. Update the Active weather values inside the Hero Card
- function updateHeroData(weatherKey) {
- const data = weatherData[weatherKey];
- if (!data) return;
- // Apply transition animation class helper
- const updateNode = (node, value, isHTML = false) => {
- node.classList.remove('value-transition');
- void node.offsetWidth; // Trigger reflow to restart animation
- node.classList.add('value-transition');
- if (isHTML) {
- node.innerHTML = value;
- } else {
- node.textContent = value;
- }
- };
- // Set colors
- body.style.setProperty('--theme-color', data.themeColor);
- body.style.setProperty('--theme-glow', data.themeGlow);
- // Update text values
- updateNode(locVal, data.loc);
- updateNode(tempVal, data.temp);
- updateNode(condVal, data.cond);
- updateNode(highlowVal, data.highlow, true);
- updateNode(labelVal, data.label);
- // Add glow active for snowy, storm tags
- if (weatherKey === 'rain' || weatherKey === 'snow') {
- labelVal.className = "weather-extreme-label glow-active";
- } else {
- labelVal.className = "weather-extreme-label";
- }
- // Update Sub-Metrics
- updateNode(windMetric, data.metrics.wind);
- updateNode(windMetricDesc, data.metrics.windDesc);
- windBar.style.width = data.metrics.windFill + '%';
- updateNode(humidMetric, data.metrics.humid);
- updateNode(humidMetricDesc, data.metrics.humidDesc);
- humidBar.style.width = data.metrics.humidFill + '%';
- updateNode(uvMetric, data.metrics.uv);
- updateNode(uvMetricDesc, data.metrics.uvDesc);
- uvBar.style.width = data.metrics.uvFill + '%';
- updateNode(aqiMetric, data.metrics.aqi);
- updateNode(aqiMetricDesc, data.metrics.aqiDesc);
- aqiBar.style.width = data.metrics.aqiFill + '%';
- // Switch active animation layers on Hero Card
- const activeLayers = document.querySelectorAll('#heroShowcase .animation-layer');
- activeLayers.forEach(layer => {
- if (layer.dataset.weather === weatherKey) {
- layer.classList.add('active');
- } else {
- layer.classList.remove('active');
- }
- });
- }
- // 5. Align Selector Switch Pill with Active Button
- function alignSwitchPill(activeBtn) {
- switcherPill.style.left = activeBtn.offsetLeft + 'px';
- switcherPill.style.width = activeBtn.offsetWidth + 'px';
- }
- // 6. Primary State Machine Trigger
- function setWeatherState(weatherKey) {
- // 1. Update text & sub-stats
- updateHeroData(weatherKey);
- // 2. Synchronize Selector Buttons
- switcherBtns.forEach(btn => {
- if (btn.dataset.weather === weatherKey) {
- btn.classList.add('active');
- alignSwitchPill(btn);
- } else {
- btn.classList.remove('active');
- }
- });
- // 3. Synchronize Mini-Cards Row
- miniCards.forEach(card => {
- if (card.dataset.target === weatherKey) {
- card.classList.add('active');
- } else {
- card.classList.remove('active');
- }
- });
- }
- // 7. Event listeners
- switcherBtns.forEach(btn => {
- btn.addEventListener('click', (e) => {
- const weather = btn.dataset.weather;
- setWeatherState(weather);
- });
- });
- miniCards.forEach(card => {
- card.addEventListener('click', () => {
- const targetWeather = card.dataset.target;
- setWeatherState(targetWeather);
- });
- });
- // 8. Initialization Process
- window.addEventListener('DOMContentLoaded', () => {
- // Create particles in showcase layers
- const showcases = document.querySelectorAll('.showcase-particles');
- showcases.forEach(canvas => {
- const type = canvas.dataset.type;
- if (type === 'rain') createDynamicParticles(canvas, 'rain', 40);
- if (type === 'snow') createDynamicParticles(canvas, 'snow', 35);
- if (type === 'sun') createDynamicParticles(canvas, 'sun', 15);
- if (type === 'wind') createDynamicParticles(canvas, 'wind', 8);
- });
- // Create particles inside mini-cards
- const miniParticles = document.querySelectorAll('.mini-particles');
- miniParticles.forEach(canvas => {
- const type = canvas.dataset.type;
- if (type === 'rain') createDynamicParticles(canvas, 'rain', 15);
- if (type === 'snow') createDynamicParticles(canvas, 'snow', 15);
- if (type === 'sun') createDynamicParticles(canvas, 'sun', 5);
- if (type === 'wind') createDynamicParticles(canvas, 'wind', 3);
- });
- // Initialize floor splatters
- initSplashes(document.getElementById('heroSplashes'), 7);
- initSplashes(document.getElementById('miniRainSplashes'), 3);
- // Position the switch pill on the initial active element (Sunny)
- const initialActiveBtn = document.querySelector('.switcher-btn.active');
- if (initialActiveBtn) {
- // Wait a small bit to ensure accurate layout width measurements
- setTimeout(() => {
- alignSwitchPill(initialActiveBtn);
- }, 100);
- }
- // Responsive recalculation of pill offset
- window.addEventListener('resize', () => {
- const activeBtn = document.querySelector('.switcher-btn.active');
- if (activeBtn) alignSwitchPill(activeBtn);
- });
- });
- </script>
- </body>
- </html>
Advertisement