timetimer / index.html
butztub's picture
Add 2 files
694b02a verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TimeTask - Gestor de Tareas Pomodoro</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
transition: background-color 0.3s, color 0.3s;
}
.dark-mode {
background-color: #1a1a1a;
color: #f8f9fa;
}
.dark-mode .card, .dark-mode .modal-content, .dark-mode .dropdown-menu {
background-color: #2d2d2d;
color: #f8f9fa;
border-color: #444;
}
.dark-mode .form-control, .dark-mode .form-select {
background-color: #3d3d3d;
color: #f8f9fa;
border-color: #555;
}
.task-card {
transition: transform 0.2s;
cursor: pointer;
}
.task-card:hover {
transform: translateY(-5px);
}
.priority-low {
border-left: 5px solid #28a745;
}
.priority-medium {
border-left: 5px solid #ffc107;
}
.priority-high {
border-left: 5px solid #dc3545;
}
.timeline {
position: relative;
padding-left: 50px;
}
.timeline::before {
content: '';
position: absolute;
left: 25px;
top: 0;
bottom: 0;
width: 2px;
background-color: #007bff;
}
.timeline-item {
position: relative;
margin-bottom: 20px;
}
.timeline-item::before {
content: '';
position: absolute;
left: -35px;
top: 10px;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #007bff;
border: 3px solid #fff;
}
.current-time {
position: absolute;
left: 0;
right: 0;
height: 2px;
background-color: red;
z-index: 100;
}
.calendar-day {
height: 100px;
border: 1px solid #dee2e6;
padding: 5px;
overflow-y: auto;
}
.calendar-day.today {
background-color: rgba(0, 123, 255, 0.1);
}
.calendar-day-header {
font-weight: bold;
margin-bottom: 5px;
}
.task-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}
.pomodoro-timer {
font-size: 3rem;
font-weight: bold;
text-align: center;
margin: 20px 0;
}
.pomodoro-btn {
width: 100px;
margin: 0 5px;
}
.pomodoro-session {
font-size: 1.2rem;
text-align: center;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container-fluid">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
<div class="container-fluid">
<a class="navbar-brand" href="#">TimeTask</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="#" onclick="showView('dashboard')">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showView('tasks')">Tareas</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showView('timeline')">Línea de Tiempo</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showView('day')">Día</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showView('week')">Semana</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showView('calendar')">Calendario</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showView('metrics')">Métricas</a>
</li>
</ul>
<div class="d-flex align-items-center">
<div class="me-3 text-white" id="current-time"></div>
<div class="form-check form-switch me-3">
<input class="form-check-input" type="checkbox" id="darkModeSwitch">
<label class="form-check-label text-white" for="darkModeSwitch">Modo Oscuro</label>
</div>
<div class="dropdown">
<button class="btn btn-outline-light dropdown-toggle" type="button" id="profileDropdown" data-bs-toggle="dropdown">
Perfil
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#" onclick="showView('profile')">Configuración</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="logout()">Cerrar Sesión</a></li>
</ul>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<div id="main-content">
<!-- Dashboard View -->
<div id="dashboard-view" class="view">
<div class="row">
<div class="col-md-8">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Pomodoro Timer</h5>
<button class="btn btn-sm btn-primary" onclick="showView('tasks')">Ver Tareas</button>
</div>
<div class="card-body">
<div class="pomodoro-timer" id="pomodoro-timer">25:00</div>
<div class="pomodoro-session" id="pomodoro-session">Sesión de Trabajo</div>
<div class="d-flex justify-content-center">
<button class="btn btn-success pomodoro-btn" id="start-btn" onclick="startPomodoro()">Iniciar</button>
<button class="btn btn-danger pomodoro-btn" id="stop-btn" onclick="stopPomodoro()" disabled>Detener</button>
<button class="btn btn-warning pomodoro-btn" id="reset-btn" onclick="resetPomodoro()">Reiniciar</button>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Tareas de Hoy</h5>
</div>
<div class="card-body" id="today-tasks">
<!-- Tasks will be loaded here -->
</div>
</div>
</div>
<div class="col-md-4">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Agregar Tarea Rápida</h5>
</div>
<div class="card-body">
<form id="quick-task-form">
<div class="mb-3">
<input type="text" class="form-control" id="quick-task-name" placeholder="Nombre de la tarea" required>
</div>
<div class="mb-3">
<select class="form-select" id="quick-task-priority">
<option value="low">Baja</option>
<option value="medium">Media</option>
<option value="high">Alta</option>
</select>
</div>
<button type="submit" class="btn btn-primary w-100">Agregar</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0">Estadísticas</h5>
</div>
<div class="card-body">
<div class="mb-3">
<h6>Tareas completadas hoy: <span id="completed-today">0</span></h6>
</div>
<div class="mb-3">
<h6>Tiempo trabajado hoy: <span id="time-worked">0h 0m</span></h6>
</div>
<div class="mb-3">
<h6>Sesiones Pomodoro: <span id="pomodoro-sessions">0</span></h6>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tasks View -->
<div id="tasks-view" class="view" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Tareas</h2>
<button class="btn btn-primary" onclick="showTaskModal()">Nueva Tarea</button>
</div>
<div class="row mb-3">
<div class="col-md-4">
<select class="form-select" id="task-sort">
<option value="date">Ordenar por: Fecha</option>
<option value="priority">Ordenar por: Prioridad</option>
<option value="category">Ordenar por: Categoría</option>
</select>
</div>
<div class="col-md-4">
<select class="form-select" id="task-filter">
<option value="all">Todas las tareas</option>
<option value="today">Hoy</option>
<option value="week">Esta semana</option>
<option value="completed">Completadas</option>
<option value="pending">Pendientes</option>
</select>
</div>
<div class="col-md-4">
<input type="text" class="form-control" id="task-search" placeholder="Buscar tareas...">
</div>
</div>
<div class="row" id="tasks-container">
<!-- Tasks will be loaded here -->
</div>
</div>
<!-- Timeline View -->
<div id="timeline-view" class="view" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Línea de Tiempo</h2>
<div class="btn-group">
<button class="btn btn-outline-primary" onclick="changeTimelineRange('day')">Día</button>
<button class="btn btn-outline-primary" onclick="changeTimelineRange('week')">Semana</button>
<button class="btn btn-outline-primary" onclick="changeTimelineRange('month')">Mes</button>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0" id="timeline-title">Hoy</h5>
<div id="timeline-date"></div>
</div>
</div>
<div class="card-body">
<div class="timeline" id="timeline-container">
<div class="current-time" id="current-time-indicator"></div>
<!-- Timeline items will be loaded here -->
</div>
</div>
</div>
</div>
<!-- Day View -->
<div id="day-view" class="view" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Agenda del Día</h2>
<div class="btn-group">
<button class="btn btn-outline-primary" onclick="changeDay(-1)">Anterior</button>
<button class="btn btn-outline-primary" onclick="changeDay(0)">Hoy</button>
<button class="btn btn-outline-primary" onclick="changeDay(1)">Siguiente</button>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0" id="day-title">Hoy</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width: 10%">Hora</th>
<th style="width: 90%">Tareas</th>
</tr>
</thead>
<tbody id="day-schedule">
<!-- Day schedule will be loaded here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Week View -->
<div id="week-view" class="view" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Semana</h2>
<div class="btn-group">
<button class="btn btn-outline-primary" onclick="changeWeek(-1)">Anterior</button>
<button class="btn btn-outline-primary" onclick="changeWeek(0)">Esta semana</button>
<button class="btn btn-outline-primary" onclick="changeWeek(1)">Siguiente</button>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0" id="week-title">Esta semana</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="week-calendar">
<thead>
<tr>
<th style="width: 14%">Domingo</th>
<th style="width: 14%">Lunes</th>
<th style="width: 14%">Martes</th>
<th style="width: 14%">Miércoles</th>
<th style="width: 14%">Jueves</th>
<th style="width: 14%">Viernes</th>
<th style="width: 14%">Sábado</th>
</tr>
</thead>
<tbody>
<tr>
<td class="calendar-day" id="week-day-0"></td>
<td class="calendar-day" id="week-day-1"></td>
<td class="calendar-day" id="week-day-2"></td>
<td class="calendar-day" id="week-day-3"></td>
<td class="calendar-day" id="week-day-4"></td>
<td class="calendar-day" id="week-day-5"></td>
<td class="calendar-day" id="week-day-6"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Calendar View -->
<div id="calendar-view" class="view" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Calendario</h2>
<div class="btn-group">
<button class="btn btn-outline-primary" onclick="changeMonth(-1)">Mes anterior</button>
<button class="btn btn-outline-primary" onclick="changeMonth(0)">Este mes</button>
<button class="btn btn-outline-primary" onclick="changeMonth(1)">Siguiente mes</button>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0" id="calendar-title">Este mes</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="month-calendar">
<thead>
<tr>
<th style="width: 14%">Dom</th>
<th style="width: 14%">Lun</th>
<th style="width: 14%">Mar</th>
<th style="width: 14%">Mié</th>
<th style="width: 14%">Jue</th>
<th style="width: 14%">Vie</th>
<th style="width: 14%">Sáb</th>
</tr>
</thead>
<tbody id="calendar-body">
<!-- Calendar will be loaded here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Metrics View -->
<div id="metrics-view" class="view" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Métricas</h2>
<div class="btn-group">
<button class="btn btn-outline-primary" onclick="changeMetricsRange('week')">Semana</button>
<button class="btn btn-outline-primary" onclick="changeMetricsRange('month')">Mes</button>
<button class="btn btn-outline-primary" onclick="changeMetricsRange('year')">Año</button>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Tareas completadas</h5>
</div>
<div class="card-body">
<canvas id="completed-chart" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Distribución por prioridad</h5>
</div>
<div class="card-body">
<canvas id="priority-chart" height="300"></canvas>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0">Productividad</h5>
</div>
<div class="card-body">
<canvas id="productivity-chart" height="300"></canvas>
</div>
</div>
</div>
<!-- Profile View -->
<div id="profile-view" class="view" style="display: none;">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Perfil de Usuario</h5>
</div>
<div class="card-body">
<form id="profile-form">
<div class="mb-3">
<label for="profile-name" class="form-label">Nombre</label>
<input type="text" class="form-control" id="profile-name" required>
</div>
<div class="mb-3">
<label for="profile-email" class="form-label">Email</label>
<input type="email" class="form-control" id="profile-email" required>
</div>
<div class="mb-3">
<label for="profile-password" class="form-label">Nueva Contraseña</label>
<input type="password" class="form-control" id="profile-password" placeholder="Dejar en blanco para no cambiar">
</div>
<div class="mb-3">
<label for="profile-confirm-password" class="form-label">Confirmar Nueva Contraseña</label>
<input type="password" class="form-control" id="profile-confirm-password">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="profile-dark-mode">
<label class="form-check-label" for="profile-dark-mode">Modo Oscuro</label>
</div>
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Task Modal -->
<div class="modal fade" id="taskModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="taskModalTitle">Nueva Tarea</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="task-form">
<input type="hidden" id="task-id">
<div class="mb-3">
<label for="task-name" class="form-label">Nombre</label>
<input type="text" class="form-control" id="task-name" required>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="task-priority" class="form-label">Prioridad</label>
<select class="form-select" id="task-priority" required>
<option value="low">Baja</option>
<option value="medium">Media</option>
<option value="high">Alta</option>
</select>
</div>
<div class="col-md-6">
<label for="task-status" class="form-label">Estado</label>
<select class="form-select" id="task-status">
<option value="pending">Pendiente</option>
<option value="in-progress">En progreso</option>
<option value="completed">Completada</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="task-category" class="form-label">Categoría</label>
<select class="form-select" id="task-category">
<option value="work">Trabajo</option>
<option value="personal">Personal</option>
<option value="study">Estudio</option>
<option value="other">Otro</option>
</select>
</div>
<div class="col-md-6">
<label for="task-subcategory" class="form-label">Subcategoría</label>
<input type="text" class="form-control" id="task-subcategory">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="task-start-date" class="form-label">Fecha de inicio</label>
<input type="date" class="form-control" id="task-start-date" required>
</div>
<div class="col-md-6">
<label for="task-end-date" class="form-label">Fecha de fin</label>
<input type="date" class="form-control" id="task-end-date">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="task-start-time" class="form-label">Hora de inicio</label>
<input type="time" class="form-control" id="task-start-time">
</div>
<div class="col-md-6">
<label for="task-end-time" class="form-label">Hora de fin</label>
<input type="time" class="form-control" id="task-end-time">
</div>
</div>
<div class="mb-3">
<label for="task-description" class="form-label">Descripción</label>
<textarea class="form-control" id="task-description" rows="5"></textarea>
</div>
</form>
<div class="mb-3">
<h6>Comentarios</h6>
<div id="task-comments">
<!-- Comments will be loaded here -->
</div>
<div class="input-group mt-3">
<input type="text" class="form-control" id="new-comment" placeholder="Agregar comentario">
<button class="btn btn-primary" type="button" onclick="addComment()">Agregar</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" onclick="saveTask()">Guardar Tarea</button>
</div>
</div>
</div>
</div>
<!-- Task Details Modal -->
<div class="modal fade" id="taskDetailsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="taskDetailsTitle">Detalles de la Tarea</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="task-details-content">
<!-- Task details will be loaded here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
<button type="button" class="btn btn-primary" id="edit-task-btn" onclick="editTask()">Editar</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Global variables
let tasks = [];
let currentView = 'dashboard';
let pomodoroInterval;
let pomodoroTime = 25 * 60; // 25 minutes in seconds
let pomodoroSession = 'work'; // 'work' or 'break'
let pomodoroSessionsCompleted = 0;
let darkMode = false;
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
// Load tasks from localStorage
loadTasks();
// Update current time every second
updateCurrentTime();
setInterval(updateCurrentTime, 1000);
// Load initial views
showView('dashboard');
updateTodayTasks();
updatePomodoroTimer();
// Event listeners
document.getElementById('darkModeSwitch').addEventListener('change', toggleDarkMode);
document.getElementById('quick-task-form').addEventListener('submit', addQuickTask);
document.getElementById('profile-form').addEventListener('submit', saveProfile);
document.getElementById('task-sort').addEventListener('change', updateTasksView);
document.getElementById('task-filter').addEventListener('change', updateTasksView);
document.getElementById('task-search').addEventListener('input', updateTasksView);
// Initialize modals
const taskModal = new bootstrap.Modal(document.getElementById('taskModal'));
const taskDetailsModal = new bootstrap.Modal(document.getElementById('taskDetailsModal'));
// Check for saved dark mode preference
if (localStorage.getItem('darkMode') === 'true') {
document.getElementById('darkModeSwitch').checked = true;
toggleDarkMode();
}
// Load profile data
loadProfile();
});
// View management
function showView(viewName) {
// Hide all views
document.querySelectorAll('.view').forEach(view => {
view.style.display = 'none';
});
// Show the selected view
document.getElementById(`${viewName}-view`).style.display = 'block';
currentView = viewName;
// Update the specific view
switch(viewName) {
case 'dashboard':
updateDashboard();
break;
case 'tasks':
updateTasksView();
break;
case 'timeline':
updateTimelineView();
break;
case 'day':
updateDayView();
break;
case 'week':
updateWeekView();
break;
case 'calendar':
updateCalendarView();
break;
case 'metrics':
updateMetricsView();
break;
case 'profile':
// Already loaded
break;
}
}
// Task management
function loadTasks() {
const savedTasks = localStorage.getItem('tasks');
if (savedTasks) {
tasks = JSON.parse(savedTasks);
}
}
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
function addTask(task) {
tasks.push(task);
saveTasks();
updateTasksView();
updateTodayTasks();
}
function updateTask(id, updatedTask) {
const index = tasks.findIndex(task => task.id === id);
if (index !== -1) {
tasks[index] = updatedTask;
saveTasks();
updateTasksView();
updateTodayTasks();
return true;
}
return false;
}
function deleteTask(id) {
const index = tasks.findIndex(task => task.id === id);
if (index !== -1) {
tasks.splice(index, 1);
saveTasks();
updateTasksView();
updateTodayTasks();
return true;
}
return false;
}
function showTaskModal(taskId = null) {
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskModal'));
const form = document.getElementById('task-form');
if (taskId) {
// Edit existing task
const task = tasks.find(t => t.id === taskId);
if (task) {
document.getElementById('taskModalTitle').textContent = 'Editar Tarea';
document.getElementById('task-id').value = task.id;
document.getElementById('task-name').value = task.name;
document.getElementById('task-priority').value = task.priority;
document.getElementById('task-status').value = task.status || 'pending';
document.getElementById('task-category').value = task.category || 'work';
document.getElementById('task-subcategory').value = task.subcategory || '';
document.getElementById('task-start-date').value = task.startDate || '';
document.getElementById('task-end-date').value = task.endDate || '';
document.getElementById('task-start-time').value = task.startTime || '';
document.getElementById('task-end-time').value = task.endTime || '';
document.getElementById('task-description').value = task.description || '';
// Load comments
loadComments(task.id);
}
} else {
// Add new task
document.getElementById('taskModalTitle').textContent = 'Nueva Tarea';
form.reset();
document.getElementById('task-id').value = '';
document.getElementById('task-status').value = 'pending';
document.getElementById('task-comments').innerHTML = '';
// Set default dates
const today = new Date().toISOString().split('T')[0];
document.getElementById('task-start-date').value = today;
}
modal.show();
}
function saveTask() {
const form = document.getElementById('task-form');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const task = {
id: document.getElementById('task-id').value || Date.now().toString(),
name: document.getElementById('task-name').value,
priority: document.getElementById('task-priority').value,
status: document.getElementById('task-status').value,
category: document.getElementById('task-category').value,
subcategory: document.getElementById('task-subcategory').value,
startDate: document.getElementById('task-start-date').value,
endDate: document.getElementById('task-end-date').value,
startTime: document.getElementById('task-start-time').value,
endTime: document.getElementById('task-end-time').value,
description: document.getElementById('task-description').value,
createdAt: new Date().toISOString()
};
if (task.id) {
// Update existing task
updateTask(task.id, task);
} else {
// Add new task
addTask(task);
}
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskModal'));
modal.hide();
}
function showTaskDetails(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskDetailsModal'));
const content = document.getElementById('task-details-content');
// Format dates
const startDate = task.startDate ? new Date(task.startDate).toLocaleDateString() : 'No especificada';
const endDate = task.endDate ? new Date(task.endDate).toLocaleDateString() : 'No especificada';
// Format times
const startTime = task.startTime || 'No especificada';
const endTime = task.endTime || 'No especificada';
// Priority badge
let priorityBadge;
switch(task.priority) {
case 'low':
priorityBadge = '<span class="badge bg-success">Baja</span>';
break;
case 'medium':
priorityBadge = '<span class="badge bg-warning text-dark">Media</span>';
break;
case 'high':
priorityBadge = '<span class="badge bg-danger">Alta</span>';
break;
}
// Status badge
let statusBadge;
switch(task.status) {
case 'pending':
statusBadge = '<span class="badge bg-secondary">Pendiente</span>';
break;
case 'in-progress':
statusBadge = '<span class="badge bg-primary">En progreso</span>';
break;
case 'completed':
statusBadge = '<span class="badge bg-success">Completada</span>';
break;
}
// Build the content
content.innerHTML = `
<h5>${task.name}</h5>
<div class="mb-3">
${priorityBadge} ${statusBadge}
</div>
<div class="mb-3">
<strong>Categoría:</strong> ${task.category} ${task.subcategory ? `(${task.subcategory})` : ''}
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Fecha inicio:</strong> ${startDate}
</div>
<div class="col-md-6">
<strong>Fecha fin:</strong> ${endDate}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Hora inicio:</strong> ${startTime}
</div>
<div class="col-md-6">
<strong>Hora fin:</strong> ${endTime}
</div>
</div>
<div class="mb-3">
<strong>Descripción:</strong>
<p>${task.description || 'No hay descripción.'}</p>
</div>
<div class="mb-3">
<strong>Comentarios:</strong>
<div id="details-comments">
${task.comments && task.comments.length > 0 ?
task.comments.map(c => `<div class="card mb-2"><div class="card-body p-2">${c.text}</div></div>`).join('') :
'<p>No hay comentarios.</p>'}
</div>
</div>
`;
// Set the edit button to edit this task
document.getElementById('edit-task-btn').setAttribute('data-task-id', task.id);
modal.show();
}
function editTask() {
const taskId = document.getElementById('edit-task-btn').getAttribute('data-task-id');
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskDetailsModal'));
modal.hide();
showTaskModal(taskId);
}
function addQuickTask(e) {
e.preventDefault();
const name = document.getElementById('quick-task-name').value;
const priority = document.getElementById('quick-task-priority').value;
const today = new Date().toISOString().split('T')[0];
const task = {
id: Date.now().toString(),
name: name,
priority: priority,
status: 'pending',
category: 'personal',
startDate: today,
createdAt: new Date().toISOString()
};
addTask(task);
document.getElementById('quick-task-form').reset();
}
function addComment() {
const commentInput = document.getElementById('new-comment');
const commentText = commentInput.value.trim();
const taskId = document.getElementById('task-id').value;
if (!commentText) return;
const task = tasks.find(t => t.id === taskId);
if (!task) return;
if (!task.comments) {
task.comments = [];
}
task.comments.push({
text: commentText,
date: new Date().toISOString()
});
saveTasks();
loadComments(taskId);
commentInput.value = '';
}
function loadComments(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
const commentsContainer = document.getElementById('task-comments');
commentsContainer.innerHTML = '';
if (task.comments && task.comments.length > 0) {
task.comments.forEach(comment => {
const commentElement = document.createElement('div');
commentElement.className = 'card mb-2';
commentElement.innerHTML = `
<div class="card-body p-2">
${comment.text}
</div>
`;
commentsContainer.appendChild(commentElement);
});
} else {
commentsContainer.innerHTML = '<p>No hay comentarios.</p>';
}
}
// View updates
function updateDashboard() {
updateTodayTasks();
updatePomodoroTimer();
updateStats();
}
function updateTasksView() {
const container = document.getElementById('tasks-container');
container.innerHTML = '';
const sortBy = document.getElementById('task-sort').value;
const filterBy = document.getElementById('task-filter').value;
const searchText = document.getElementById('task-search').value.toLowerCase();
// Filter tasks
let filteredTasks = [...tasks];
// Apply search filter
if (searchText) {
filteredTasks = filteredTasks.filter(task =>
task.name.toLowerCase().includes(searchText) ||
(task.description && task.description.toLowerCase().includes(searchText))
);
}
// Apply status filter
switch(filterBy) {
case 'today':
const today = new Date().toISOString().split('T')[0];
filteredTasks = filteredTasks.filter(task => task.startDate === today);
break;
case 'week':
const currentWeek = getWeekNumber(new Date());
filteredTasks = filteredTasks.filter(task => {
const taskDate = new Date(task.startDate);
return getWeekNumber(taskDate) === currentWeek;
});
break;
case 'completed':
filteredTasks = filteredTasks.filter(task => task.status === 'completed');
break;
case 'pending':
filteredTasks = filteredTasks.filter(task => task.status !== 'completed');
break;
// 'all' - no filter
}
// Sort tasks
switch(sortBy) {
case 'date':
filteredTasks.sort((a, b) => new Date(a.startDate) - new Date(b.startDate));
break;
case 'priority':
const priorityOrder = { high: 1, medium: 2, low: 3 };
filteredTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
break;
case 'category':
filteredTasks.sort((a, b) => a.category.localeCompare(b.category));
break;
}
// Display tasks
if (filteredTasks.length === 0) {
container.innerHTML = '<div class="col-12"><p>No hay tareas que mostrar.</p></div>';
return;
}
filteredTasks.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = 'col-md-6 col-lg-4 mb-4';
// Priority class
let priorityClass;
switch(task.priority) {
case 'low':
priorityClass = 'priority-low';
break;
case 'medium':
priorityClass = 'priority-medium';
break;
case 'high':
priorityClass = 'priority-high';
break;
}
// Status badge
let statusBadge;
switch(task.status) {
case 'pending':
statusBadge = '<span class="badge bg-secondary">Pendiente</span>';
break;
case 'in-progress':
statusBadge = '<span class="badge bg-primary">En progreso</span>';
break;
case 'completed':
statusBadge = '<span class="badge bg-success">Completada</span>';
break;
}
// Format date
const taskDate = task.startDate ? new Date(task.startDate).toLocaleDateString() : 'Sin fecha';
taskElement.innerHTML = `
<div class="card task-card ${priorityClass}" onclick="showTaskDetails('${task.id}')">
<div class="card-body">
<h5 class="card-title">${task.name}</h5>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">${taskDate}</span>
${statusBadge}
</div>
<p class="card-text">${task.description ? task.description.substring(0, 100) + (task.description.length > 100 ? '...' : '') : 'Sin descripción'}</p>
<div class="d-flex justify-content-between">
<span class="badge bg-light text-dark">${task.category}</span>
<button class="btn btn-sm btn-outline-danger" onclick="event.stopPropagation(); deleteTask('${task.id}')">Eliminar</button>
</div>
</div>
</div>
`;
container.appendChild(taskElement);
});
}
function updateTodayTasks() {
const container = document.getElementById('today-tasks');
container.innerHTML = '';
const today = new Date().toISOString().split('T')[0];
const todayTasks = tasks.filter(task => task.startDate === today);
if (todayTasks.length === 0) {
container.innerHTML = '<p>No hay tareas para hoy.</p>';
return;
}
todayTasks.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = 'mb-3';
// Priority class
let priorityClass;
switch(task.priority) {
case 'low':
priorityClass = 'priority-low';
break;
case 'medium':
priorityClass = 'priority-medium';
break;
case 'high':
priorityClass = 'priority-high';
break;
}
// Status badge
let statusBadge;
switch(task.status) {
case 'pending':
statusBadge = '<span class="badge bg-secondary">Pendiente</span>';
break;
case 'in-progress':
statusBadge = '<span class="badge bg-primary">En progreso</span>';
break;
case 'completed':
statusBadge = '<span class="badge bg-success">Completada</span>';
break;
}
// Time info
const timeInfo = task.startTime ?
(task.endTime ? `${task.startTime} - ${task.endTime}` : `Desde ${task.startTime}`) :
'Sin hora específica';
taskElement.innerHTML = `
<div class="card ${priorityClass}">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">${task.name}</h6>
${statusBadge}
</div>
<div class="d-flex justify-content-between mt-2">
<small class="text-muted">${timeInfo}</small>
<div>
<button class="btn btn-sm btn-outline-primary" onclick="showTaskDetails('${task.id}')">Detalles</button>
<button class="btn btn-sm btn-outline-success" onclick="completeTask('${task.id}')">Completar</button>
</div>
</div>
</div>
</div>
`;
container.appendChild(taskElement);
});
}
function completeTask(taskId) {
const task = tasks.find(t => t.id === taskId);
if (task) {
task.status = 'completed';
saveTasks();
updateTodayTasks();
updateTasksView();
}
}
function updateTimelineView() {
const container = document.getElementById('timeline-container');
const title = document.getElementById('timeline-title');
const dateDisplay = document.getElementById('timeline-date');
// Clear existing items except the current time indicator
container.innerHTML = '<div class="current-time" id="current-time-indicator"></div>';
// Get today's tasks with time
const today = new Date().toISOString().split('T')[0];
const todayTasks = tasks.filter(task =>
task.startDate === today && task.startTime
).sort((a, b) => {
// Sort by time
const timeA = a.startTime.split(':').map(Number);
const timeB = b.startTime.split(':').map(Number);
return (timeA[0] * 60 + timeA[1]) - (timeB[0] * 60 + timeB[1]);
});
title.textContent = 'Hoy';
dateDisplay.textContent = new Date().toLocaleDateString();
if (todayTasks.length === 0) {
const noTasks = document.createElement('div');
noTasks.className = 'alert alert-info';
noTasks.textContent = 'No hay tareas programadas para hoy con hora específica.';
container.appendChild(noTasks);
return;
}
todayTasks.forEach(task => {
const item = document.createElement('div');
item.className = 'timeline-item';
// Priority dot
let priorityDot;
switch(task.priority) {
case 'low':
priorityDot = 'bg-success';
break;
case 'medium':
priorityDot = 'bg-warning';
break;
case 'high':
priorityDot = 'bg-danger';
break;
}
// Status badge
let statusBadge;
switch(task.status) {
case 'pending':
statusBadge = '<span class="badge bg-secondary">Pendiente</span>';
break;
case 'in-progress':
statusBadge = '<span class="badge bg-primary">En progreso</span>';
break;
case 'completed':
statusBadge = '<span class="badge bg-success">Completada</span>';
break;
}
// Time range
const timeRange = task.endTime ?
`${task.startTime} - ${task.endTime}` :
`Desde ${task.startTime}`;
item.innerHTML = `
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">${task.name}</h6>
<span class="badge ${priorityDot}">${task.priority}</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span>${timeRange}</span>
${statusBadge}
</div>
${task.description ? `<p class="mt-2 mb-0">${task.description}</p>` : ''}
</div>
</div>
`;
container.appendChild(item);
});
// Update current time indicator position
updateCurrentTimeIndicator();
}
function updateCurrentTimeIndicator() {
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
const totalMinutes = hours * 60 + minutes;
// Assuming timeline starts at 8:00 AM and ends at 8:00 PM (12 hours)
const timelineStart = 8 * 60; // 8:00 AM in minutes
const timelineEnd = 20 * 60; // 8:00 PM in minutes
const timelineHeight = document.getElementById('timeline-container').offsetHeight;
if (totalMinutes >= timelineStart && totalMinutes <= timelineEnd) {
const position = ((totalMinutes - timelineStart) / (timelineEnd - timelineStart)) * timelineHeight;
document.getElementById('current-time-indicator').style.top = `${position}px`;
document.getElementById('current-time-indicator').style.display = 'block';
} else {
document.getElementById('current-time-indicator').style.display = 'none';
}
}
function updateDayView() {
const container = document.getElementById('day-schedule');
const title = document.getElementById('day-title');
// For now, we'll just show today's schedule
const today = new Date();
title.textContent = today.toLocaleDateString('es-ES', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
// Clear the schedule
container.innerHTML = '';
// Get today's tasks
const todayStr = today.toISOString().split('T')[0];
const todayTasks = tasks.filter(task => task.startDate === todayStr);
if (todayTasks.length === 0) {
container.innerHTML = '<tr><td colspan="2" class="text-center py-4">No hay tareas programadas para hoy.</td></tr>';
return;
}
// Group tasks by hour (simplified for this example)
const hours = Array.from({ length: 24 }, (_, i) => i); // 0-23
hours.forEach(hour => {
const hourTasks = todayTasks.filter(task => {
if (!task.startTime) return false;
const taskHour = parseInt(task.startTime.split(':')[0]);
return taskHour === hour;
});
if (hourTasks.length > 0) {
const row = document.createElement('tr');
// Hour cell
const hourCell = document.createElement('td');
hourCell.textContent = `${hour}:00`;
row.appendChild(hourCell);
// Tasks cell
const tasksCell = document.createElement('td');
hourTasks.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = 'mb-2';
// Priority dot
let priorityDot;
switch(task.priority) {
case 'low':
priorityDot = 'bg-success';
break;
case 'medium':
priorityDot = 'bg-warning';
break;
case 'high':
priorityDot = 'bg-danger';
break;
}
taskElement.innerHTML = `
<div class="d-flex align-items-center">
<span class="task-dot ${priorityDot}"></span>
<span>${task.name}</span>
<button class="btn btn-sm btn-outline-primary ms-auto" onclick="showTaskDetails('${task.id}')">Ver</button>
</div>
`;
tasksCell.appendChild(taskElement);
});
row.appendChild(tasksCell);
container.appendChild(row);
}
});
}
function updateWeekView() {
const title = document.getElementById('week-title');
const now = new Date();
const startOfWeek = getStartOfWeek(now);
title.textContent = `Semana del ${startOfWeek.toLocaleDateString()} al ${new Date(startOfWeek.getTime() + 6 * 24 * 60 * 60 * 1000).toLocaleDateString()}`;
// Update each day in the week view
for (let i = 0; i < 7; i++) {
const day = new Date(startOfWeek.getTime() + i * 24 * 60 * 60 * 1000);
const dayStr = day.toISOString().split('T')[0];
const dayElement = document.getElementById(`week-day-${i}`);
// Clear the day
dayElement.innerHTML = '';
// Add day header
const header = document.createElement('div');
header.className = 'calendar-day-header';
header.textContent = day.toLocaleDateString('es-ES', { weekday: 'short', day: 'numeric' });
dayElement.appendChild(header);
// Check if it's today
const today = new Date().toISOString().split('T')[0];
if (dayStr === today) {
dayElement.classList.add('today');
} else {
dayElement.classList.remove('today');
}
// Get tasks for this day
const dayTasks = tasks.filter(task => task.startDate === dayStr);
if (dayTasks.length === 0) {
const noTasks = document.createElement('div');
noTasks.className = 'text-muted small';
noTasks.textContent = 'Sin tareas';
dayElement.appendChild(noTasks);
continue;
}
// Add tasks to the day
dayTasks.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = 'small mb-1';
// Priority dot
let priorityDot;
switch(task.priority) {
case 'low':
priorityDot = 'bg-success';
break;
case 'medium':
priorityDot = 'bg-warning';
break;
case 'high':
priorityDot = 'bg-danger';
break;
}
taskElement.innerHTML = `
<span class="task-dot ${priorityDot}"></span>
${task.name}
`;
dayElement.appendChild(taskElement);
});
}
}
function updateCalendarView() {
const title = document.getElementById('calendar-title');
const now = new Date();
const month = now.getMonth();
const year = now.getFullYear();
title.textContent = now.toLocaleDateString('es-ES', { month: 'long', year: 'numeric' });
// Get first day of month and last day of month
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
// Get days in month
const daysInMonth = lastDay.getDate();
// Get starting day of week (0 = Sunday, 6 = Saturday)
const startingDay = firstDay.getDay();
// Clear calendar
const calendarBody = document.getElementById('calendar-body');
calendarBody.innerHTML = '';
let date = 1;
let row;
// Create calendar rows
for (let i = 0; i < 6; i++) {
// Stop if we've run out of days
if (date > daysInMonth) break;
// Create a new row
row = document.createElement('tr');
// Create cells for each day of the week
for (let j = 0; j < 7; j++) {
const cell = document.createElement('td');
// Fill in empty cells before the first day of the month
if (i === 0 && j < startingDay) {
cell.className = 'calendar-day';
row.appendChild(cell);
continue;
}
// Stop if we've run out of days
if (date > daysInMonth) {
cell.className = 'calendar-day';
row.appendChild(cell);
continue;
}
// Create day cell
cell.className = 'calendar-day';
// Check if it's today
const today = new Date();
if (date === today.getDate() && month === today.getMonth() && year === today.getFullYear()) {
cell.classList.add('today');
}
// Add day number
const dayHeader = document.createElement('div');
dayHeader.className = 'calendar-day-header';
dayHeader.textContent = date;
cell.appendChild(dayHeader);
// Get tasks for this day
const dayStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(date).padStart(2, '0')}`;
const dayTasks = tasks.filter(task => task.startDate === dayStr);
// Add tasks to the day
dayTasks.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = 'small mb-1';
// Priority dot
let priorityDot;
switch(task.priority) {
case 'low':
priorityDot = 'bg-success';
break;
case 'medium':
priorityDot = 'bg-warning';
break;
case 'high':
priorityDot = 'bg-danger';
break;
}
taskElement.innerHTML = `
<span class="task-dot ${priorityDot}"></span>
${task.name}
`;
cell.appendChild(taskElement);
});
row.appendChild(cell);
date++;
}
calendarBody.appendChild(row);
}
}
function updateMetricsView() {
// In a real app, we would use a charting library like Chart.js
// For this example, we'll just show some basic stats
// Completed tasks this week
const currentWeek = getWeekNumber(new Date());
const weekTasks = tasks.filter(task => {
const taskDate = new Date(task.startDate);
return getWeekNumber(taskDate) === currentWeek;
});
const completedThisWeek = weekTasks.filter(task => task.status === 'completed').length;
// Priority distribution
const priorityCounts = {
low: tasks.filter(task => task.priority === 'low').length,
medium: tasks.filter(task => task.priority === 'medium').length,
high: tasks.filter(task => task.priority === 'high').length
};
// For now, we'll just update some text elements
// In a real app, we would render charts here
console.log('Metrics updated:', {
completedThisWeek,
priorityCounts
});
}
// Pomodoro timer functions
function startPomodoro() {
document.getElementById('start-btn').disabled = true;
document.getElementById('stop-btn').disabled = false;
pomodoroInterval = setInterval(() => {
pomodoroTime--;
updatePomodoroTimer();
if (pomodoroTime <= 0) {
clearInterval(pomodoroInterval);
pomodoroSessionCompleted();
}
}, 1000);
}
function stopPomodoro() {
clearInterval(pomodoroInterval);
document.getElementById('start-btn').disabled = false;
document.getElementById('stop-btn').disabled = true;
}
function resetPomodoro() {
stopPomodoro();
pomodoroTime = pomodoroSession === 'work' ? 25 * 60 : 5 * 60;
updatePomodoroTimer();
}
function pomodoroSessionCompleted() {
pomodoroSessionsCompleted++;
if (pomodoroSession === 'work') {
// Work session completed, start break
pomodoroSession = 'break';
pomodoroTime = 5 * 60; // 5 minutes
document.getElementById('pomodoro-session').textContent = 'Descanso';
alert('¡Sesión de trabajo completada! Toma un descanso de 5 minutos.');
} else {
// Break completed, start work
pomodoroSession = 'work';
pomodoroTime = 25 * 60; // 25 minutes
document.getElementById('pomodoro-session').textContent = 'Sesión de Trabajo';
alert('¡Descanso completado! Volvamos al trabajo.');
}
updateStats();
startPomodoro();
}
function updatePomodoroTimer() {
const minutes = Math.floor(pomodoroTime / 60);
const seconds = pomodoroTime % 60;
document.getElementById('pomodoro-timer').textContent =
`${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
// Utility functions
function updateCurrentTime() {
const now = new Date();
document.getElementById('current-time').textContent = now.toLocaleTimeString();
// Update current time indicator in timeline view if it's visible
if (currentView === 'timeline') {
updateCurrentTimeIndicator();
}
}
function getStartOfWeek(date) {
const d = new Date(date);
const day = d.getDay();
const diff = d.getDate() - day + (day === 0 ? -6 : 1); // adjust when day is Sunday
return new Date(d.setDate(diff));
}
function getWeekNumber(date) {
const d = new Date(date);
d.setHours(0, 0, 0, 0);
d.setDate(d.getDate() + 3 - (d.getDay() + 6) % 7);
const week1 = new Date(d.getFullYear(), 0, 4);
return 1 + Math.round(((d - week1) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
}
function toggleDarkMode() {
darkMode = document.getElementById('darkModeSwitch').checked;
document.body.classList.toggle('dark-mode', darkMode);
localStorage.setItem('darkMode', darkMode);
}
function updateStats() {
// Completed today
const today = new Date().toISOString().split('T')[0];
const completedToday = tasks.filter(task =>
task.startDate === today && task.status === 'completed'
).length;
document.getElementById('completed-today').textContent = completedToday;
// Pomodoro sessions
document.getElementById('pomodoro-sessions').textContent = pomodoroSessionsCompleted;
// Time worked (simplified for this example)
document.getElementById('time-worked').textContent = `${Math.floor(pomodoroSessionsCompleted * 25 / 60)}h ${pomodoroSessionsCompleted * 25 % 60}m`;
}
function changeDay(offset) {
// In a real app, we would change the day being viewed
console.log('Day changed by', offset);
updateDayView();
}
function changeWeek(offset) {
// In a real app, we would change the week being viewed
console.log('Week changed by', offset);
updateWeekView();
}
function changeMonth(offset) {
// In a real app, we would change the month being viewed
console.log('Month changed by', offset);
updateCalendarView();
}
function changeTimelineRange(range) {
// In a real app, we would change the timeline range
console.log('Timeline range changed to', range);
updateTimelineView();
}
function changeMetricsRange(range) {
// In a real app, we would change the metrics range
console.log('Metrics range changed to', range);
updateMetricsView();
}
// Profile functions
function loadProfile() {
const profile = JSON.parse(localStorage.getItem('profile')) || {
name: 'Usuario',
email: 'usuario@example.com',
darkMode: false
};
document.getElementById('profile-name').value = profile.name;
document.getElementById('profile-email').value = profile.email;
document.getElementById('profile-dark-mode').checked = profile.darkMode;
}
function saveProfile(e) {
e.preventDefault();
const profile = {
name: document.getElementById('profile-name').value,
email: document.getElementById('profile-email').value,
darkMode: document.getElementById('profile-dark-mode').checked
};
const password = document.getElementById('profile-password').value;
const confirmPassword = document.getElementById('profile-confirm-password').value;
if (password && password !== confirmPassword) {
alert('Las contraseñas no coinciden.');
return;
}
if (password) {
profile.password = password; // In a real app, you would hash this
}
localStorage.setItem('profile', JSON.stringify(profile));
// Update dark mode if changed
if (profile.darkMode !== darkMode) {
darkMode = profile.darkMode;
document.getElementById('darkModeSwitch').checked = darkMode;
document.body.classList.toggle('dark-mode', darkMode);
}
alert('Perfil actualizado correctamente.');
}
function logout() {
// In a real app, you would handle actual logout
alert('Sesión cerrada (simulado)');
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=butztub/timetimer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>