anycoder-4a11315e / index.html
Multimedix's picture
Upload folder using huggingface_hub
582107c verified
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fokus & Flow - Produktivitäts-Dashboard</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
/* Light Theme Variables */
--bg-color: #f3f4f6;
--text-color: #1f2937;
--card-bg: #ffffff;
--primary-color: #6366f1; /* Indigo */
--secondary-color: #a855f7; /* Purple */
--accent-color: #10b981; /* Emerald */
--danger-color: #ef4444;
--border-color: #e5e7eb;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--font-main: 'Outfit', sans-serif;
}
[data-theme="dark"] {
/* Dark Theme Variables */
--bg-color: #0f172a;
--text-color: #f3f4f6;
--card-bg: #1e293b;
--primary-color: #818cf8;
--secondary-color: #c084fc;
--accent-color: #34d399;
--danger-color: #f87171;
--border-color: #334155;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-main);
background-color: var(--bg-color);
color: var(--text-color);
transition: var(--transition);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* --- Header --- */
header {
background: rgba(255, 255, 255, 0.0);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
border-bottom: 1px solid var(--border-color);
background-color: var(--card-bg);
}
.logo {
font-size: 1.5rem;
font-weight: 700;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
display: flex;
align-items: center;
gap: 0.5rem;
}
.anycoder-link {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-color);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 2rem;
background: linear-gradient(90deg, rgba(99, 102, 241, 0.1), rgba(168, 85, 247, 0.1));
border: 1px solid var(--border-color);
transition: var(--transition);
}
.anycoder-link:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
background: linear-gradient(90deg, rgba(99, 102, 241, 0.2), rgba(168, 85, 247, 0.2));
}
.theme-toggle {
background: none;
border: none;
color: var(--text-color);
font-size: 1.2rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: var(--transition);
}
.theme-toggle:hover {
background-color: rgba(128, 128, 128, 0.1);
}
/* --- Main Layout --- */
main {
flex: 1;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 2rem;
}
.card {
background-color: var(--card-bg);
border-radius: 1.5rem;
padding: 1.5rem;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
transition: var(--transition);
display: flex;
flex-direction: column;
}
.card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
h2 {
font-size: 1.25rem;
margin-bottom: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* --- Pomodoro Timer (Left Large) --- */
.timer-section {
grid-column: span 12;
text-align: center;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
@media (min-width: 768px) {
.timer-section {
grid-column: span 7;
}
}
.timer-display {
position: relative;
width: 280px;
height: 280px;
margin: 0 auto 1.5rem;
}
.progress-ring {
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.progress-ring__circle {
stroke-dasharray: 816; /* 2 * PI * r (r=130) */
stroke-dashoffset: 816;
transition: stroke-dashoffset 1s linear;
stroke: var(--primary-color);
}
.timer-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 4rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.timer-label {
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1rem;
color: var(--secondary-color);
text-transform: uppercase;
letter-spacing: 2px;
}
.timer-controls {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
border: none;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
font-family: var(--font-main);
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.btn-primary:hover {
filter: brightness(1.1);
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);
}
.btn-outline {
background: transparent;
border: 2px solid var(--border-color);
color: var(--text-color);
}
.btn-outline:hover {
border-color: var(--primary-color);
color: var(--primary-color);
}
.mode-selector {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-bottom: 2rem;
background: var(--bg-color);
padding: 0.5rem;
border-radius: 1rem;
width: fit-content;
margin-left: auto;
margin-right: auto;
}
.mode-btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
border: none;
background: transparent;
color: var(--text-color);
cursor: pointer;
font-size: 0.9rem;
transition: var(--transition);
}
.mode-btn.active {
background-color: var(--card-bg);
color: var(--primary-color);
box-shadow: var(--shadow-sm);
font-weight: 600;
}
/* --- Tasks (Right Column) --- */
.tasks-section {
grid-column: span 12;
}
@media (min-width: 768px) {
.tasks-section {
grid-column: span 5;
}
}
.task-input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.task-input {
flex: 1;
padding: 0.75rem;
border-radius: 0.75rem;
border: 1px solid var(--border-color);
background-color: var(--bg-color);
color: var(--text-color);
outline: none;
font-family: var(--font-main);
transition: var(--transition);
}
.task-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}
.task-list {
list-style: none;
overflow-y: auto;
max-height: 300px;
padding-right: 0.5rem;
}
/* Custom Scrollbar */
.task-list::-webkit-scrollbar {
width: 6px;
}
.task-list::-webkit-scrollbar-track {
background: transparent;
}
.task-list::-webkit-scrollbar-thumb {
background-color: var(--border-color);
border-radius: 20px;
}
.task-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
background-color: var(--bg-color);
border-radius: 0.75rem;
margin-bottom: 0.5rem;
border: 1px solid transparent;
transition: var(--transition);
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.task-item:hover {
border-color: var(--primary-color);
}
.task-content {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
cursor: pointer;
}
.custom-checkbox {
width: 20px;
height: 20px;
border: 2px solid var(--primary-color);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.task-item.completed .custom-checkbox {
background-color: var(--primary-color);
}
.custom-checkbox i {
color: white;
font-size: 0.7rem;
opacity: 0;
transform: scale(0);
transition: var(--transition);
}
.task-item.completed .custom-checkbox i {
opacity: 1;
transform: scale(1);
}
.task-text {
transition: var(--transition);
}
.task-item.completed .task-text {
text-decoration: line-through;
color: #9ca3af;
}
.delete-btn {
background: none;
border: none;
color: #9ca3af;
cursor: pointer;
padding: 0.5rem;
transition: var(--transition);
}
.delete-btn:hover {
color: var(--danger-color);
}
/* --- Breathing Exercise (Bottom Left) --- */
.breathing-section {
grid-column: span 12;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
position: relative;
}
@media (min-width: 768px) {
.breathing-section {
grid-column: span 6;
}
}
.breath-circle-container {
position: relative;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 1rem;
}
.breath-circle {
width: 100%;
height: 100%;
background: radial-gradient(circle, var(--accent-color) 0%, transparent 70%);
border-radius: 50%;
opacity: 0.6;
transform: scale(0.5);
filter: blur(10px);
}
.breath-circle.animating {
animation: breathAnim 12s infinite ease-in-out;
}
@keyframes breathAnim {
0% { transform: scale(0.5); opacity: 0.4; } /* Start Inhale */
33% { transform: scale(1.2); opacity: 0.8; } /* End Inhale / Start Hold */
66% { transform: scale(1.2); opacity: 0.8; } /* End Hold / Start Exhale */
100% { transform: scale(0.5); opacity: 0.4; } /* End Exhale */
}
.breath-instruction {
position: absolute;
font-size: 1.5rem;
font-weight: 600;
color: var(--text-color);
text-align: center;
}
.breath-controls {
margin-top: 2rem;
}
/* --- Stats (Bottom Right) --- */
.stats-section {
grid-column: span 12;
}
@media (min-width: 768px) {
.stats-section {
grid-column: span 6;
}
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
height: 100%;
}
.stat-item {
background-color: var(--bg-color);
padding: 1.5rem;
border-radius: 1rem;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
color: var(--primary-color);
}
.stat-label {
font-size: 0.9rem;
color: #6b7280;
}
.stat-icon {
font-size: 1.5rem;
color: var(--secondary-color);
margin-bottom: 0.5rem;
}
/* Footer */
footer {
text-align: center;
padding: 2rem;
color: #6b7280;
font-size: 0.9rem;
margin-top: auto;
}
/* Mobile Responsiveness Tweaks */
@media (max-width: 640px) {
header {
flex-direction: column;
gap: 1rem;
}
.timer-display {
width: 240px;
height: 240px;
}
.timer-text {
font-size: 3.5rem;
}
}
</style>
</head>
<body>
<header>
<div class="logo">
<i class="fa-solid fa-layer-group"></i> Fokus & Flow
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
<button class="theme-toggle" id="themeToggle" aria-label="Thema wechseln">
<i class="fa-solid fa-moon"></i>
</button>
</header>
<main>
<!-- Timer Section -->
<section class="card timer-section">
<div class="mode-selector">
<button class="mode-btn active" onclick="setMode('focus')">Fokus</button>
<button class="mode-btn" onclick="setMode('shortBreak')">Kurze Pause</button>
<button class="mode-btn" onclick="setMode('longBreak')">Lange Pause</button>
</div>
<div class="timer-display">
<svg class="progress-ring" width="280" height="280">
<circle class="progress-ring__circle-bg" stroke="var(--border-color)" stroke-width="12" fill="transparent" r="130" cx="140" cy="140" />
<circle class="progress-ring__circle" id="progressCircle" stroke-width="12" fill="transparent" r="130" cx="140" cy="140" stroke-linecap="round" />
</svg>
<div class="timer-text" id="timerText">25:00</div>
<div class="timer-label" id="timerLabel">Bereit</div>
</div>
<div class="timer-controls">
<button class="btn btn-primary" id="startBtn">
<i class="fa-solid fa-play"></i> Start
</button>
<button class="btn btn-outline" id="resetBtn">
<i class="fa-solid fa-rotate-right"></i> Reset
</button>
</div>
</section>
<!-- Tasks Section -->
<section class="card tasks-section">
<h2><i class="fa-solid fa-list-check" style="color: var(--secondary-color)"></i> Aufgaben</h2>
<div class="task-input-group">
<input type="text" class="task-input" id="taskInput" placeholder="Neue Aufgabe hinzufügen...">
<button class="btn btn-primary" id="addTaskBtn">
<i class="fa-solid fa-plus"></i>
</button>
</div>
<ul class="task-list" id="taskList">
<!-- Tasks will be added here -->
</ul>
</section>
<!-- Breathing Section -->
<section class="card breathing-section">
<h2><i class="fa-solid fa-lungs" style="color: var(--accent-color)"></i> Atmen & Entspannen</h2>
<div class="breath-circle-container">
<div class="breath-circle" id="breathCircle"></div>
<div class="breath-instruction" id="breathText">Starten</div>
</div>
<div class="breath-controls">
<button class="btn btn-outline" id="breathBtn">Übung starten</button>
</div>
</section>
<!-- Stats Section -->
<section class="card stats-section">
<h2><i class="fa-solid fa-chart-simple" style="color: var(--primary-color)"></i> Tagesstatistik</h2>
<div class="stats-grid">
<div class="stat-item">
<i class="fa-solid fa-clock stat-icon"></i>
<div class="stat-value" id="totalTime">0</div>
<div class="stat-label">Minuten Fokus</div>
</div>
<div class="stat-item">
<i class="fa-solid fa-check-double stat-icon"></i>
<div class="stat-value" id="completedTasks">0</div>
<div class="stat-label">Aufgaben erledigt</div>
</div>
</div>
</section>
</main>
<footer>
&copy; 2023 Fokus & Flow. Alle Rechte vorbehalten.
</footer>
<script>
// --- Theme Logic ---
const themeToggle = document.getElementById('themeToggle');
const body = document.body;
const icon = themeToggle.querySelector('i');
// Check local storage
if (localStorage.getItem('theme') === 'dark') {
body.setAttribute('data-theme', 'dark');
icon.classList.replace('fa-moon', 'fa-sun');
}
themeToggle.addEventListener('click', () => {
if (body.getAttribute('data-theme') === 'dark') {
body.removeAttribute('data-theme');
localStorage.setItem('theme', 'light');
icon.classList.replace('fa-sun', 'fa-moon');
} else {
body.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
icon.classList.replace('fa-moon', 'fa-sun');
}
});
// --- Timer Logic ---
let timerInterval;
let timeLeft = 25 * 60;
let totalTime = 25 * 60;
let isRunning = false;
let totalMinutesFocused = 0;
const timerText = document.getElementById('timerText');
const timerLabel = document.getElementById('timerLabel');
const progressCircle = document.getElementById('progressCircle');
const startBtn = document.getElementById('startBtn');
const resetBtn = document.getElementById('resetBtn');
const modeBtns = document.querySelectorAll('.mode-btn');
// Circle Config
const radius = progressCircle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
progressCircle.style.strokeDasharray = `${circumference} ${circumference}`;
progressCircle.style.strokeDashoffset = 0;
function setProgress(percent) {
const offset = circumference - (percent / 100) * circumference;
progressCircle.style.strokeDashoffset = offset;
}
function updateDisplay() {
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
timerText.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
const percent = (timeLeft / totalTime) * 100;
setProgress(percent);
}
function setMode(mode) {
stopTimer();
modeBtns.forEach(btn => btn.classList.remove('active'));
if (mode === 'focus') {
timeLeft = 25 * 60;
totalTime = 25 * 60;
timerLabel.textContent = 'Fokus Zeit';
modeBtns[0].classList.add('active');
document.documentElement.style.setProperty('--primary-color', '#6366f1');
} else if (mode === 'shortBreak') {
timeLeft = 5 * 60;
totalTime = 5 * 60;
timerLabel.textContent = 'Kurze Pause';
modeBtns[1].classList.add('active');
document.documentElement.style.setProperty('--primary-color', '#10b981');
} else if (mode === 'longBreak') {
timeLeft = 15 * 60;
totalTime = 15 * 60;
timerLabel.textContent = 'Lange Pause';
modeBtns[2].classList.add('active');
document.documentElement.style.setProperty('--primary-color', '#a855f7');
}
updateDisplay();
setProgress(100); // Reset circle full
}
function startTimer() {
if (isRunning) {
stopTimer();
return;
}
isRunning = true;
startBtn.innerHTML = '<i class="fa-solid fa-pause"></i> Pause';
timerLabel.textContent = 'Läuft...';
timerInterval = setInterval(() => {
timeLeft--;
updateDisplay();
if (modeBtns[0].classList.contains('active')) {
// Only count stats if in focus mode
// Simple logic: update stats every minute or at end?
// Let's update continuously for visual feedback or just store end result.
// For simplicity, we increment stats at the end of a session.
}
if (timeLeft <= 0) {
stopTimer();
timerLabel.textContent = 'Fertig!';
// Play sound or alert could go here
if (modeBtns[0].classList.contains('active')) {
totalMinutesFocused += (totalTime / 60);
document.getElementById('totalTime').textContent = Math.round(totalMinutesFocused);
}
}
}, 1000);
}
function stopTimer() {
isRunning = false;
clearInterval(timerInterval);
startBtn.innerHTML = '<i class="fa-solid fa-play"></i> Start';
if(timeLeft > 0) timerLabel.textContent = 'Pausiert';
}
startBtn.addEventListener('click', startTimer);
resetBtn.addEventListener('click', () => {
stopTimer();
const activeMode = document.querySelector('.mode-btn.active').textContent;
if (activeMode.includes('Fokus')) setMode('focus');
else if (activeMode.includes('Kurze')) setMode('shortBreak');
else setMode('longBreak');
});
// --- Task Logic ---
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
const taskList = document.getElementById('taskList');
const completedStats = document.getElementById('completedTasks');
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
function renderTasks() {
taskList.innerHTML = '';
let completedCount = 0;
tasks.forEach((task, index) => {
if (task.completed) completedCount++;
const li = document.createElement('li');
li.className = `task-item ${task.completed ? 'completed' : ''}`;
li.innerHTML = `
<div class="task-content" onclick="toggleTask(${index})">
<div class="custom-checkbox">
<i class="fa-solid fa-check"></i>
</div>
<span class="task-text">${task.text}</span>
</div>
<button class="delete-btn" onclick="deleteTask(${index})">
<i class="fa-solid fa-trash"></i>
</button>
`;
taskList.appendChild(li);
});
completedStats.textContent = completedCount;
localStorage.setItem('tasks', JSON.stringify(tasks));
}
function addTask() {
const text = taskInput.value.trim();
if (text) {
tasks.push({ text, completed: false });
taskInput.value = '';
renderTasks();
}
}
window.toggleTask = (index) => {
tasks[index].completed = !tasks[index].completed;
renderTasks();
};
window.deleteTask = (index) => {
tasks.splice(index, 1);
renderTasks();
};
addTaskBtn.addEventListener('click', addTask);
taskInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addTask();
});
// --- Breathing Logic ---
const breathBtn = document.getElementById('breathBtn');
const breathCircle = document.getElementById('breathCircle');
const breathText = document.getElementById('breathText');
let isBreathing = false;
let breathTimeout;
// The CSS animation is 12s long: 4s inhale, 4s hold, 4s exhale
function breathCycle() {
if (!isBreathing) return;
breathText.textContent = "Einatmen";
setTimeout(() => {
if (!isBreathing) return;
breathText.textContent = "Halten";
}, 4000);
setTimeout(() => {
if (!isBreathing) return;
breathText.textContent = "Ausatmen";
}, 8000);
}
breathBtn.addEventListener('click', () => {
if (isBreathing) {
// Stop
isBreathing = false;
breathCircle.classList.remove('animating');
breathBtn.textContent = "Übung starten";
breathBtn.classList.remove('btn-primary');
breathBtn.classList.add('btn-outline');
breathText.textContent = "Starten";
// Reset animation
const newOne = breathCircle.cloneNode(true);
breathCircle.parentNode.replaceChild(newOne, breathCircle);
} else {
// Start
isBreathing = true;
breathCircle.classList.add('animating');
breathBtn.textContent = "Beenden";
breathBtn.classList.remove('btn-outline');
breathBtn.classList.add('btn-primary');
breathCycle(); // First immediate cycle
// Sync with CSS animation duration (12s)
setInterval(() => {
if(isBreathing) breathCycle();
}, 12000);
}
});
// Initialize
renderTasks();
setMode('focus'); // Init display
</script>
</body>
</html>