App.css
1 /* CSS Variables for theming */ 2 :root { 3 --bg-gradient-start: #1a1a2e; 4 --bg-gradient-end: #16213e; 5 --card-bg: rgba(30, 30, 46, 0.95); 6 --card-hover-shadow: rgba(0, 0, 0, 0.3); 7 --text-primary: #ffffff; 8 --text-secondary: #e2e8f0; 9 --text-tertiary: #cbd5e0; 10 --accent-gradient-start: #7c92f5; 11 --accent-gradient-end: #8b5ecf; 12 --weather-card-bg: rgba(45, 45, 60, 0.8); 13 --weather-card-border: rgba(255, 255, 255, 0.1); 14 --section-title-color: #f7fafc; 15 --date-color: #cbd5e0; 16 --summary-color: #f7fafc; 17 --temp-unit-color: #e2e8f0; 18 --divider-color: #4a5568; 19 --error-bg: rgba(220, 38, 38, 0.1); 20 --error-border: #ef4444; 21 --error-text: #fca5a5; 22 --skeleton-bg-1: rgba(255, 255, 255, 0.05); 23 --skeleton-bg-2: rgba(255, 255, 255, 0.1); 24 --tile-min-height: 120px; 25 --tile-gap: 0.75rem; 26 --cta-height: 3rem; 27 --card-inner-gap: 1rem; 28 --focus-color: #a78bfa; 29 } 30 31 @media (prefers-color-scheme: light) { 32 :root { 33 --bg-gradient-start: #f0f4ff; 34 --bg-gradient-end: #e0e7ff; 35 --card-bg: rgba(255, 255, 255, 0.98); 36 --card-hover-shadow: rgba(0, 0, 0, 0.15); 37 --text-primary: #1a202c; 38 --text-secondary: #2d3748; 39 --text-tertiary: #4a5568; 40 --accent-gradient-start: #5b6fd8; 41 --accent-gradient-end: #6b46a3; 42 --weather-card-bg: rgba(255, 255, 255, 0.9); 43 --weather-card-border: rgba(102, 126, 234, 0.15); 44 --section-title-color: #1a202c; 45 --date-color: #2d3748; 46 --summary-color: #1a202c; 47 --temp-unit-color: #4a5568; 48 --divider-color: #cbd5e0; 49 --error-bg: #fee; 50 --error-border: #dc2626; 51 --error-text: #991b1b; 52 --skeleton-bg-1: #f0f0f0; 53 --skeleton-bg-2: #e0e0e0; 54 --tile-min-height: 120px; 55 --tile-gap: 0.75rem; 56 --cta-height: 3rem; 57 --card-inner-gap: 1rem; 58 --focus-color: #5b21b6; 59 } 60 } 61 62 /* Root container */ 63 #root { 64 width: 100%; 65 min-height: 100vh; 66 display: flex; 67 flex-direction: column; 68 overflow-x: hidden; 69 } 70 71 /* Accessibility utilities */ 72 .visually-hidden { 73 position: absolute; 74 width: 1px; 75 height: 1px; 76 padding: 0; 77 margin: -1px; 78 overflow: hidden; 79 clip: rect(0, 0, 0, 0); 80 white-space: nowrap; 81 border-width: 0; 82 } 83 84 /* App container */ 85 .app-container { 86 width: 100%; 87 min-height: 100vh; 88 display: flex; 89 flex-direction: column; 90 background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); 91 color: var(--text-primary); 92 } 93 94 /* Header */ 95 .app-header { 96 padding: 2.5rem 2rem 1.5rem; 97 text-align: center; 98 animation: fadeInDown 0.6s ease-out; 99 } 100 101 .logo-link { 102 display: inline-block; 103 border-radius: 0.5rem; 104 } 105 106 .logo-link:focus-visible { 107 outline: 3px solid var(--accent-gradient-end); 108 outline-offset: 8px; 109 } 110 111 .logo { 112 height: 5rem; 113 width: auto; 114 transition: transform 300ms ease, filter 300ms ease; 115 filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2)); 116 } 117 118 .logo:hover { 119 transform: scale(1.1) rotate(5deg); 120 filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.3)); 121 } 122 123 .app-title { 124 font-size: 2.75rem; 125 font-weight: 700; 126 margin: 1.25rem 0 0.5rem; 127 background: linear-gradient(135deg, var(--text-primary) 0%, var(--text-secondary) 100%); 128 -webkit-background-clip: text; 129 -webkit-text-fill-color: transparent; 130 background-clip: text; 131 letter-spacing: -0.02em; 132 } 133 134 .app-subtitle { 135 font-size: 1.05rem; 136 color: var(--text-tertiary); 137 margin: 0; 138 font-weight: 300; 139 } 140 141 /* Main content */ 142 .main-content { 143 flex: 1; 144 max-width: 1400px; 145 width: 100%; 146 margin: 0 auto; 147 padding: 0rem 2rem 2rem; 148 display: flex; 149 justify-content: center; 150 align-items: center; 151 } 152 153 /* Card styles */ 154 .card { 155 background: var(--card-bg); 156 backdrop-filter: blur(10px); 157 border-radius: 1rem; 158 padding: 1.25rem 1.5rem; 159 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 160 color: var(--text-primary); 161 animation: fadeInUp 0.6s ease-out; 162 border: 1px solid var(--weather-card-border); 163 display: flex; 164 flex-direction: column; 165 gap: var(--card-inner-gap); 166 } 167 168 /* Section styles */ 169 .demo-section { 170 animation: fadeInUp 0.6s ease-out; 171 } 172 173 .weather-section { 174 animation: fadeInUp 0.6s ease-out; 175 animation-delay: 0.1s; 176 flex: 1; 177 max-width: 1200px; 178 width: 100%; 179 } 180 181 .section-header { 182 display: flex; 183 justify-content: space-between; 184 align-items: flex-start; 185 margin-bottom: 0; 186 flex-wrap: wrap; 187 gap: 1rem; 188 } 189 190 .header-actions { 191 display: flex; 192 gap: 0.75rem; 193 align-items: center; 194 } 195 196 .section-title { 197 font-size: 1.25rem; 198 font-weight: 600; 199 margin: 0; 200 color: var(--section-title-color); 201 } 202 203 /* Counter area */ 204 .counter-card .section-header { 205 margin-bottom: 0; 206 } 207 208 .counter-panel { 209 background: var(--weather-card-bg); 210 border-radius: 0.75rem; 211 border: 1px solid var(--weather-card-border); 212 padding: 1.25rem; 213 display: flex; 214 flex-direction: column; 215 gap: var(--tile-gap); 216 min-height: var(--tile-min-height); 217 backdrop-filter: blur(10px); 218 flex: 1; 219 } 220 221 .counter-value-group { 222 display: flex; 223 flex-direction: column; 224 align-items: center; 225 justify-content: center; 226 gap: 0.25rem; 227 flex: 1; 228 margin-bottom: var(--tile-gap); 229 text-align: center; 230 } 231 232 .counter-label { 233 font-size: 0.75rem; 234 text-transform: uppercase; 235 letter-spacing: 0.1em; 236 color: var(--text-tertiary); 237 font-weight: 600; 238 } 239 240 .counter-value { 241 font-size: 2.25rem; 242 font-weight: 700; 243 line-height: 1; 244 background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); 245 -webkit-background-clip: text; 246 -webkit-text-fill-color: transparent; 247 background-clip: text; 248 } 249 250 .increment-button { 251 display: flex; 252 align-items: center; 253 justify-content: center; 254 gap: 0.5rem; 255 background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); 256 color: white; 257 border: none; 258 border-radius: 0.5rem; 259 padding: 0 1.5rem; 260 height: var(--cta-height); 261 font-size: 0.875rem; 262 font-weight: 600; 263 cursor: pointer; 264 transition: all 0.3s ease; 265 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); 266 width: 100%; 267 margin-top: auto; 268 } 269 270 .increment-button:hover { 271 transform: translateY(-1px); 272 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); 273 } 274 275 .increment-button:active { 276 transform: translateY(0); 277 } 278 279 .increment-button:focus-visible { 280 outline: 3px solid var(--accent-gradient-end); 281 outline-offset: 2px; 282 } 283 284 .increment-icon { 285 transition: transform 0.3s ease; 286 } 287 288 .increment-button:hover .increment-icon { 289 transform: scale(1.1); 290 } 291 292 /* Toggle switch */ 293 .toggle-switch { 294 display: flex; 295 background: rgba(255, 255, 255, 0.1); 296 border-radius: 0.5rem; 297 padding: 0.25rem; 298 gap: 0.25rem; 299 border: 1px solid var(--weather-card-border); 300 margin: 0; 301 padding: 0.25rem; 302 min-width: 0; 303 } 304 305 .toggle-switch legend { 306 padding: 0; 307 } 308 309 @media (prefers-color-scheme: light) { 310 .toggle-switch { 311 background: rgba(102, 126, 234, 0.08); 312 } 313 } 314 315 .toggle-option { 316 display: flex; 317 align-items: center; 318 justify-content: center; 319 background: transparent; 320 color: var(--text-secondary); 321 border: none; 322 border-radius: 0.375rem; 323 padding: 0 1rem; 324 height: 2.5rem; 325 font-size: 0.875rem; 326 font-weight: 600; 327 cursor: pointer; 328 transition: all 0.3s ease; 329 min-width: 3rem; 330 position: relative; 331 } 332 333 .toggle-option[aria-pressed="true"] { 334 background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); 335 color: white; 336 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); 337 } 338 339 .toggle-option[aria-pressed="false"]:hover { 340 background: rgba(255, 255, 255, 0.05); 341 color: var(--text-primary); 342 } 343 344 @media (prefers-color-scheme: light) { 345 .toggle-option[aria-pressed="false"]:hover { 346 background: rgba(102, 126, 234, 0.1); 347 } 348 } 349 350 .toggle-option:focus-visible { 351 outline: 3px solid var(--focus-color); 352 outline-offset: 2px; 353 z-index: 1; 354 } 355 356 /* Refresh button */ 357 .refresh-button { 358 display: flex; 359 align-items: center; 360 justify-content: center; 361 gap: 0.5rem; 362 background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); 363 color: white; 364 border: none; 365 border-radius: 0.5rem; 366 padding: 0 1.5rem; 367 height: var(--cta-height); 368 font-size: 0.875rem; 369 font-weight: 600; 370 cursor: pointer; 371 transition: transform 0.3s ease, box-shadow 0.3s ease, opacity 0.3s ease; 372 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); 373 min-width: 140px; 374 white-space: nowrap; 375 } 376 377 .refresh-button:hover:not(:disabled) { 378 transform: translateY(-1px); 379 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); 380 } 381 382 .refresh-button:disabled { 383 opacity: 0.6; 384 cursor: not-allowed; 385 transform: none; 386 } 387 388 .refresh-button:focus-visible { 389 outline: 3px solid var(--focus-color); 390 outline-offset: 3px; 391 } 392 393 .refresh-icon { 394 transition: transform 0.3s ease; 395 } 396 397 .refresh-icon.spinning { 398 animation: spin 1s linear infinite; 399 } 400 401 /* Error message */ 402 .error-message { 403 display: flex; 404 align-items: center; 405 gap: 0.75rem; 406 background-color: var(--error-bg); 407 border-left: 4px solid var(--error-border); 408 color: var(--error-text); 409 padding: 1rem; 410 border-radius: 0.5rem; 411 margin: 1rem 0; 412 animation: slideIn 0.3s ease-out; 413 } 414 415 /* Loading skeleton */ 416 .loading-skeleton { 417 display: flex; 418 flex-direction: column; 419 gap: 1rem; 420 margin-top: 1rem; 421 } 422 423 .skeleton-row { 424 height: 80px; 425 background: linear-gradient(90deg, var(--skeleton-bg-1) 25%, var(--skeleton-bg-2) 50%, var(--skeleton-bg-1) 75%); 426 background-size: 200% 100%; 427 animation: shimmer 1.5s infinite; 428 border-radius: 0.5rem; 429 } 430 431 /* Weather grid */ 432 .weather-grid { 433 display: grid; 434 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); 435 gap: 1rem; 436 margin-top: 0.75rem; 437 } 438 439 .weather-card { 440 background: var(--weather-card-bg); 441 border-radius: 0.75rem; 442 padding: 1.25rem; 443 display: flex; 444 flex-direction: column; 445 gap: 0.75rem; 446 transition: all 0.3s ease; 447 border: 1px solid var(--weather-card-border); 448 backdrop-filter: blur(10px); 449 min-height: var(--tile-min-height); 450 } 451 452 .weather-card:hover { 453 transform: translateY(-4px); 454 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); 455 } 456 457 .weather-card:focus-within { 458 outline: 2px solid var(--focus-color); 459 outline-offset: 2px; 460 } 461 462 .weather-date { 463 font-weight: 600; 464 font-size: 0.875rem; 465 color: var(--date-color); 466 text-transform: uppercase; 467 letter-spacing: 0.05em; 468 margin: 0; 469 } 470 471 .weather-summary { 472 font-size: 1.125rem; 473 font-weight: 500; 474 color: var(--summary-color); 475 min-height: 1.5rem; 476 margin: 0; 477 } 478 479 .weather-temps { 480 display: flex; 481 align-items: center; 482 justify-content: center; 483 gap: 0.75rem; 484 margin-top: 0.5rem; 485 padding-top: 0.75rem; 486 border-top: 1px solid var(--weather-card-border); 487 } 488 489 .temp-group { 490 display: flex; 491 flex-direction: column; 492 align-items: center; 493 width: 100%; 494 } 495 496 .temp-value { 497 font-size: 1.5rem; 498 font-weight: 700; 499 background: linear-gradient(135deg, var(--accent-gradient-start) 0%, var(--accent-gradient-end) 100%); 500 -webkit-background-clip: text; 501 -webkit-text-fill-color: transparent; 502 background-clip: text; 503 } 504 505 .temp-unit { 506 font-size: 0.75rem; 507 color: var(--temp-unit-color); 508 margin-top: 0.125rem; 509 } 510 511 /* Responsive design */ 512 @media (max-width: 1024px) { 513 .main-content { 514 padding: 1rem; 515 } 516 } 517 .app-footer { 518 padding: 1.5rem; 519 text-align: center; 520 background: rgba(0, 0, 0, 0.2); 521 backdrop-filter: blur(10px); 522 } 523 524 .app-footer nav { 525 display: flex; 526 justify-content: center; 527 align-items: center; 528 gap: 1.5rem; 529 } 530 531 .app-footer a { 532 color: var(--text-secondary); 533 text-decoration: none; 534 font-weight: 500; 535 transition: color 0.3s ease, border-color 0.3s ease; 536 border-bottom: 2px solid transparent; 537 font-size: 0.875rem; 538 padding-bottom: 0.125rem; 539 } 540 541 .app-footer a:hover { 542 color: var(--text-primary); 543 border-bottom-color: var(--text-primary); 544 } 545 546 .app-footer a:focus-visible { 547 outline: 3px solid var(--focus-color); 548 outline-offset: 4px; 549 border-radius: 4px; 550 } 551 552 /* Animations */ 553 @keyframes fadeInDown { 554 from { 555 opacity: 0; 556 transform: translateY(-20px); 557 } 558 to { 559 opacity: 1; 560 transform: translateY(0); 561 } 562 } 563 564 @keyframes fadeInUp { 565 from { 566 opacity: 0; 567 transform: translateY(20px); 568 } 569 to { 570 opacity: 1; 571 transform: translateY(0); 572 } 573 } 574 575 @keyframes slideIn { 576 from { 577 opacity: 0; 578 transform: translateX(-10px); 579 } 580 to { 581 opacity: 1; 582 transform: translateX(0); 583 } 584 } 585 586 @keyframes spin { 587 from { 588 transform: rotate(0deg); 589 } 590 to { 591 transform: rotate(360deg); 592 } 593 } 594 595 @keyframes shimmer { 596 0% { 597 background-position: -200% 0; 598 } 599 100% { 600 background-position: 200% 0; 601 } 602 } 603 604 /* Responsive design */ 605 @media (max-width: 1024px) { 606 .main-content { 607 grid-template-columns: 1fr; 608 padding: 1rem; 609 gap: 1rem; 610 } 611 } 612 613 /* Footer */ 614 .app-footer { 615 padding: 1.5rem 0; 616 text-align: center; 617 background: rgba(0, 0, 0, 0.2); 618 backdrop-filter: blur(10px); 619 display: flex; 620 justify-content: center; 621 align-items: center; 622 } 623 624 .app-footer > * { 625 max-width: 1400px; 626 width: 100%; 627 display: flex; 628 justify-content: space-between; 629 align-items: center; 630 padding: 0 2rem; 631 gap: 1rem; 632 } 633 634 .app-footer a { 635 color: var(--text-secondary); 636 text-decoration: none; 637 font-weight: 500; 638 transition: color 0.3s ease, transform 0.3s ease; 639 border-bottom: 2px solid transparent; 640 font-size: 0.875rem; 641 } 642 643 .app-footer a:hover { 644 color: var(--text-primary); 645 border-bottom-color: var(--text-primary); 646 } 647 648 .app-footer a:focus-visible { 649 outline: 2px solid var(--text-primary); 650 outline-offset: 4px; 651 border-radius: 2px; 652 } 653 654 .github-link { 655 display: inline-flex; 656 align-items: center; 657 gap: 0.5rem; 658 border-bottom: none !important; 659 } 660 661 .github-link:focus-visible { 662 outline: 3px solid var(--focus-color); 663 outline-offset: 4px; 664 border-radius: 4px; 665 } 666 667 .github-link img { 668 transition: transform 0.3s ease, opacity 0.3s ease; 669 filter: brightness(0) invert(1); 670 } 671 672 @media (prefers-color-scheme: light) { 673 .github-link img { 674 filter: brightness(0) invert(0); 675 opacity: 0.7; 676 } 677 678 .github-link:hover img { 679 opacity: 1; 680 } 681 } 682 683 .github-link:hover img { 684 transform: scale(1.1); 685 } 686 687 @media (max-width: 768px) { 688 :root { 689 --cta-height: 2.75rem; 690 } 691 692 .app-header { 693 padding: 1.5rem 1rem 1rem; 694 } 695 696 .logo { 697 height: 3rem; 698 } 699 700 .app-title { 701 font-size: 1.5rem; 702 } 703 704 .app-subtitle { 705 font-size: 0.875rem; 706 } 707 708 .main-content { 709 padding: 0.75rem; 710 } 711 712 .card { 713 padding: 1rem; 714 } 715 716 .section-title { 717 font-size: 1.125rem; 718 } 719 720 .section-header { 721 flex-direction: column; 722 align-items: stretch; 723 gap: 0.75rem; 724 } 725 726 .header-actions { 727 width: 100%; 728 } 729 730 .toggle-switch { 731 flex: 1; 732 } 733 734 .toggle-option { 735 flex: 1; 736 } 737 738 .refresh-button { 739 flex: 1; 740 justify-content: center; 741 padding: 0 1.25rem; 742 } 743 744 .weather-grid { 745 grid-template-columns: 1fr; 746 gap: 0.75rem; 747 } 748 749 .weather-card { 750 padding: 1.25rem; 751 } 752 753 .app-footer { 754 padding: 1rem 0; 755 } 756 757 .app-footer > * { 758 flex-direction: column; 759 padding: 0 1.5rem; 760 } 761 762 .github-link { 763 order: -1; 764 } 765 } 766 767 /* Reduced motion support */ 768 @media (prefers-reduced-motion: reduce) { 769 *, 770 *::before, 771 *::after { 772 animation-duration: 0.01ms !important; 773 animation-iteration-count: 1 !important; 774 transition-duration: 0.01ms !important; 775 } 776 } 777 778 /* High contrast mode support */ 779 @media (prefers-contrast: high) { 780 .card { 781 border: 2px solid currentColor; 782 } 783 784 .weather-card { 785 border: 1px solid currentColor; 786 } 787 } 788 789 /* Focus visible support for better keyboard navigation */ 790 *:focus-visible { 791 outline: 3px solid var(--accent-gradient-end); 792 outline-offset: 2px; 793 } 794