Improve mobile responsiveness and fix particle field calculations
This commit is contained in:
+13
-7
@@ -403,9 +403,9 @@ class ParticleField {
|
|||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
this.COLS = 100;
|
this.COLS = 0;
|
||||||
this.ROWS = 4;
|
this.ROWS = 4;
|
||||||
this.N = this.COLS * this.ROWS;
|
this.N = 0;
|
||||||
this.targetProgress = 0;
|
this.targetProgress = 0;
|
||||||
this.currentProgress = 0;
|
this.currentProgress = 0;
|
||||||
this.cardState = 'active';
|
this.cardState = 'active';
|
||||||
@@ -420,6 +420,16 @@ class ParticleField {
|
|||||||
this.canvas.height = rect.height * devicePixelRatio;
|
this.canvas.height = rect.height * devicePixelRatio;
|
||||||
this.canvas.style.width = rect.width + 'px';
|
this.canvas.style.width = rect.width + 'px';
|
||||||
this.canvas.style.height = rect.height + 'px';
|
this.canvas.style.height = rect.height + 'px';
|
||||||
|
|
||||||
|
const dpr = devicePixelRatio;
|
||||||
|
const radius = 4 * dpr;
|
||||||
|
const gap = 2 * dpr;
|
||||||
|
const step = radius * 2 + gap;
|
||||||
|
|
||||||
|
// Calculate how many columns fit with 32px padding on each side
|
||||||
|
const padding = 32 * dpr;
|
||||||
|
this.COLS = Math.max(1, Math.floor((this.canvas.width - padding * 2) / step));
|
||||||
|
this.N = this.COLS * this.ROWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(progress, cardState) {
|
update(progress, cardState) {
|
||||||
@@ -429,6 +439,7 @@ class ParticleField {
|
|||||||
|
|
||||||
draw(_ts) {
|
draw(_ts) {
|
||||||
const { ctx, canvas, COLS, ROWS, N } = this;
|
const { ctx, canvas, COLS, ROWS, N } = this;
|
||||||
|
if (N === 0) return;
|
||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
const h = canvas.height;
|
const h = canvas.height;
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
@@ -639,11 +650,6 @@ function rebuildCards() {
|
|||||||
const id = card.id.replace('card-', '');
|
const id = card.id.replace('card-', '');
|
||||||
const canvas = card.querySelector('canvas');
|
const canvas = card.querySelector('canvas');
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
const rect = card.getBoundingClientRect();
|
|
||||||
canvas.width = rect.width * devicePixelRatio;
|
|
||||||
canvas.height = rect.height * devicePixelRatio;
|
|
||||||
canvas.style.width = rect.width + 'px';
|
|
||||||
canvas.style.height = rect.height + 'px';
|
|
||||||
particleFields[id] = new ParticleField(canvas);
|
particleFields[id] = new ParticleField(canvas);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -16,12 +16,12 @@
|
|||||||
<label class="toggle" id="workModeLabel">
|
<label class="toggle" id="workModeLabel">
|
||||||
<input type="checkbox" id="workMode">
|
<input type="checkbox" id="workMode">
|
||||||
<span class="track"></span>
|
<span class="track"></span>
|
||||||
<span class="toggle-label">Work time</span>
|
<span class="toggle-label"><span>Work time</span></span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle" id="bridgeDaysLabel">
|
<label class="toggle" id="bridgeDaysLabel">
|
||||||
<input type="checkbox" id="bridgeDays">
|
<input type="checkbox" id="bridgeDays">
|
||||||
<span class="track"></span>
|
<span class="track"></span>
|
||||||
<span class="toggle-label">Bridge days</span>
|
<span class="toggle-label"><span>Bridge days</span></span>
|
||||||
</label>
|
</label>
|
||||||
<button id="editToggle" aria-label="Edit timers">✎</button>
|
<button id="editToggle" aria-label="Edit timers">✎</button>
|
||||||
<button id="themeToggle" aria-label="Toggle theme"></button>
|
<button id="themeToggle" aria-label="Toggle theme"></button>
|
||||||
|
|||||||
+55
-2
@@ -20,7 +20,6 @@ html, body {
|
|||||||
font-family: 'Inter', system-ui, sans-serif;
|
font-family: 'Inter', system-ui, sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Header ─────────────────────────────────────── */
|
/* ── Header ─────────────────────────────────────── */
|
||||||
@@ -31,6 +30,16 @@ header {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 32px;
|
padding: 0 32px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
header {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#week-number {
|
#week-number {
|
||||||
@@ -45,6 +54,12 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.toggles {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.toggle {
|
.toggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -92,6 +107,18 @@ header {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workModeLabel .toggle-label::before { content: '⚒'; filter: grayscale(100%); opacity: 0.7; }
|
||||||
|
#bridgeDaysLabel .toggle-label::before { content: '🏖'; filter: grayscale(100%); opacity: 0.7; }
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.toggle-label span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#bridgeDaysLabel {
|
#bridgeDaysLabel {
|
||||||
@@ -107,7 +134,7 @@ header {
|
|||||||
|
|
||||||
/* ── Cards ───────────────────────────────────────── */
|
/* ── Cards ───────────────────────────────────────── */
|
||||||
#cards {
|
#cards {
|
||||||
height: calc(100vh - var(--header-height));
|
min-height: calc(100vh - var(--header-height));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -115,6 +142,7 @@ header {
|
|||||||
.card {
|
.card {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 80px;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -140,6 +168,12 @@ header {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.card-content {
|
||||||
|
padding: 8px 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card-name {
|
.card-name {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -149,6 +183,12 @@ header {
|
|||||||
transition: color 0.4s ease;
|
transition: color 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.card-name, .card-ends, .card-remaining {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card-ends {
|
.card-ends {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@@ -279,6 +319,19 @@ header {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.add-timer-form {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px 0;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-timer-form input[type="text"],
|
||||||
|
.add-timer-form input[type="date"] {
|
||||||
|
width: 200px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.add-timer-form input[type="text"],
|
.add-timer-form input[type="text"],
|
||||||
.add-timer-form input[type="date"] {
|
.add-timer-form input[type="date"] {
|
||||||
background: none;
|
background: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user