Skip to main content

Overview

SkyCast IA provides detailed hourly forecasts for the next 24 hours, including temperature trends, precipitation probability, and intelligent rain/snow alerts.

ForecastCard Component

The ForecastCard displays an 8-hour forecast with visual temperature charts:
import ForecastCard from '@/components/ui/ForecastCard';

<ForecastCard forecast={forecastData} />

Data Structure

Forecast data comes from OpenWeatherMap’s 5-day/3-hour forecast API:
interface ForecastItem {
  dt: number;                    // Unix timestamp
  main: {
    temp: number;                // Temperature in Celsius
    feels_like: number;          // Thermal sensation
    temp_min: number;
    temp_max: number;
    pressure: number;
    humidity: number;
  };
  weather: [
    {
      id: number;
      main: string;              // "Rain", "Snow", "Clear", etc.
      description: string;
      icon: string;              // Icon code (e.g., "01d")
    }
  ];
  pop: number;                   // Probability of precipitation (0-1)
  wind: {
    speed: number;
    deg: number;
  };
}

Precipitation Intelligence

The forecast card includes smart precipitation analysis:

Rain Detection Logic

const currentStatus = forecast[0];

// Check if it's raining now
const isRainingNow = 
  currentStatus.weather[0].main.toLowerCase().includes("rain") ||
  currentStatus.pop > 0.4;  // >40% probability

// Find next rain if not raining
const nextRain = forecast.find((item, i) => 
  i > 0 && item.pop > 0.3
);

// Find when rain stops if raining
const rainEnds = forecast.find((item, i) => 
  i > 0 && item.pop < 0.2
);

Display Examples

Status: Lloviendo ahora. Cesa a las 16:00
  • Shows rain cloud icon with pulse animation
  • Displays time when rain stops
  • Highlights affected hours in forecast

Temperature Chart

SkyCast uses Recharts to visualize temperature trends:
import { AreaChart, Area, ResponsiveContainer } from 'recharts';

const chartData = forecast.map((item) => ({
  temp: Math.round(item.main.temp)
}));

<ResponsiveContainer width="100%" height="100%">
  <AreaChart data={chartData} margin={{ left: 45, right: 45 }}>
    <defs>
      <linearGradient id="chartGrad" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#ffffff" stopOpacity={0.6} />
        <stop offset="100%" stopColor="#ffffff" stopOpacity={0} />
      </linearGradient>
    </defs>
    
    <Area
      type="natural"
      dataKey="temp"
      stroke="#ffffff"
      strokeWidth={2.5}
      fill="url(#chartGrad)"
      animationDuration={1200}
    />
  </AreaChart>
</ResponsiveContainer>

Chart Features

Smooth Curves

Natural cubic spline interpolation for smooth temperature transitions

Gradient Fill

Subtle gradient from solid to transparent

Glow Effect

SVG filter adds soft glow to line

Animated

1.2 second entrance animation

Hourly Forecast Items

Each hour displays:

Time Display

const hour = new Date(item.dt * 1000).getHours();

<span className="text-xs font-black">
  {hour}:00
</span>

Weather Icon

<img
  src={`https://openweathermap.org/img/wn/${item.weather[0].icon}@2x.png`}
  alt={item.weather[0].description}
  className="w-12 h-12"
/>
OpenWeatherMap provides day/night variants automatically (icon codes ending in ‘d’ or ‘n’)

Temperature

<span className="text-2xl font-black tracking-tighter">
  {Math.round(item.main.temp)}°
</span>

Precipitation Probability

<div className="flex items-center gap-1">
  <Droplets size={15} fill="currentColor" />
  <span className="text-xs">
    {Math.round(item.pop * 100)}%
  </span>
</div>

Fetching Forecast Data

Use the weather API to get forecast data:
import { getWeatherForecast } from '@/lib/api/weather';

// Fetch 5-day forecast
const forecastList = await getWeatherForecast(lat, lon);

// Take first 8 items (24 hours)
const hourlyForecast = forecastList.slice(0, 8);

<ForecastCard forecast={hourlyForecast} />

API Response

OpenWeather returns forecasts in 3-hour intervals:
{
  "list": [
    {
      "dt": 1678291200,
      "main": {
        "temp": 18.5,
        "feels_like": 17.8,
        "pressure": 1013,
        "humidity": 65
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "pop": 0.1,
      "wind": {
        "speed": 4.5,
        "deg": 180
      }
    }
  ]
}

Responsive Design

The forecast card adapts to screen sizes:

Desktop

  • Horizontal scrolling for 8+ hours
  • All items visible without scrolling
  • Chart spans full width

Mobile

  • Touch-friendly swipe scrolling
  • Larger touch targets (48x48px minimum)
  • Stacked layout for better readability
<div className="overflow-x-auto no-scrollbar">
  <div className="min-w-[850px]">
    {/* Forecast items */}
  </div>
</div>
The no-scrollbar class hides scrollbars while keeping scroll functionality

Adaptive Theming

Forecast styling changes based on weather:
const isSnow = forecast[0]?.weather[0]?.main?.toLowerCase() === "snow";

const bgClass = isSnow 
  ? "bg-black/5 border-black/10" 
  : "bg-white/10 border-white/10";

const textClass = isSnow 
  ? "text-slate-900" 
  : "text-white";

Theme Variants

WeatherBackgroundTextChart
SnowLight grayDarkBlack line
DefaultDark translucentWhiteWhite line
RainBlue tintWhiteBlue line

Usage Example

Complete forecast integration:
import { useState, useEffect } from 'react';
import ForecastCard from '@/components/ui/ForecastCard';
import { getWeatherForecast } from '@/lib/api/weather';

function WeatherForecast({ lat, lon }: { lat: number, lon: number }) {
  const [forecast, setForecast] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    getWeatherForecast(lat, lon)
      .then(data => {
        setForecast(data.slice(0, 8));  // Next 24 hours
        setLoading(false);
      })
      .catch(error => {
        console.error('Forecast error:', error);
        setLoading(false);
      });
  }, [lat, lon]);
  
  if (loading) return <Skeleton />;
  
  return <ForecastCard forecast={forecast} />;
}

Performance Optimization

Forecast fetches are debounced when coordinates change rapidly:
const [debouncedCoords] = useDebounce(coords, 1000);

useEffect(() => {
  if (debouncedCoords) {
    fetchForecast(debouncedCoords.lat, debouncedCoords.lon);
  }
}, [debouncedCoords]);
Chart data is memoized to prevent unnecessary recalculations:
const chartData = useMemo(
  () => forecast.map(item => ({ temp: Math.round(item.main.temp) })),
  [forecast]
);
Charts load only when visible using Intersection Observer:
const [isVisible, setIsVisible] = useState(false);
const chartRef = useRef(null);

useEffect(() => {
  const observer = new IntersectionObserver(
    ([entry]) => setIsVisible(entry.isIntersecting)
  );
  
  if (chartRef.current) observer.observe(chartRef.current);
  
  return () => observer.disconnect();
}, []);

Accessibility

  • Semantic HTML structure
  • ARIA labels on interactive elements
  • Keyboard navigation support
  • High contrast text
  • Screen reader announcements for precipitation changes

Build docs developers (and LLMs) love