Update 2025-04-13_16:43:49
This commit is contained in:
13
static/app.js
Normal file
13
static/app.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { setupResetButton } from './button.js';
|
||||
import { loadStatus, updateTimer } from './timer.js';
|
||||
import { drawChart } from './chart.js';
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
loadStatus();
|
||||
updateTimer();
|
||||
setInterval(updateTimer, 1000);
|
||||
|
||||
setupResetButton();
|
||||
drawChart();
|
||||
});
|
||||
|
30
static/button.js
Normal file
30
static/button.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { getBreakMessage } from './message.js';
|
||||
import { loadStatus, updateTimer, startTime, setBreakActiveUntil } from './timer.js';
|
||||
import { drawChart } from './chart.js';
|
||||
|
||||
export function setupResetButton() {
|
||||
const resetBtn = document.getElementById("resetBtn");
|
||||
const messageEl = document.getElementById("message");
|
||||
|
||||
resetBtn.addEventListener("click", () => {
|
||||
const now = new Date();
|
||||
const seconds = Math.floor((now - startTime) / 1000);
|
||||
|
||||
// Update history
|
||||
const history = JSON.parse(localStorage.getItem("victorytimer_history") || "[]");
|
||||
history.push(seconds);
|
||||
localStorage.setItem("victorytimer_history", JSON.stringify(history));
|
||||
|
||||
// Reset start time
|
||||
localStorage.setItem("victorytimer_start", now.toISOString());
|
||||
|
||||
// Break-Text setzen
|
||||
messageEl.textContent = getBreakMessage(seconds);
|
||||
setBreakActiveUntil(Date.now() + 3000);
|
||||
|
||||
loadStatus();
|
||||
updateTimer();
|
||||
drawChart();
|
||||
});
|
||||
}
|
||||
|
77
static/chart.js
Normal file
77
static/chart.js
Normal file
@ -0,0 +1,77 @@
|
||||
// Minimal eingebettete Chart.js-Version (angepasst für VictoryTimer)
|
||||
// Vertikaler Bar-Chart ohne Achsentitel und ohne Labels
|
||||
// Feste Balkenhöhe: 20px + 10px Abstand unten, kein Abstand oben
|
||||
|
||||
class ChartWrapper {
|
||||
constructor(ctx, config) {
|
||||
this.ctx = ctx;
|
||||
this.config = config;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { datasets } = this.config.data;
|
||||
const data = datasets[0].data;
|
||||
|
||||
this.ctx.canvas.width = this.ctx.canvas.clientWidth;
|
||||
this.ctx.canvas.height = this.ctx.canvas.clientHeight;
|
||||
|
||||
const width = this.ctx.canvas.width;
|
||||
const height = this.ctx.canvas.height;
|
||||
|
||||
const barHeight = 15;
|
||||
const spacing = 5;
|
||||
|
||||
const max = 720; // 30 Tage = 720 Stunden
|
||||
|
||||
this.ctx.clearRect(0, 0, width, height);
|
||||
|
||||
const colors = [
|
||||
'#ff6f61', '#6a5acd', '#ffd700', '#40e0d0', '#ff69b4', '#87cefa', '#98fb98', '#ffa07a', '#dda0dd', '#f08080',
|
||||
'#00ced1', '#dc143c', '#7b68ee', '#00fa9a', '#ffc0cb', '#ba55d3', '#20b2aa', '#ff8c00', '#4682b4', '#9acd32'
|
||||
];
|
||||
|
||||
data.forEach((val, i) => {
|
||||
const y = i * (barHeight + spacing);
|
||||
const barWidth = (val / max) * width;
|
||||
this.ctx.fillStyle = colors[i % colors.length];
|
||||
this.ctx.fillRect(0, y, barWidth, barHeight);
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
let chart;
|
||||
|
||||
export function drawChart() {
|
||||
const history = JSON.parse(localStorage.getItem("victorytimer_history") || "[]").slice(-20)
|
||||
.filter(seconds => seconds >= 300) // mindestens 5 Minuten
|
||||
.map(seconds => +(seconds / 3600).toFixed(1));
|
||||
|
||||
const ctx = document.getElementById("historyChart").getContext("2d");
|
||||
if (chart) chart.destroy();
|
||||
|
||||
chart = new ChartWrapper(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Rauchfrei-Zeit (Stunden)',
|
||||
data: history
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: { display: false, text: "" }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
21
static/index.html
Normal file
21
static/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🏆 VictoryTimer</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script type="module" src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🏆 VictoryTimer</h1>
|
||||
|
||||
<h2>⏱ Zeit seit letzter Zigarette:</h2>
|
||||
<div id="timer">–</div>
|
||||
<button id="resetBtn">Ich habe geraucht</button>
|
||||
<p id="message"></p>
|
||||
|
||||
<h2>📈 Deine letzten Versuche</h2>
|
||||
<canvas id="historyChart" width="600" height="300"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
|
21
static/manifest.webmanifest
Normal file
21
static/manifest.webmanifest
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "VictoryTimer",
|
||||
"short_name": "VictoryTimer",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#4caf50",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
46
static/message.js
Normal file
46
static/message.js
Normal file
@ -0,0 +1,46 @@
|
||||
export function getMotivationalMessage(seconds) {
|
||||
if (seconds < 20 * 60) {
|
||||
return "🔁 Gerade aufgehört. Erste Entscheidung ✅ – bleib jetzt dran! 💪";
|
||||
} else if (seconds < 60 * 60) {
|
||||
return "🧠 Kopf kämpft mit – du führst! 🚶 Trink Wasser, bleib fokussiert. 🧊🫁";
|
||||
} else if (seconds < 2 * 60 * 60) {
|
||||
return "🏁 Stark gestartet! Dein Körper fährt runter – du übernimmst die Kontrolle. 🛑🧠";
|
||||
} else if (seconds < 3 * 60 * 60) {
|
||||
return "⏱️ Die Uhr läuft für dich. Jeder Moment = mehr Freiheit. 🔓🕊️";
|
||||
} else if (seconds < 6 * 60 * 60) {
|
||||
return "🩺 Kreislauf entspannt sich. Nikotinlevel sinkt. Du bleibst ruhig. 🧘♂️";
|
||||
} else if (seconds < 12 * 60 * 60) {
|
||||
return "🌙 Halber Tag. Deine Organe sagen: Danke! 🫁🧡";
|
||||
} else if (seconds < 24 * 60 * 60) {
|
||||
return "🌄 Fast ein Tag! Lunge startet Reboot. 🧼🫁 Mach weiter!";
|
||||
} else if (seconds < 2 * 24 * 60 * 60) {
|
||||
return "✅ Du bist drüber. Der schlimmste Entzug ist fast vorbei. 🎯";
|
||||
} else if (seconds < 3 * 24 * 60 * 60) {
|
||||
return "🧪 Nikotin = raus. Jetzt übernimmt dein Wille. 🧠⚡";
|
||||
} else if (seconds < 7 * 24 * 60 * 60) {
|
||||
return "🪴 Heilung beginnt. Körper und Kopf arbeiten zusammen. 🛠️🧠";
|
||||
} else if (seconds < 14 * 24 * 60 * 60) {
|
||||
return "👅 Du schmeckst wieder besser. Und das Leben sowieso. 😋🎉";
|
||||
} else if (seconds < 21 * 24 * 60 * 60) {
|
||||
return "🛡️ Stabiler geworden. Trigger? Du reagierst schon gelassener. 😎";
|
||||
} else if (seconds < 30 * 24 * 60 * 60) {
|
||||
return "📈 Durchhalte-Modus aktiviert. Du baust echte Stärke auf. 💪💥";
|
||||
} else {
|
||||
return "🏆 Ziel erreicht. 💨🚫 Ein ganzer Monat frei. Jetzt ist’s kein Versuch mehr – es ist dein neues Ich. 💫";
|
||||
}
|
||||
}
|
||||
|
||||
export function getBreakMessage(seconds) {
|
||||
if (seconds < 5 * 60) {
|
||||
return "⚠️ Das war ein kurzer Ausrutscher. Du hast's sofort gemerkt – das zählt. Jetzt wieder auf Kurs!";
|
||||
} else if (seconds < 60 * 60) {
|
||||
return "🛑 Rückfall – aber du bist hier. Das zeigt: Du gibst nicht auf. Fang neu an – und lerne daraus.";
|
||||
} else if (seconds < 6 * 60 * 60) {
|
||||
return "🤕 Es war eine Pause. Keine Kapitulation. Deine Erfahrung macht dich stärker beim nächsten Mal.";
|
||||
} else if (seconds < 24 * 60 * 60) {
|
||||
return "🌫️ Ein Tag ist futsch – aber nicht deine Motivation. Rückfälle sind Teil des Weges. Mach weiter!";
|
||||
} else {
|
||||
return "🧭 Du hast schon einmal viel geschafft. Jetzt neu starten – aber mit noch mehr Wissen und Klarheit. ✨";
|
||||
}
|
||||
}
|
||||
|
24
static/service-worker.js
Normal file
24
static/service-worker.js
Normal file
@ -0,0 +1,24 @@
|
||||
self.addEventListener('install', (e) => {
|
||||
e.waitUntil(
|
||||
caches.open('victorytimer-v1').then((cache) => {
|
||||
return cache.addAll([
|
||||
'/',
|
||||
'/index.html',
|
||||
'/style.css',
|
||||
'/app.js',
|
||||
'/timer.js',
|
||||
'/button.js',
|
||||
'/chart.js',
|
||||
'/messages.js',
|
||||
'/manifest.webmanifest'
|
||||
]);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (e) => {
|
||||
e.respondWith(
|
||||
caches.match(e.request).then((resp) => resp || fetch(e.request))
|
||||
);
|
||||
});
|
||||
|
63
static/style.css
Normal file
63
static/style.css
Normal file
@ -0,0 +1,63 @@
|
||||
body {
|
||||
max-width: 960px;
|
||||
margin: auto;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
#timer {
|
||||
font-size: 2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
background: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
margin: 0.4rem;
|
||||
width: 90%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
canvas {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#timer {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
#auth {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#auth input {
|
||||
padding: 0.3rem;
|
||||
margin: 0.2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#auth button {
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
44
static/timer.js
Normal file
44
static/timer.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { getMotivationalMessage } from './message.js';
|
||||
|
||||
export let startTime = null;
|
||||
let breakActiveUntil = 0; // bis wann Break-Text sichtbar bleibt (ms)
|
||||
|
||||
export function setBreakActiveUntil(timestamp) {
|
||||
breakActiveUntil = timestamp;
|
||||
}
|
||||
|
||||
export function getBreakActiveUntil() {
|
||||
return breakActiveUntil;
|
||||
}
|
||||
|
||||
export function loadStatus() {
|
||||
const stored = localStorage.getItem("victorytimer_start");
|
||||
startTime = stored ? new Date(stored) : new Date(Math.floor(Date.now() / 1000) * 1000);
|
||||
if (!stored) {
|
||||
localStorage.setItem("victorytimer_start", startTime.toISOString());
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTimer() {
|
||||
if (!startTime) return;
|
||||
const now = new Date();
|
||||
const diff = Math.floor((now - startTime) / 1000);
|
||||
|
||||
const h = Math.floor(diff / 3600);
|
||||
const m = Math.floor((diff % 3600) / 60);
|
||||
const s = diff % 60;
|
||||
|
||||
document.getElementById("timer").textContent = `${h}h ${m}m ${s}s`;
|
||||
|
||||
const messageEl = document.getElementById("message");
|
||||
if (!messageEl) return;
|
||||
|
||||
// Falls Break-Phase aktiv -> Text nicht überschreiben
|
||||
if (Date.now() < getBreakActiveUntil()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ansonsten normale Motivationsmeldung
|
||||
messageEl.textContent = getMotivationalMessage(diff);
|
||||
}
|
||||
|
Reference in New Issue
Block a user