OFI
Oficina virtual en 3D para equipos remotos: avatares, un espacio que diseñan juntos y un workspace para el trabajo diario.
Ver Demo | Código Fuente | Descargar
En este artículo, voy a presentarles ClockTab, una aplicación web moderna y elegante que he desarrollado como parte de mi exploración en tecnologías frontend. La aplicación combina un reloj digital con información meteorológica en tiempo real, todo ello en una interfaz visual atractiva y adaptable a cualquier dispositivo.
ClockTab nació de una necesidad personal: quería una página de inicio minimalista pero funcional que me mostrara la información que consulto constantemente (hora, fecha y clima) sin distracciones. Lo que comenzó como un pequeño proyecto para practicar React, se transformó en una aplicación completa con múltiples funcionalidades y un cuidado diseño visual.
ClockTab es una aplicación web que combina:
Para desarrollar ClockTab he utilizado diversas tecnologías modernas:
Una de las partes más interesantes del código es cómo la aplicación detecta y se adapta a la orientación del dispositivo para ofrecer la mejor experiencia de usuario:
function App() {
// Detectar si el dispositivo está en modo horizontal o vertical
const [isLandscape, setIsLandscape] = useState(window.innerWidth > window.innerHeight);
// Detectar cambios en la orientación
useEffect(() => {
const handleResize = () => {
setIsLandscape(window.innerWidth > window.innerHeight);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// En el render, usamos el estado para mostrar layouts diferentes
return (
<BackgroundManager timeOfDay={timeOfDay} theme={theme}>
<div className="min-h-screen w-full p-4 md:p-6 flex flex-col">
{isLandscape ? (
// Layout optimizado para orientación horizontal
<div className="h-full grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Contenido específico para landscape */}
</div>
) : (
// Layout optimizado para orientación vertical
<div className="flex flex-col items-center justify-between h-full gap-6">
{/* Contenido específico para portrait */}
</div>
)}
</div>
</BackgroundManager>
);
}
Otro aspecto fascinante es cómo la aplicación cambia su fondo según la hora del día y las condiciones climáticas actuales:
const getBackgroundImage = () => {
// Fondos según hora del día
const timeBackgrounds = {
morning: '/backgrounds/day.webp',
day: '/backgrounds/day.webp',
sunset: '/backgrounds/day.webp',
night: '/backgrounds/night.webp'
};
// Si hay información de clima, la combinamos con la hora
if (currentWeather) {
if (['rain', 'snow', 'fog'].includes(currentWeather)) {
return '/backgrounds/cloudy.webp';
}
if (['sunny', 'partly_sunny', 'partly_clear'].includes(currentWeather)) {
return timeOfDay === 'night' ? '/backgrounds/night.webp' : '/backgrounds/day.webp';
}
}
// Fallback a solo hora del día
return timeBackgrounds[timeOfDay];
};
El componente del reloj digital es simple pero efectivo, actualizándose cada segundo y formateando la hora de manera atractiva:
const DigitalClock = ({ className = '' }) => {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
const formatTime = (date) => {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return { hours, minutes, seconds };
};
const { hours, minutes, seconds } = formatTime(time);
return (
<div className={`glass-morphism rounded-2xl p-4 md:p-5 shadow-xl ${className}`}>
<div className="flex items-center justify-center">
<div className="font-clock text-5xl md:text-6xl lg:text-7xl font-bold text-center flex items-center text-white drop-shadow-lg">
<div className="flex-shrink-0 w-auto">
<span className="inline-block min-w-[2ch] text-center">{hours}</span>
</div>
<div className="flex-shrink-0 w-auto px-1 md:px-2 text-primary">
<span className="inline-block">:</span>
</div>
<div className="flex-shrink-0 w-auto">
<span className="inline-block min-w-[2ch] text-center">{minutes}</span>
</div>
<div className="flex-shrink-0 w-auto self-start ml-1 md:ml-2 mt-2">
<span className="text-2xl md:text-3xl lg:text-4xl text-secondary opacity-80">{seconds}</span>
</div>
</div>
</div>
</div>
);
};
Para evitar hacer demasiadas peticiones a la API de clima, implementé un sistema de caché que guarda los datos durante 10 minutos:
// Caché para los datos del clima
const weatherCache = {
data: null,
timestamp: 0,
TTL: 10 * 60 * 1000 // 10 minutos en milisegundos
};
// Función para obtener datos con caché
export const fetchWeatherWithCache = async () => {
const now = Date.now();
// Si tenemos datos en caché y no han expirado, usarlos
if (weatherCache.data && now - weatherCache.timestamp < weatherCache.TTL) {
return weatherCache.data;
}
// Si no hay caché o expiró, hacer la petición
try {
// Obtener configuración desde localStorage
const apiKey = localStorage.getItem('weatherApiKey') || '';
const placeId = localStorage.getItem('weatherPlaceId') || '';
// Construir URL con parámetros
let url = 'https://devline.app/clock/api/weather.php';
// Si hay configuración personalizada, usar endpoint v02
if (apiKey && placeId) {
url = `https://devline.app/clock/api/weather-v02.php?api_key=${encodeURIComponent(apiKey)}&place_id=${encodeURIComponent(placeId)}`;
}
const response = await fetch(url);
const data = await response.json();
// Actualizar caché
weatherCache.data = data;
weatherCache.timestamp = now;
return data;
} catch (error) {
throw error;
}
};
Un detalle interesante es el cálculo y visualización del progreso del año:
const calculateYearProgress = () => {
const dayOfYear = getDayOfYear(currentDate);
const isLeapYear = new Date(currentDate.getFullYear(), 1, 29).getMonth() === 1;
const totalDays = isLeapYear ? 366 : 365;
return (dayOfYear / totalDays) * 100;
};
// En el renderizado mostramos una barra de progreso
<div className="w-full bg-gray-300 bg-opacity-20 rounded-full h-2.5">
<div
className="bg-gradient-to-r from-secondary to-primary h-2.5 rounded-full"
style={{ width: `${yearProgress}%` }}
></div>
</div>
<div className="text-xs text-right mt-1 text-gray-200">{Math.round(yearProgress)}% del año</div>
La forma más sencilla es acceder directamente a clock-tab.cubitdev.com desde cualquier navegador. Puedes:
Si prefieres tener tu propia instancia, puedes descargar e instalar el código:
git clone https://github.com/cubitdev/clock-tab.gitnpm installnpm run devSi quieres usar ClockTab como base para tu propio proyecto:
ClockTab permite personalizar la API de clima para mostrar información de tu ciudad:
Durante el desarrollo de ClockTab, aprendí varias lecciones importantes:
Diseño responsivo avanzado: Más allá del típico enfoque mobile-first, aprendí a crear diferentes layouts basados en la orientación del dispositivo.
Optimización de peticiones API: Implementar un sistema de caché para reducir las llamadas a la API, respetando los límites de uso y mejorando el rendimiento.
Efectos visuales con CSS moderno: El uso de glass-morphism, animaciones sutiles y transiciones suaves mejoran enormemente la experiencia de usuario sin sobrecargar la interfaz.
Service Workers y PWA: Configurar una aplicación para que funcione offline y sea instalable como PWA, lo que mejora la retención de usuarios.
Gestión de estado en React: Uso efectivo de useState y useEffect para manejar diferentes aspectos de la aplicación de manera clara y organizada.
Tengo planificadas varias mejoras para futuras versiones:
¡Las contribuciones son bienvenidas! Si quieres mejorar ClockTab:
git checkout -b feature/nueva-funciongit commit -m 'Añade nueva función'También puedes:
ClockTab es un proyecto que demuestra cómo se pueden crear aplicaciones web modernas, atractivas y útiles utilizando tecnologías frontend actuales. Aunque es un proyecto relativamente simple, incorpora muchas buenas prácticas de desarrollo web y ofrece una experiencia de usuario pulida.
Te invito a probar la aplicación en clock-tab.cubitdev.com, explorar el código en GitHub y adaptarlo para tus propios proyectos.
¿Has creado alguna vez una aplicación web similar? ¿Qué otras funcionalidades te gustaría ver en ClockTab? ¡Déjame tus comentarios!
Autor: [Tu nombre] de CubitDev
Fecha de publicación: [Fecha]
Oficina virtual en 3D para equipos remotos: avatares, un espacio que diseñan juntos y un workspace para el trabajo diario.
Shooter espacial en 3D con estética neón poligonal: guía a NYX y a la flota Éxodo hasta Auriga.
Juego casual de reflejos con estética neón, nacido en una habitación de hospital.