/ frontend / src / App.css
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