Merge branch 'main' of github.com:rafaelfoster/oikos

This commit is contained in:
Rafael Foster
2026-04-26 07:50:59 -03:00
16 changed files with 275 additions and 120 deletions
-11
View File
@@ -1,11 +0,0 @@
window.addEventListener('DOMContentLoaded', () => {
window.ui = window.SwaggerUIBundle({
url: '/openapi.json',
dom_id: '#swagger-ui',
deepLinking: true,
docExpansion: 'list',
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
});
});
-37
View File
@@ -1,37 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Oikos API Docs</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css">
<style>
body { margin: 0; background: #f6f8fb; }
.docs-topbar {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
background: #0f172a;
color: #fff;
font: 14px/1.4 system-ui, sans-serif;
}
.docs-links { display: flex; gap: 12px; flex-wrap: wrap; }
.docs-links a { color: #93c5fd; text-decoration: none; }
.docs-links a:hover { text-decoration: underline; }
</style>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" defer></script>
<script src="/doc-assets/swagger-init.js" defer></script>
</head>
<body>
<header class="docs-topbar">
<strong>Oikos API Documentation</strong>
<nav class="docs-links">
<a href="/openapi.json" target="_blank" rel="noreferrer">openapi.json</a>
<a href="/openapi.json?download=1">Download JSON</a>
</nav>
</header>
<div id="swagger-ui"></div>
</body>
</html>
+22 -22
View File
@@ -631,28 +631,28 @@
"currencyLabel": "Währung",
"currencyHint": "Legt die Währung für den gesamten Budget-Bereich fest.",
"currencySaved": "Währung gespeichert.",
"apiTokensTitle": "API Tokens",
"apiTokensCardTitle": "Access Tokens",
"apiTokensHint": "Create API tokens for external integrations. The full token is shown only once after creation.",
"apiTokenNameLabel": "Token name",
"apiTokenExpiresLabel": "Expiration date",
"apiTokenExpiresHint": "Leave empty to create a token without expiration.",
"apiTokenCreatedLabel": "New API token",
"apiTokenCreatedHint": "Store this token securely. It cannot be shown again.",
"apiTokenCreate": "Create token",
"apiTokenInvalidExpiration": "Please enter a valid expiration date.",
"apiTokenCreatedToast": "API token created.",
"apiTokenRevokedToast": "API token revoked.",
"apiTokenRevokeConfirm": "Revoke API token \"{{name}}\"?",
"apiTokenRevoke": "Revoke token",
"apiTokenRevoked": "Revoked",
"apiTokenExpired": "Expired",
"apiTokenActive": "Active",
"apiTokenPrefix": "Prefix",
"apiTokenExpires": "Expires",
"apiTokenNeverExpires": "No expiration",
"apiTokenLastUsed": "Last used",
"apiTokenNeverUsed": "Never used",
"apiTokensTitle": "API-Tokens",
"apiTokensCardTitle": "Zugriffstoken",
"apiTokensHint": "Erstelle API-Tokens für externe Integrationen. Der vollständige Token wird nach der Erstellung nur einmal angezeigt.",
"apiTokenNameLabel": "Tokenname",
"apiTokenExpiresLabel": "Ablaufdatum",
"apiTokenExpiresHint": "Leer lassen, um einen Token ohne Ablaufdatum zu erstellen.",
"apiTokenCreatedLabel": "Neuer API-Token",
"apiTokenCreatedHint": "Speichere diesen Token sicher. Er kann nicht erneut angezeigt werden.",
"apiTokenCreate": "Token erstellen",
"apiTokenInvalidExpiration": "Bitte gib ein gültiges Ablaufdatum ein.",
"apiTokenCreatedToast": "API-Token erstellt.",
"apiTokenRevokedToast": "API-Token widerrufen.",
"apiTokenRevokeConfirm": "API-Token \"{{name}}\" widerrufen?",
"apiTokenRevoke": "Token widerrufen",
"apiTokenRevoked": "Widerrufen",
"apiTokenExpired": "Abgelaufen",
"apiTokenActive": "Aktiv",
"apiTokenPrefix": "Präfix",
"apiTokenExpires": "Läuft ab",
"apiTokenNeverExpires": "Kein Ablaufdatum",
"apiTokenLastUsed": "Zuletzt verwendet",
"apiTokenNeverUsed": "Nie verwendet",
"ics": {
"title": "ICS-Abonnements",
"add": "Abonnement hinzufügen",
+13 -5
View File
@@ -546,7 +546,7 @@ function openCustomizeModal(currentConfig, onSave) {
const isFirst = i === 0;
const isLast = i === draft.length - 1;
return `
<div class="customize-row" data-id="${w.id}">
<div class="customize-row" data-id="${esc(w.id)}" style="view-transition-name: widget-row-${esc(w.id)}">
<label class="customize-row__toggle">
<input type="checkbox" class="customize-row__check" data-id="${w.id}"
${w.visible ? 'checked' : ''} aria-label="${widgetLabel(w.id)}">
@@ -584,10 +584,18 @@ function openCustomizeModal(currentConfig, onSave) {
function rebuildList() {
const list = panel.querySelector('#customize-list');
if (!list) return;
list.replaceChildren();
list.insertAdjacentHTML('beforeend', buildRows());
if (window.lucide) window.lucide.createIcons({ el: list });
wireRows();
const doRebuild = () => {
list.replaceChildren();
list.insertAdjacentHTML('beforeend', buildRows());
if (window.lucide) window.lucide.createIcons({ el: list });
wireRows();
};
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (document.startViewTransition && !reducedMotion) {
document.startViewTransition(doRebuild);
} else {
doRebuild();
}
}
function wireRows() {
+11 -11
View File
@@ -267,25 +267,25 @@
.widget__body {
flex: 1;
padding: var(--space-2) var(--space-4) var(--space-3);
padding: var(--space-2) var(--space-4) var(--space-4);
}
.widget__empty {
padding: var(--space-5) var(--space-4);
text-align: center;
padding: var(--space-3) var(--space-4);
color: var(--color-text-tertiary);
font-size: var(--text-sm);
display: flex;
flex-direction: column;
flex-direction: row;
align-items: center;
gap: var(--space-1);
gap: var(--space-2);
}
.widget__empty .empty-state__icon {
width: 28px;
height: 28px;
width: 20px;
height: 20px;
flex-shrink: 0;
color: var(--color-text-tertiary);
margin-bottom: var(--space-1);
opacity: 0.6;
}
/* Widget hover lift (desktop) */
@@ -1125,7 +1125,7 @@
}
.customize-row:hover {
background-color: var(--color-surface-raised);
background-color: var(--color-surface-hover);
}
.customize-row__toggle {
@@ -1212,8 +1212,8 @@
}
.customize-row__btn:hover:not(:disabled) {
background-color: var(--color-surface-raised);
color: var(--color-text);
background-color: var(--color-surface-hover);
color: var(--color-text-primary);
}
.customize-row__btn:disabled {
+77 -6
View File
@@ -142,8 +142,8 @@
display: flex;
flex-direction: column;
z-index: var(--z-nav);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
backdrop-filter: var(--blur-md) saturate(180%);
-webkit-backdrop-filter: var(--blur-md) saturate(180%);
}
/* ── Items-Reihe ── */
@@ -171,8 +171,8 @@
inset: 0;
background-color: var(--color-overlay);
z-index: calc(var(--z-nav) + 1);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
backdrop-filter: var(--blur-2xs);
-webkit-backdrop-filter: var(--blur-2xs);
}
.more-backdrop--visible {
@@ -1583,8 +1583,8 @@
top: 0;
z-index: var(--z-sticky);
background-color: color-mix(in srgb, var(--color-bg) 90%, transparent);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: var(--blur-sm);
-webkit-backdrop-filter: var(--blur-sm);
padding-bottom: var(--space-3);
margin-bottom: var(--space-4);
}
@@ -1906,6 +1906,31 @@
border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
/* --------------------------------------------------------
* Reduced Transparency — layout.css Fallbacks
* glass.css deckt Glass-Komponenten ab; diese drei Elemente
* nutzen backdrop-filter außerhalb von @supports und brauchen
* eigene Fallbacks.
* -------------------------------------------------------- */
@media (prefers-reduced-transparency: reduce) {
.nav-bottom {
background-color: var(--color-surface);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.more-backdrop {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.sticky-header {
background-color: var(--color-bg);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
/* --------------------------------------------------------
* Print-Styles
* -------------------------------------------------------- */
@@ -2000,6 +2025,52 @@ button svg { pointer-events: none; }
/* Textarea: vertikale Größenänderung ist nutzbar */
textarea.input { resize: vertical; }
/* --------------------------------------------------------
* High Contrast (prefers-contrast: more)
* Tokens in tokens.css stellen bereits opake Glass-Werte bereit.
* Hier kommen komponentenspezifische Korrekturen hinzu.
* -------------------------------------------------------- */
@media (prefers-contrast: more) {
/* Fokus: dicker und weiter versetzt */
:focus-visible {
outline-width: 3px;
outline-offset: 4px;
}
/* Ghost- und Secondary-Buttons: explizite Umrandung */
.btn--ghost,
.btn--secondary {
border: 1.5px solid currentColor;
}
.btn--ghost:hover {
background-color: var(--color-surface-3);
}
/* Karten: Schatten entfernen, Border verstärken */
.card {
box-shadow: none;
border: 1px solid var(--color-border);
}
.card--flat {
border-color: var(--color-text-secondary);
}
/* Formulareingaben: kräftigere Border */
.input,
.form-input {
border-color: var(--color-text-primary);
border-width: 2px;
}
/* Aktiver Nav-Eintrag: zusätzliche Unterstreichung als farb-unabhängiger Indikator */
.nav-item[aria-current="page"] {
text-decoration: underline;
text-underline-offset: 3px;
}
}
/* --------------------------------------------------------
* Windows High Contrast / Forced Colors
* -------------------------------------------------------- */
+4 -4
View File
@@ -19,7 +19,7 @@
background: var(--color-priority-urgent);
color: var(--color-text-on-accent);
font-size: var(--text-2xs);
font-weight: 700;
font-weight: var(--font-weight-bold);
line-height: 16px;
text-align: center;
pointer-events: none;
@@ -37,7 +37,7 @@
display: flex;
align-items: center;
flex-shrink: 0;
color: var(--color-accent);
color: var(--module-reminders);
}
.toast__reminder-text {
@@ -57,7 +57,7 @@
.toast__reminder-text span {
font-size: var(--text-sm, 0.875rem);
font-weight: 500;
font-weight: var(--font-weight-medium);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -79,7 +79,7 @@
.reminder-section__title {
font-size: var(--text-sm, 0.875rem);
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
+22 -7
View File
@@ -172,8 +172,10 @@
--module-birthdays: var(--_module-birthdays); /* Rose - Geburtstage */
--_module-budget: #0F766E;
--module-budget: var(--_module-budget); /* Teal-700 - Finanzen, Stabilität */
--_module-settings: #6E7781;
--module-settings: var(--_module-settings); /* Grau - Konfiguration */
--_module-settings: #6E7781;
--module-settings: var(--_module-settings); /* Grau - Konfiguration */
--_module-reminders: #0E7490;
--module-reminders: var(--_module-reminders); /* Cyan-700 - Erinnerungen, 6.3:1 auf weiß — WCAG AA */
/* --------------------------------------------------------
* 5. Farben - Mahlzeit-Typen
@@ -246,6 +248,10 @@
--shadow-md: var(--_shadow-md);
--_shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.04);
--shadow-lg: var(--_shadow-lg);
--_shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.18), 0 4px 12px rgba(0, 0, 0, 0.06);
--shadow-xl: var(--_shadow-xl);
--_shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.08);
--shadow-xs: var(--_shadow-xs);
/* --------------------------------------------------------
* 9. Border-Radien
@@ -320,7 +326,7 @@
--nav-height-mobile: 56px;
--nav-bottom-height: calc(var(--nav-height-mobile) + 12px); /* scroll (56px) + dots-indicator (12px) */
--sidebar-width: 56px; /* collapsed icon-only (10241279px) */
--sidebar-width-expanded: 220px; /* full sidebar (1280px+), max 240px laut Spec */
--sidebar-width-expanded: 220px; /* full sidebar (1440px+), max 240px laut Spec */
--content-max-width: 1280px;
--content-max-width-narrow: 720px;
--cal-hour-height: 56px;
@@ -403,6 +409,7 @@
--glass-tint-strength: var(--_glass-tint-strength);
/* b) Blur-Stufen */
--blur-2xs: blur(2px);
--blur-xs: blur(4px);
--blur-sm: blur(8px);
--blur-md: blur(16px);
@@ -523,8 +530,9 @@
--_module-notes: #FCD34D;
--_module-contacts: #60A5FA;
--_module-birthdays: #FB7185;
--_module-budget: #2DD4BF;
--_module-settings: #94A3B8;
--_module-budget: #2DD4BF;
--_module-settings: #94A3B8;
--_module-reminders: #22D3EE; /* Cyan-400 */
--_meal-breakfast: #F59E0B;
--_meal-breakfast-light: #332400;
@@ -545,6 +553,8 @@
--_shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
--_shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35);
--_shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.45);
--_shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.60), 0 4px 12px rgba(0, 0, 0, 0.25);
--_shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.30);
--_glass-bg: rgba(28, 28, 26, 0.75);
--_glass-bg-hover: rgba(38, 38, 36, 0.82);
@@ -625,8 +635,9 @@
--_module-notes: #FCD34D;
--_module-contacts: #60A5FA;
--_module-birthdays: #FB7185;
--_module-budget: #2DD4BF; /* Teal-400 */
--_module-settings: #94A3B8;
--_module-budget: #2DD4BF; /* Teal-400 */
--_module-settings: #94A3B8;
--_module-reminders: #22D3EE; /* Cyan-400 */
/* Mahlzeit-Typ */
--_meal-breakfast: #F59E0B;
@@ -651,6 +662,8 @@
--_shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
--_shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35);
--_shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.45);
--_shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.60), 0 4px 12px rgba(0, 0, 0, 0.25);
--_shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.30);
/* Glass */
--_glass-bg: rgba(28, 28, 26, 0.75);
@@ -688,6 +701,7 @@
--glass-highlight: transparent;
--glass-highlight-subtle: transparent;
--glass-tint-strength: 0%;
--blur-2xs: blur(0px);
--blur-xs: blur(0px);
--blur-sm: blur(0px);
--blur-md: blur(0px);
@@ -712,6 +726,7 @@
--glass-border-subtle: var(--color-text-secondary);
--glass-highlight: transparent;
--glass-highlight-subtle: transparent;
--blur-2xs: blur(0px);
--blur-xs: blur(0px);
--blur-sm: blur(0px);
--blur-md: blur(0px);