Compare commits

...

5 Commits

Author SHA1 Message Date
Kantine Wrapper
ce7d8a3de5 dist files for v1.4.4 built 2026-02-24 10:46:06 +01:00
Kantine Wrapper
0309f488bd dist files for v1.4.4 built 2026-02-24 10:46:00 +01:00
Kantine Wrapper
d82762430f dist files for v1.4.3 built 2026-02-24 10:26:59 +01:00
Kantine Wrapper
54e5ada03d dist files for v1.4.3 built 2026-02-24 08:37:52 +01:00
Kantine Wrapper
136fe7d355 dist files for v1.4.2 built 2026-02-23 08:24:51 +01:00
10 changed files with 298 additions and 37 deletions

View File

@@ -54,10 +54,13 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
| FR-071 | Die Hervorhebung muss anhand von Menüname und Beschreibung erfolgen (Substring-Matching, case-insensitive). | Niedrig | v1.1.0 |
| FR-072 | Hervorgehobene Menüs müssen ein Tag-Badge mit dem matchenden Schlagwort anzeigen. | Niedrig | v1.2.4 |
| FR-073 | Die Nächste-Woche-Navigation muss die Anzahl der dort gefundenen Highlights als Badge anzeigen. | Niedrig | v1.2.5 |
| **Darstellung & Theming** | | | |
| FR-080 | Das System muss einen hellen und einen dunklen Darstellungsmodus (Light/Dark Theme) unterstützen. | Niedrig | v1.0.1 |
| FR-081 | Die Theme-Präferenz des Benutzers muss persistent gespeichert werden. | Niedrig | v1.0.1 |
| FR-082 | Das System muss beim erstmaligen Laden die Betriebssystem-Präferenz für das Farbschema berücksichtigen. | Niedrig | v1.0.1 |
| **Header UI & Navigation** | | | |
| FR-090 | Die Hauptnavigation (Wochen-Toggles) muss linksbündig neben dem App-Titel positioniert sein. | Niedrig | v1.5.0 |
| FR-091 | Ein dynamisches Alarm-Icon im Header muss den Überwachungsstatus geflaggter Menüs anzeigen (Gelb=Überwachung aktiv, Grün=Menü verfügbar, Versteckt=keine Flags). Der Tooltip muss den Zeitpunkt der letzten Prüfung als relativen String (z.B. "vor 4 Min.") enthalten. | Mittel | v1.5.0 |
| FR-092 | Sobald über den Daten-Refresh erstmals Menüdaten für die Nächste Woche geladen werden, muss der entsprechende Navigation-Button animiert und farblich (Gelb) hervorgehoben werden. Zusätzlich muss einmalig ein Hinweis eingeblendet werden. Bei Klick auf den Button muss die Hervorhebung erlöschen. | Mittel | v1.6.0 |
| **Benutzer-Feedback** | | | |
| FR-090 | Alle benutzerrelevanten Aktionen (Bestellung, Stornierung, Fehler) müssen durch nicht-blockierende Benachrichtigungen (Toasts) bestätigt werden. | Mittel | v1.0.1 |
| FR-091 | Bei einem Verbindungsfehler muss ein Fehlerdialog mit Fallback-Link zur Originalseite angezeigt werden. | Mittel | v1.0.1 |
@@ -69,6 +72,7 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
| FR-112 | Benutzer müssen eine Versionsliste mit Installationslinks einsehen können (Versionsmenü). | Niedrig | v1.3.0 |
| FR-113 | Es muss möglich sein, zu einer älteren Version zurückzukehren (Downgrade). | Niedrig | v1.3.0 |
| FR-114 | Ein Dev-Mode muss es ermöglichen, zwischen stabilen Releases und Entwicklungs-Tags umzuschalten. | Niedrig | v1.3.0 |
| FR-115 | Das Versionsmenü muss Links zur Erstellung von Feature-Requests und Bug-Reports auf GitHub enthalten. | Niedrig | v1.4.4 |
## 3. Nicht-funktionale Anforderungen

Binary file not shown.

View File

@@ -1,3 +1,13 @@
## v1.4.4 (2026-02-24)
- **Feature**: Das Versionsmenü enthält nun direkte Links zu GitHub, um Fehler zu melden oder neue Features vorzuschlagen.
## v1.4.3 (2026-02-24)
- **Fix**: Der Rahmen des "Heute Bestellt" Menüs ist nun konsequent violett (passend zum Glow-Effekt).
## v1.4.2 (2026-02-23)
- **Fix**: Das "Heute Bestellt" Menü leuchtet nun stimmig im Design-Violett statt Blau.
- **Fix**: Abfangen des GitHub API Rate Limit (403) im Versionsdialog mit einer freundlicheren Fehlermeldung, da der User-Agent im Browser nicht manuell gesetzt werden darf.
## v1.4.1 (2026-02-22)
- **UX Verbesserungen**: Bestellhistorie gruppiert nach Jahren und Monaten mittels einklappbarem Akkordeon. Monatssummen integriert und Stati farblich abgehoben (Offen, Abgeschlossen, Storniert).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

23
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -197,6 +197,29 @@ body {
color: white;
}
/* Notification state for Next Week */
.nav-btn.new-week-available {
animation: goldPulse 2s infinite;
border-color: #f59e0b;
color: var(--accent-color);
}
.nav-btn.new-week-available.active {
color: white;
}
@keyframes goldPulse {
0% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
}
}
/* Badge for nav buttons (day count indicator) */
.nav-badge {
background-color: var(--error-color);
@@ -795,8 +818,8 @@ body {
}
.menu-item.today-ordered {
border: 2px solid var(--accent-color);
box-shadow: 0 0 20px rgba(96, 165, 250, 0.4);
border: 2px solid #8b5cf6;
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
border-radius: 8px;
padding: 1rem;
margin: 0 -1rem 1.5rem -1rem;
@@ -808,15 +831,15 @@ body {
@keyframes pulse-glow {
0% {
box-shadow: 0 0 15px rgba(96, 165, 250, 0.3);
box-shadow: 0 0 15px rgba(139, 92, 246, 0.3);
}
50% {
box-shadow: 0 0 25px rgba(96, 165, 250, 0.6);
box-shadow: 0 0 25px rgba(139, 92, 246, 0.6);
}
100% {
box-shadow: 0 0 15px rgba(96, 165, 250, 0.3);
box-shadow: 0 0 15px rgba(139, 92, 246, 0.3);
}
}
@@ -1997,9 +2020,16 @@ body {
<div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="header-left">
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.1</small></h1>
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.4</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div>
</div>
<div class="nav-group" style="margin-left: 1rem;">
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
</div>
<button id="alarm-bell" class="icon-btn hidden" aria-label="Benachrichtigungen" title="Keine beobachteten Menüs" style="margin-left: -0.5rem;">
<span class="material-icons-round" id="alarm-bell-icon" style="color:var(--text-secondary); transition: color 0.3s;">notifications</span>
</button>
</div>
<div class="header-center-wrapper">
<div id="header-week-info" class="header-week-info"></div>
@@ -2015,10 +2045,6 @@ body {
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
<span class="material-icons-round">label</span>
</button>
<div class="nav-group">
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
</div>
<button id="theme-toggle" class="icon-btn" aria-label="Toggle Theme">
<span class="material-icons-round theme-icon">light_mode</span>
</button>
@@ -2136,7 +2162,7 @@ body {
</div>
<div class="modal-body">
<div style="margin-bottom: 1rem;">
<strong>Aktuell:</strong> <span id="version-current">v1.4.1</span>
<strong>Aktuell:</strong> <span id="version-current">v1.4.4</span>
</div>
<div class="dev-toggle">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
@@ -2144,9 +2170,17 @@ body {
<span>Dev-Mode (alle Tags anzeigen)</span>
</label>
</div>
<div id="version-list-container" style="margin-top:1rem;">
<div id="version-list-container" style="margin-top:1rem; max-height: 250px; overflow-y: auto;">
<p style="color:var(--text-secondary);">Lade Versionen...</p>
</div>
<div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border-color); display: flex; flex-direction: column; gap: 0.75rem; font-size: 0.9em;">
<a href="https://github.com/TauNeutrino/kantine-overview/issues" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Melde einen Fehler auf GitHub">
<span class="material-icons-round" style="font-size: 1.2em;">bug_report</span> Fehler melden
</a>
<a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor">
<span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen
</a>
</div>
</div>
</div>
</div>
@@ -2280,6 +2314,7 @@ body {
});
btnNextWeek.addEventListener('click', () => {
btnNextWeek.classList.remove('new-week-available');
if (displayMode !== 'next-week') {
displayMode = 'next-week';
btnNextWeek.classList.add('active');
@@ -2484,11 +2519,24 @@ body {
const progressFill = document.getElementById('history-progress-fill');
const progressText = document.getElementById('history-progress-text');
// Check memory cache first
if (fullOrderHistoryCache) {
renderHistory(fullOrderHistoryCache);
return;
}
// Check local storage cache
const localCache = localStorage.getItem('kantine_history_cache');
if (localCache) {
try {
fullOrderHistoryCache = JSON.parse(localCache);
renderHistory(fullOrderHistoryCache);
return;
} catch (e) {
console.warn('History cache parse error', e);
}
}
if (!authToken) return;
historyContent.innerHTML = '';
@@ -2526,6 +2574,9 @@ body {
}
fullOrderHistoryCache = allOrders;
try {
localStorage.setItem('kantine_history_cache', JSON.stringify(allOrders));
} catch (e) { console.warn('History cache write error', e); }
renderHistory(fullOrderHistoryCache);
} catch (error) {
@@ -2732,6 +2783,7 @@ body {
if (response.ok || response.status === 201) {
showToast(`Bestellt: ${name}`, 'success');
localStorage.removeItem('kantine_history_cache');
await fetchOrders();
} else {
const data = await response.json();
@@ -2761,6 +2813,7 @@ body {
if (response.ok) {
showToast(`Storniert: ${name}`, 'success');
localStorage.removeItem('kantine_history_cache');
await fetchOrders();
} else {
const data = await response.json();
@@ -2777,6 +2830,55 @@ body {
localStorage.setItem('kantine_flags', JSON.stringify([...userFlags]));
}
function updateAlarmBell() {
const bellBtn = document.getElementById('alarm-bell');
const bellIcon = document.getElementById('alarm-bell-icon');
if (!bellBtn || !bellIcon) return;
if (userFlags.size === 0) {
bellBtn.classList.add('hidden');
return;
}
bellBtn.classList.remove('hidden');
// Check if any flagged item is available
let anyAvailable = false;
for (const wk of allWeeks) {
if (!wk.days) continue;
for (const d of wk.days) {
if (!d.items) continue;
for (const item of d.items) {
if (item.available && userFlags.has(item.id)) {
anyAvailable = true;
break;
}
}
if (anyAvailable) break;
}
if (anyAvailable) break;
}
const lastUpdatedStr = localStorage.getItem('kantine_last_updated');
let timeStr = 'Unbekannt';
if (lastUpdatedStr) {
const lastUpdated = new Date(lastUpdatedStr);
const diffMs = Date.now() - lastUpdated.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 60) timeStr = `vor ${diffMins} Min.`;
else timeStr = `vor ${Math.floor(diffMins / 60)} Std.`;
}
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
if (anyAvailable) {
bellIcon.style.color = 'var(--success-color)';
bellIcon.style.textShadow = '0 0 10px rgba(16, 185, 129, 0.4)';
} else {
bellIcon.style.color = 'var(--warning-color)';
bellIcon.style.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
}
}
function toggleFlag(date, articleId, name, cutoff) {
const id = `${date}_${articleId}`;
if (userFlags.has(id)) {
@@ -2790,6 +2892,7 @@ body {
}
}
saveFlags();
updateAlarmBell();
renderVisibleWeeks();
}
@@ -3158,6 +3261,7 @@ body {
updateAuthUI(); // This will trigger fetchOrders if logged in
renderVisibleWeeks();
updateNextWeekBadge();
updateAlarmBell();
progressMessage.textContent = 'Fertig!';
setTimeout(() => progressModal.classList.add('hidden'), 500);
@@ -3315,6 +3419,14 @@ body {
badge.classList.add('has-highlights');
}
// FR-092: Highlight Next Week Button when new data arrives
const storageKey = `kantine_notified_nextweek_${nextYear}_${nextWeek}`;
if (!localStorage.getItem(storageKey)) {
localStorage.setItem(storageKey, 'true');
btnNextWeek.classList.add('new-week-available');
showToast('Neue Menüdaten für nächste Woche verfügbar!', 'info');
}
} else if (badge) {
badge.remove();
}
@@ -3699,7 +3811,12 @@ body {
: `${GITHUB_API}/releases?per_page=20`;
const resp = await fetch(endpoint, { headers: githubHeaders() });
if (!resp.ok) throw new Error(`GitHub API ${resp.status}`);
if (!resp.ok) {
if (resp.status === 403) {
throw new Error('API Rate Limit erreicht (403). Bitte später erneut versuchen.');
}
throw new Error(`GitHub API ${resp.status}`);
}
const data = await resp.json();
// Normalize to common format: { tag, name, url, body }
@@ -3716,7 +3833,7 @@ body {
// Periodic update check (runs on init + every hour)
async function checkForUpdates() {
const currentVersion = 'v1.4.1';
const currentVersion = 'v1.4.4';
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
try {
@@ -3757,7 +3874,7 @@ body {
const modal = document.getElementById('version-modal');
const container = document.getElementById('version-list-container');
const devToggle = document.getElementById('dev-mode-toggle');
const currentVersion = 'v1.4.1';
const currentVersion = 'v1.4.4';
if (!modal) return;
modal.classList.remove('hidden');

View File

@@ -74,6 +74,13 @@
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">{{VERSION}}</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div>
</div>
<div class="nav-group" style="margin-left: 1rem;">
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
</div>
<button id="alarm-bell" class="icon-btn hidden" aria-label="Benachrichtigungen" title="Keine beobachteten Menüs" style="margin-left: -0.5rem;">
<span class="material-icons-round" id="alarm-bell-icon" style="color:var(--text-secondary); transition: color 0.3s;">notifications</span>
</button>
</div>
<div class="header-center-wrapper">
<div id="header-week-info" class="header-week-info"></div>
@@ -89,10 +96,6 @@
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
<span class="material-icons-round">label</span>
</button>
<div class="nav-group">
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
</div>
<button id="theme-toggle" class="icon-btn" aria-label="Toggle Theme">
<span class="material-icons-round theme-icon">light_mode</span>
</button>
@@ -218,9 +221,17 @@
<span>Dev-Mode (alle Tags anzeigen)</span>
</label>
</div>
<div id="version-list-container" style="margin-top:1rem;">
<div id="version-list-container" style="margin-top:1rem; max-height: 250px; overflow-y: auto;">
<p style="color:var(--text-secondary);">Lade Versionen...</p>
</div>
<div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border-color); display: flex; flex-direction: column; gap: 0.75rem; font-size: 0.9em;">
<a href="https://github.com/TauNeutrino/kantine-overview/issues" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Melde einen Fehler auf GitHub">
<span class="material-icons-round" style="font-size: 1.2em;">bug_report</span> Fehler melden
</a>
<a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor">
<span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen
</a>
</div>
</div>
</div>
</div>
@@ -354,6 +365,7 @@
});
btnNextWeek.addEventListener('click', () => {
btnNextWeek.classList.remove('new-week-available');
if (displayMode !== 'next-week') {
displayMode = 'next-week';
btnNextWeek.classList.add('active');
@@ -558,11 +570,24 @@
const progressFill = document.getElementById('history-progress-fill');
const progressText = document.getElementById('history-progress-text');
// Check memory cache first
if (fullOrderHistoryCache) {
renderHistory(fullOrderHistoryCache);
return;
}
// Check local storage cache
const localCache = localStorage.getItem('kantine_history_cache');
if (localCache) {
try {
fullOrderHistoryCache = JSON.parse(localCache);
renderHistory(fullOrderHistoryCache);
return;
} catch (e) {
console.warn('History cache parse error', e);
}
}
if (!authToken) return;
historyContent.innerHTML = '';
@@ -600,6 +625,9 @@
}
fullOrderHistoryCache = allOrders;
try {
localStorage.setItem('kantine_history_cache', JSON.stringify(allOrders));
} catch (e) { console.warn('History cache write error', e); }
renderHistory(fullOrderHistoryCache);
} catch (error) {
@@ -806,6 +834,7 @@
if (response.ok || response.status === 201) {
showToast(`Bestellt: ${name}`, 'success');
localStorage.removeItem('kantine_history_cache');
await fetchOrders();
} else {
const data = await response.json();
@@ -835,6 +864,7 @@
if (response.ok) {
showToast(`Storniert: ${name}`, 'success');
localStorage.removeItem('kantine_history_cache');
await fetchOrders();
} else {
const data = await response.json();
@@ -851,6 +881,55 @@
localStorage.setItem('kantine_flags', JSON.stringify([...userFlags]));
}
function updateAlarmBell() {
const bellBtn = document.getElementById('alarm-bell');
const bellIcon = document.getElementById('alarm-bell-icon');
if (!bellBtn || !bellIcon) return;
if (userFlags.size === 0) {
bellBtn.classList.add('hidden');
return;
}
bellBtn.classList.remove('hidden');
// Check if any flagged item is available
let anyAvailable = false;
for (const wk of allWeeks) {
if (!wk.days) continue;
for (const d of wk.days) {
if (!d.items) continue;
for (const item of d.items) {
if (item.available && userFlags.has(item.id)) {
anyAvailable = true;
break;
}
}
if (anyAvailable) break;
}
if (anyAvailable) break;
}
const lastUpdatedStr = localStorage.getItem('kantine_last_updated');
let timeStr = 'Unbekannt';
if (lastUpdatedStr) {
const lastUpdated = new Date(lastUpdatedStr);
const diffMs = Date.now() - lastUpdated.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 60) timeStr = `vor ${diffMins} Min.`;
else timeStr = `vor ${Math.floor(diffMins / 60)} Std.`;
}
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
if (anyAvailable) {
bellIcon.style.color = 'var(--success-color)';
bellIcon.style.textShadow = '0 0 10px rgba(16, 185, 129, 0.4)';
} else {
bellIcon.style.color = 'var(--warning-color)';
bellIcon.style.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
}
}
function toggleFlag(date, articleId, name, cutoff) {
const id = `${date}_${articleId}`;
if (userFlags.has(id)) {
@@ -864,6 +943,7 @@
}
}
saveFlags();
updateAlarmBell();
renderVisibleWeeks();
}
@@ -1232,6 +1312,7 @@
updateAuthUI(); // This will trigger fetchOrders if logged in
renderVisibleWeeks();
updateNextWeekBadge();
updateAlarmBell();
progressMessage.textContent = 'Fertig!';
setTimeout(() => progressModal.classList.add('hidden'), 500);
@@ -1389,6 +1470,14 @@
badge.classList.add('has-highlights');
}
// FR-092: Highlight Next Week Button when new data arrives
const storageKey = `kantine_notified_nextweek_${nextYear}_${nextWeek}`;
if (!localStorage.getItem(storageKey)) {
localStorage.setItem(storageKey, 'true');
btnNextWeek.classList.add('new-week-available');
showToast('Neue Menüdaten für nächste Woche verfügbar!', 'info');
}
} else if (badge) {
badge.remove();
}
@@ -1773,7 +1862,12 @@
: `${GITHUB_API}/releases?per_page=20`;
const resp = await fetch(endpoint, { headers: githubHeaders() });
if (!resp.ok) throw new Error(`GitHub API ${resp.status}`);
if (!resp.ok) {
if (resp.status === 403) {
throw new Error('API Rate Limit erreicht (403). Bitte später erneut versuchen.');
}
throw new Error(`GitHub API ${resp.status}`);
}
const data = await resp.json();
// Normalize to common format: { tag, name, url, body }

View File

@@ -186,6 +186,29 @@ body {
color: white;
}
/* Notification state for Next Week */
.nav-btn.new-week-available {
animation: goldPulse 2s infinite;
border-color: #f59e0b;
color: var(--accent-color);
}
.nav-btn.new-week-available.active {
color: white;
}
@keyframes goldPulse {
0% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
}
}
/* Badge for nav buttons (day count indicator) */
.nav-badge {
background-color: var(--error-color);
@@ -784,8 +807,8 @@ body {
}
.menu-item.today-ordered {
border: 2px solid var(--accent-color);
box-shadow: 0 0 20px rgba(96, 165, 250, 0.4);
border: 2px solid #8b5cf6;
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
border-radius: 8px;
padding: 1rem;
margin: 0 -1rem 1.5rem -1rem;
@@ -797,15 +820,15 @@ body {
@keyframes pulse-glow {
0% {
box-shadow: 0 0 15px rgba(96, 165, 250, 0.3);
box-shadow: 0 0 15px rgba(139, 92, 246, 0.3);
}
50% {
box-shadow: 0 0 25px rgba(96, 165, 250, 0.6);
box-shadow: 0 0 25px rgba(139, 92, 246, 0.6);
}
100% {
box-shadow: 0 0 15px rgba(96, 165, 250, 0.3);
box-shadow: 0 0 15px rgba(139, 92, 246, 0.3);
}
}

View File

@@ -1 +1 @@
v1.4.1
v1.4.4