feat: Light Mode mit System-Erkennung und konfigurierbarem Erscheinungsbild.

Stellt hell/dunkel für Ocean, Material und Cupertino bereit, migriert die Kern-UI auf CSS-Variablen und ergänzt die Einstellungen inkl. i18n und Select-Kontrast.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 17:26:50 +02:00
co-authored by Cursor
parent 42736fedf3
commit 2f6c668ca4
8 changed files with 695 additions and 364 deletions
+168 -332
View File
@@ -1,10 +1,11 @@
/* Kapteins Daagbok App styling */ /* Kapteins Daagbok App styling */
body { body {
background: radial-gradient(circle at center, #1b264f 0%, #0b0c10 100%); background: var(--app-body-bg);
min-height: 100vh; min-height: 100vh;
margin: 0; margin: 0;
font-family: system-ui, -apple-system, sans-serif; font-family: system-ui, -apple-system, sans-serif;
color: var(--app-text);
} }
#root:has(.auth-screen) { #root:has(.auth-screen) {
@@ -27,15 +28,15 @@ body {
/* Glassmorphism Auth Card */ /* Glassmorphism Auth Card */
.auth-card { .auth-card {
background: rgba(11, 12, 16, 0.75); background: var(--app-surface);
backdrop-filter: blur(20px); backdrop-filter: var(--app-backdrop);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: var(--app-backdrop);
border: 1px solid rgba(212, 175, 55, 0.25); border: 1px solid var(--app-border);
border-radius: 20px; border-radius: var(--app-radius-card);
padding: 40px; padding: 40px;
width: 450px; width: 450px;
max-width: 90%; max-width: 90%;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(255, 255, 255, 0.05); box-shadow: var(--app-shadow);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -65,8 +66,8 @@ body {
.auth-brand h1 { .auth-brand h1 {
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
color: #f8fafc; color: var(--app-text-heading);
background: linear-gradient(135deg, #fef08a 0%, #d97706 100%); background: var(--app-accent-gradient);
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
@@ -77,7 +78,7 @@ body {
.auth-brand .tagline { .auth-brand .tagline {
font-size: 14.5px; font-size: 14.5px;
color: #94a3b8; color: var(--app-text-muted);
margin: 0; margin: 0;
line-height: 1.45; line-height: 1.45;
} }
@@ -96,31 +97,41 @@ body {
.input-text { .input-text {
width: 100%; width: 100%;
background: rgba(11, 12, 16, 0.85); background: var(--app-input-bg);
border: 1px solid rgba(148, 163, 184, 0.25); border: 1px solid var(--app-input-border);
border-radius: 10px; border-radius: var(--app-radius-input);
padding: 14px 16px; padding: 14px 16px;
font-size: 16px; font-size: 16px;
color: #f1f5f9; color: var(--app-input-text);
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.input-text:focus { .input-text:focus {
border-color: #d97706; border-color: var(--app-accent);
box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2); box-shadow: 0 0 0 3px var(--app-accent-focus-ring);
background: #0b0c10; background: var(--app-input-bg-focus);
}
select.input-text {
color-scheme: inherit;
cursor: pointer;
}
select.input-text option {
background-color: var(--app-input-bg);
color: var(--app-input-text);
} }
.input-textarea { .input-textarea {
width: 100%; width: 100%;
background: rgba(11, 12, 16, 0.85); background: var(--app-input-bg);
border: 1px solid rgba(148, 163, 184, 0.25); border: 1px solid var(--app-input-border);
border-radius: 10px; border-radius: var(--app-radius-input);
padding: 14px; padding: 14px;
font-size: 15px; font-size: 15px;
color: #f1f5f9; color: var(--app-input-text);
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;
resize: none; resize: none;
@@ -129,9 +140,9 @@ body {
} }
.input-textarea:focus { .input-textarea:focus {
border-color: #d97706; border-color: var(--app-accent);
box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2); box-shadow: 0 0 0 3px var(--app-accent-focus-ring);
background: #0b0c10; background: var(--app-input-bg-focus);
} }
.auth-submit-actions { .auth-submit-actions {
@@ -148,7 +159,7 @@ body {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
border: none; border: none;
border-radius: 10px; border-radius: var(--app-radius-btn);
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.25s ease;
display: flex; display: flex;
@@ -158,14 +169,15 @@ body {
} }
.btn.primary { .btn.primary {
background: linear-gradient(135deg, #fbbf24 0%, #d97706 100%); background-color: var(--app-accent);
color: #0b0c10; background-image: var(--app-accent-gradient);
box-shadow: 0 4px 15px rgba(217, 119, 6, 0.3); color: var(--app-btn-primary-text);
box-shadow: 0 4px 15px var(--app-accent-focus-ring);
} }
.btn.primary:hover:not(:disabled) { .btn.primary:hover:not(:disabled) {
transform: translateY(-1.5px); transform: translateY(-1.5px);
box-shadow: 0 6px 20px rgba(217, 119, 6, 0.45); filter: brightness(1.05);
} }
.btn.primary:active:not(:disabled) { .btn.primary:active:not(:disabled) {
@@ -173,14 +185,13 @@ body {
} }
.btn.secondary { .btn.secondary {
background: rgba(255, 255, 255, 0.05); background: var(--app-btn-secondary-bg);
color: #e2e8f0; color: var(--app-btn-secondary-text);
border: 1px solid rgba(255, 255, 255, 0.12); border: 1px solid var(--app-btn-secondary-border);
} }
.btn.secondary:hover:not(:disabled) { .btn.secondary:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.08); background: var(--app-btn-secondary-hover-bg);
color: #ffffff;
} }
.btn:disabled { .btn:disabled {
@@ -213,15 +224,15 @@ body {
.auth-card h2 { .auth-card h2 {
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
color: #f8fafc; color: var(--app-text-heading);
margin: 0; margin: 0;
} }
.recovery-warning { .recovery-warning {
font-size: 13.5px; font-size: 13.5px;
color: #f43f5e; color: var(--app-warning-text);
background: rgba(244, 63, 94, 0.08); background: var(--app-warning-bg);
border: 1px solid rgba(244, 63, 94, 0.2); border: 1px solid var(--app-warning-border);
border-radius: 8px; border-radius: 8px;
padding: 12px 14px; padding: 12px 14px;
line-height: 145%; line-height: 145%;
@@ -238,18 +249,18 @@ body {
} }
.phrase-word { .phrase-word {
background: rgba(11, 12, 16, 0.9); background: var(--app-input-bg);
border: 1px solid rgba(255, 255, 255, 0.06); border: 1px solid var(--app-border-subtle);
border-radius: 6px; border-radius: 6px;
padding: 12px 8px; padding: 12px 8px;
font-size: 14.5px; font-size: 14.5px;
color: #f1f5f9; color: var(--app-text);
text-align: center; text-align: center;
font-family: ui-monospace, monospace; font-family: ui-monospace, monospace;
} }
.word-num { .word-num {
color: #d97706; color: var(--app-accent);
font-size: 11px; font-size: 11px;
margin-right: 4px; margin-right: 4px;
} }
@@ -262,9 +273,9 @@ body {
.auth-error { .auth-error {
width: 100%; width: 100%;
background: rgba(244, 63, 94, 0.08); background: var(--app-error-bg);
color: #fda4af; color: var(--app-error-text);
border-left: 3px solid #f43f5e; border-left: 3px solid var(--app-error-border);
padding: 10px 12px; padding: 10px 12px;
font-size: 14px; font-size: 14px;
border-radius: 4px; border-radius: 4px;
@@ -275,7 +286,7 @@ body {
.auth-footer { .auth-footer {
margin-top: 30px; margin-top: 30px;
width: 100%; width: 100%;
border-top: 1px solid rgba(255, 255, 255, 0.06); border-top: 1px solid var(--app-divider);
padding-top: 20px; padding-top: 20px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -285,7 +296,7 @@ body {
.btn-icon-text { .btn-icon-text {
background: none; background: none;
border: none; border: none;
color: #94a3b8; color: var(--app-text-muted);
font-size: 13.5px; font-size: 13.5px;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -298,16 +309,16 @@ body {
} }
.btn-icon-text:hover { .btn-icon-text:hover {
color: #d97706; color: var(--app-accent);
background: rgba(217, 119, 6, 0.06); background: var(--app-accent-bg);
} }
.btn-icon-text.link-sec { .btn-icon-text.link-sec {
color: #4b5563; color: var(--app-text-subtle);
} }
.btn-icon-text.link-sec:hover { .btn-icon-text.link-sec:hover {
color: #9ca3af; color: var(--app-text-muted);
} }
/* Dashboard Mock Layout (for verified state) */ /* Dashboard Mock Layout (for verified state) */
@@ -346,7 +357,7 @@ body {
flex-direction: column; flex-direction: column;
padding: 24px; padding: 24px;
box-sizing: border-box; box-sizing: border-box;
color: #f1f5f9; color: var(--app-text);
} }
.dashboard-header { .dashboard-header {
@@ -355,7 +366,7 @@ body {
align-items: center; align-items: center;
margin-bottom: 32px; margin-bottom: 32px;
padding-bottom: 16px; padding-bottom: 16px;
border-bottom: 1px solid rgba(212, 175, 55, 0.15); border-bottom: 1px solid var(--app-header-border);
} }
.header-brand { .header-brand {
@@ -365,15 +376,15 @@ body {
} }
.header-logo { .header-logo {
color: #fbbf24; color: var(--app-accent-light);
filter: drop-shadow(0 0 8px rgba(251, 191, 36, 0.4)); filter: drop-shadow(0 0 8px var(--app-accent-focus-ring));
} }
.header-brand h1 { .header-brand h1 {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
margin: 0; margin: 0;
background: linear-gradient(135deg, #fef08a 0%, #d97706 100%); background: var(--app-accent-gradient);
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
@@ -381,7 +392,7 @@ body {
.header-brand .subtitle { .header-brand .subtitle {
font-size: 12px; font-size: 12px;
color: #94a3b8; color: var(--app-text-muted);
margin: 2px 0 0 0; margin: 2px 0 0 0;
} }
@@ -420,15 +431,15 @@ body {
font-size: 13px; font-size: 13px;
padding: 6px 12px; padding: 6px 12px;
border-radius: 20px; border-radius: 20px;
background: rgba(255, 255, 255, 0.05); background: var(--app-btn-secondary-bg);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid var(--app-btn-secondary-border);
color: #e2e8f0; color: var(--app-btn-secondary-text);
} }
.btn-icon { .btn-icon {
background: rgba(255, 255, 255, 0.05); background: var(--app-icon-btn-bg);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid var(--app-icon-btn-border);
color: #94a3b8; color: var(--app-text-muted);
width: 36px; width: 36px;
height: 36px; height: 36px;
border-radius: 50%; border-radius: 50%;
@@ -440,9 +451,9 @@ body {
} }
.btn-icon:hover { .btn-icon:hover {
background: rgba(217, 119, 6, 0.1); background: var(--app-accent-bg);
border-color: #d97706; border-color: var(--app-accent);
color: #fbbf24; color: var(--app-accent-light);
} }
.btn-icon.logout:hover { .btn-icon.logout:hover {
@@ -511,11 +522,11 @@ body {
} }
.create-section { .create-section {
background: rgba(11, 12, 16, 0.6); background: var(--app-surface-alt);
backdrop-filter: blur(20px); backdrop-filter: var(--app-backdrop);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: var(--app-backdrop);
border: 1px solid rgba(212, 175, 55, 0.2); border: 1px solid var(--app-border-muted);
border-radius: 16px; border-radius: var(--app-radius-card);
padding: 24px; padding: 24px;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -524,7 +535,7 @@ body {
font-size: 18px; font-size: 18px;
margin-top: 0; margin-top: 0;
margin-bottom: 20px; margin-bottom: 20px;
color: #f8fafc; color: var(--app-text-heading);
} }
.dashboard-form { .dashboard-form {
@@ -574,13 +585,13 @@ body {
.section-title-bar h2 { .section-title-bar h2 {
font-size: 20px; font-size: 20px;
margin: 0; margin: 0;
color: #f8fafc; color: var(--app-text-heading);
} }
.btn-refresh { .btn-refresh {
background: none; background: none;
border: none; border: none;
color: #94a3b8; color: var(--app-text-muted);
cursor: pointer; cursor: pointer;
padding: 6px; padding: 6px;
border-radius: 4px; border-radius: 4px;
@@ -591,7 +602,7 @@ body {
} }
.btn-refresh:hover { .btn-refresh:hover {
color: #fbbf24; color: var(--app-accent-light);
} }
.spin { .spin {
@@ -619,11 +630,11 @@ body {
} }
.logbook-card { .logbook-card {
background: rgba(11, 12, 16, 0.6); background: var(--app-surface-alt);
backdrop-filter: blur(15px); backdrop-filter: var(--app-backdrop);
-webkit-backdrop-filter: blur(15px); -webkit-backdrop-filter: var(--app-backdrop);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid var(--app-border-subtle);
border-radius: 12px; border-radius: var(--app-radius-card);
padding: 20px; padding: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -635,14 +646,14 @@ body {
.logbook-card:hover { .logbook-card:hover {
transform: translateY(-2px); transform: translateY(-2px);
border-color: rgba(212, 175, 55, 0.4); border-color: var(--app-border);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); box-shadow: var(--app-card-shadow);
background: rgba(11, 12, 16, 0.75); background: var(--app-surface-hover);
} }
.card-icon { .card-icon {
background: rgba(217, 119, 6, 0.1); background: var(--app-accent-bg);
color: #fbbf24; color: var(--app-accent-light);
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 10px; border-radius: 10px;
@@ -650,7 +661,7 @@ body {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
border: 1px solid rgba(217, 119, 6, 0.2); border: 1px solid var(--app-accent-border);
} }
.card-info { .card-info {
@@ -662,7 +673,7 @@ body {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #f1f5f9; color: var(--app-text);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -693,7 +704,7 @@ body {
} }
.date-badge { .date-badge {
color: #64748b; color: var(--app-text-subtle);
} }
.btn-delete { .btn-delete {
@@ -757,7 +768,7 @@ body {
flex-direction: column; flex-direction: column;
padding: 24px; padding: 24px;
box-sizing: border-box; box-sizing: border-box;
color: #f1f5f9; color: var(--app-text);
} }
.app-header { .app-header {
@@ -766,7 +777,7 @@ body {
align-items: center; align-items: center;
margin-bottom: 24px; margin-bottom: 24px;
padding-bottom: 16px; padding-bottom: 16px;
border-bottom: 1px solid rgba(212, 175, 55, 0.15); border-bottom: 1px solid var(--app-header-border);
} }
.app-header-left { .app-header-left {
@@ -776,9 +787,9 @@ body {
} }
.btn-back { .btn-back {
background: rgba(255, 255, 255, 0.05); background: var(--app-btn-secondary-bg);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid var(--app-btn-secondary-border);
color: #fbbf24; color: var(--app-accent-light);
padding: 8px 16px; padding: 8px 16px;
border-radius: 8px; border-radius: 8px;
display: flex; display: flex;
@@ -790,19 +801,19 @@ body {
} }
.btn-back:hover { .btn-back:hover {
background: rgba(217, 119, 6, 0.1); background: var(--app-accent-bg);
border-color: #d97706; border-color: var(--app-accent);
} }
.app-title-area h2 { .app-title-area h2 {
font-size: 20px; font-size: 20px;
margin: 0; margin: 0;
color: #f8fafc; color: var(--app-text-heading);
} }
.app-title-area .app-subtitle { .app-title-area .app-subtitle {
font-size: 12px; font-size: 12px;
color: #94a3b8; color: var(--app-text-muted);
margin: 2px 0 0 0; margin: 2px 0 0 0;
} }
@@ -823,10 +834,10 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
background: rgba(11, 12, 16, 0.6); background: var(--app-surface-alt);
backdrop-filter: blur(20px); backdrop-filter: var(--app-backdrop);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid var(--app-border-subtle);
border-radius: 12px; border-radius: var(--app-radius-card);
padding: 16px; padding: 16px;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -834,7 +845,7 @@ body {
.sidebar-btn { .sidebar-btn {
background: none; background: none;
border: none; border: none;
color: #94a3b8; color: var(--app-text-muted);
padding: 12px 16px; padding: 12px 16px;
border-radius: 8px; border-radius: 8px;
display: flex; display: flex;
@@ -849,21 +860,21 @@ body {
} }
.sidebar-btn:hover { .sidebar-btn:hover {
background: rgba(255, 255, 255, 0.04); background: var(--app-surface-inset);
color: #f1f5f9; color: var(--app-text);
} }
.sidebar-btn.active { .sidebar-btn.active {
background: rgba(217, 119, 6, 0.1); background: var(--app-sidebar-active-bg);
border: 1px solid rgba(217, 119, 6, 0.2); border: 1px solid var(--app-accent-border);
color: #fbbf24; color: var(--app-sidebar-active-text);
} }
.app-content { .app-content {
background: rgba(11, 12, 16, 0.6); background: var(--app-surface-alt);
backdrop-filter: blur(20px); backdrop-filter: var(--app-backdrop);
border: 1px solid rgba(212, 175, 55, 0.2); border: 1px solid var(--app-border-muted);
border-radius: 16px; border-radius: var(--app-radius-card);
padding: 32px; padding: 32px;
min-height: 400px; min-height: 400px;
box-sizing: border-box; box-sizing: border-box;
@@ -877,12 +888,12 @@ body {
justify-content: center; justify-content: center;
min-height: 300px; min-height: 300px;
text-align: center; text-align: center;
color: #94a3b8; color: var(--app-text-muted);
} }
.tab-placeholder h3 { .tab-placeholder h3 {
font-size: 24px; font-size: 24px;
color: #fbbf24; color: var(--app-accent-light);
margin-top: 16px; margin-top: 16px;
margin-bottom: 8px; margin-bottom: 8px;
} }
@@ -895,11 +906,11 @@ body {
/* Form and Editor layout structures */ /* Form and Editor layout structures */
.form-card { .form-card {
background: rgba(11, 12, 16, 0.6); background: var(--app-surface-alt);
backdrop-filter: blur(20px); backdrop-filter: var(--app-backdrop);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: var(--app-backdrop);
border: 1px solid rgba(212, 175, 55, 0.2); border: 1px solid var(--app-border-muted);
border-radius: 16px; border-radius: var(--app-radius-card);
padding: 32px; padding: 32px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
@@ -915,14 +926,14 @@ body {
.form-header h2 { .form-header h2 {
font-size: 20px; font-size: 20px;
margin: 0; margin: 0;
color: #fbbf24; color: var(--app-accent-light);
min-width: 0; min-width: 0;
flex-shrink: 1; flex-shrink: 1;
word-break: break-word; word-break: break-word;
} }
.form-icon { .form-icon {
color: #fbbf24; color: var(--app-accent-light);
} }
.vessel-form { .vessel-form {
@@ -951,7 +962,7 @@ body {
.vessel-form label { .vessel-form label {
display: block; display: block;
font-size: 13.5px; font-size: 13.5px;
color: #94a3b8; color: var(--app-text-muted);
margin-bottom: 6px; margin-bottom: 6px;
font-weight: 500; font-weight: 500;
} }
@@ -986,9 +997,9 @@ body {
} }
.member-editor-card { .member-editor-card {
background: rgba(255, 255, 255, 0.02); background: var(--app-surface-inset);
border: 1px solid rgba(212, 175, 55, 0.15); border: 1px solid var(--app-border-muted);
border-radius: 12px; border-radius: var(--app-radius-card);
padding: 24px; padding: 24px;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -1002,7 +1013,7 @@ body {
.editor-header h3 { .editor-header h3 {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
color: #fbbf24; color: var(--app-accent-light);
} }
.editor-actions { .editor-actions {
@@ -1187,7 +1198,7 @@ body {
left: 0; left: 0;
width: 100%; width: 100%;
height: 3px; height: 3px;
background: linear-gradient(90deg, #fbbf24, #d97706, #fbbf24); background: var(--app-progress-bar);
background-size: 200% 100%; background-size: 200% 100%;
animation: sync-slide 1.5s infinite linear; animation: sync-slide 1.5s infinite linear;
z-index: 9999; z-index: 9999;
@@ -1228,232 +1239,57 @@ body {
/* ========================================== */ /* ========================================== */
/* PHASE 4: OS-ADAPTIVE UI THEMES */ /* PHASE 4: OS-ADAPTIVE UI THEMES */
/* Color tokens: themes.css · classes on html */
/* ========================================== */ /* ========================================== */
/* Body Overrides via :has() */ html.theme-material {
body:has(.theme-material) { font-family: 'Roboto', 'Noto Sans', system-ui, -apple-system, sans-serif;
background: #121212 !important;
}
body:has(.theme-cupertino) {
background: #000000 !important;
} }
/* --- MATERIAL THEME (ANDROID/LINUX) --- */ html.theme-cupertino {
.theme-material { font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: 'Roboto', 'Noto Sans', system-ui, -apple-system, sans-serif !important;
} }
/* Color & Text overrides */ html.theme-material .sidebar-btn {
.theme-material h1, border-radius: 0;
.theme-material h2, border-left: 4px solid transparent;
.theme-material h3,
.theme-material h4,
.theme-material .form-header h2,
.theme-material .form-icon,
.theme-material .header-logo,
.theme-material .card-icon,
.theme-material .btn-back,
.theme-material .cell-label {
color: #00adb5 !important;
} }
/* Card Overrides */ html.theme-material .sidebar-btn.active {
.theme-material .auth-card, border-left: 4px solid var(--app-sidebar-active-border);
.theme-material .form-card, border-top: none;
.theme-material .create-section, border-right: none;
.theme-material .app-sidebar, border-bottom: none;
.theme-material .app-content,
.theme-material .logbook-card,
.theme-material .crew-member-card,
.theme-material .member-editor-card {
background: #1e1e1e !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
border: 1px solid #2d2d2d !important;
border-radius: 4px !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3) !important;
} }
/* Input Overrides */ html.theme-material .btn.primary {
.theme-material .input-text, background-image: none;
.theme-material .input-textarea,
.theme-material select.input-text {
background: #2a2a2a !important;
border: 1px solid #3d3d3d !important;
border-radius: 4px !important;
color: #f1f5f9 !important;
}
.theme-material .input-text:focus,
.theme-material .input-textarea:focus {
border-color: #00adb5 !important;
box-shadow: 0 0 0 2px rgba(0, 173, 181, 0.2) !important;
} }
/* Button Overrides */ html.theme-cupertino .btn.primary,
.theme-material .btn.primary { html.theme-cupertino .btn.secondary,
background: #00adb5 !important; html.theme-cupertino .btn-back {
color: #ffffff !important; border-radius: var(--app-radius-btn);
border-radius: 4px !important;
box-shadow: none !important;
}
.theme-material .btn.primary:hover {
background: #008f95 !important;
transform: none !important;
}
.theme-material .btn.secondary,
.theme-material .btn-back {
background: #2a2a2a !important;
border: 1px solid #3d3d3d !important;
color: #f1f5f9 !important;
border-radius: 4px !important;
}
.theme-material .btn.secondary:hover,
.theme-material .btn-back:hover {
background: #333333 !important;
} }
/* Sidebar Overrides */ html.theme-material .events-table th,
.theme-material .sidebar-btn { html.theme-cupertino .events-table th {
border-radius: 0 !important; color: var(--app-accent);
border-left: 4px solid transparent !important; border-bottom-color: var(--app-table-border);
}
.theme-material .sidebar-btn.active {
background: rgba(0, 173, 181, 0.08) !important;
border-left: 4px solid #00adb5 !important;
color: #00adb5 !important;
border-top: none !important;
border-right: none !important;
border-bottom: none !important;
} }
/* Header Overrides */ html.theme-material .events-table td,
.theme-material .app-header { html.theme-cupertino .events-table td {
border-bottom: 1px solid #2d2d2d !important; border-bottom-color: var(--app-table-border);
} }
/* Tables and Grids */ html.theme-material .events-scroll-container,
.theme-material .events-table th { html.theme-cupertino .events-scroll-container {
color: #00adb5 !important; border-color: var(--app-table-border);
border-bottom: 2px solid #2d2d2d !important;
}
.theme-material .events-table td {
border-bottom: 1px solid #2d2d2d !important;
}
.theme-material .events-scroll-container {
border: 1px solid #2d2d2d !important;
} }
/* Progress bar override for Material */ html.theme-cupertino .events-scroll-container {
.theme-material ~ .sync-progress-bar { background: var(--app-surface-inset);
background: linear-gradient(90deg, #00adb5, #008f95, #00adb5) !important;
}
/* --- CUPERTINO THEME (iOS/macOS) --- */
.theme-cupertino {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif !important;
}
/* Color & Text overrides */
.theme-cupertino h1,
.theme-cupertino h2,
.theme-cupertino h3,
.theme-cupertino h4,
.theme-cupertino .form-header h2,
.theme-cupertino .form-icon,
.theme-cupertino .header-logo,
.theme-cupertino .card-icon,
.theme-cupertino .btn-back,
.theme-cupertino .cell-label {
color: #0a84ff !important;
}
/* Card Overrides */
.theme-cupertino .auth-card,
.theme-cupertino .form-card,
.theme-cupertino .create-section,
.theme-cupertino .app-sidebar,
.theme-cupertino .app-content,
.theme-cupertino .logbook-card,
.theme-cupertino .crew-member-card,
.theme-cupertino .member-editor-card {
background: rgba(28, 28, 30, 0.7) !important;
backdrop-filter: blur(25px) !important;
-webkit-backdrop-filter: blur(25px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
border-radius: 12px !important;
box-shadow: none !important;
}
/* Input Overrides */
.theme-cupertino .input-text,
.theme-cupertino .input-textarea,
.theme-cupertino select.input-text {
background: rgba(255, 255, 255, 0.05) !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
border-radius: 8px !important;
color: #ffffff !important;
}
.theme-cupertino .input-text:focus,
.theme-cupertino .input-textarea:focus {
border-color: #0a84ff !important;
box-shadow: 0 0 0 2px rgba(10, 132, 255, 0.25) !important;
}
/* Button Overrides */
.theme-cupertino .btn.primary {
background: #0a84ff !important;
color: #ffffff !important;
border-radius: 9999px !important;
box-shadow: none !important;
}
.theme-cupertino .btn.primary:hover {
background: #007aff !important;
transform: none !important;
}
.theme-cupertino .btn.secondary,
.theme-cupertino .btn-back {
background: rgba(255, 255, 255, 0.08) !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
color: #ffffff !important;
border-radius: 9999px !important;
}
.theme-cupertino .btn.secondary:hover,
.theme-cupertino .btn-back:hover {
background: rgba(255, 255, 255, 0.12) !important;
}
/* Sidebar Overrides */
.theme-cupertino .sidebar-btn {
border-radius: 8px !important;
}
.theme-cupertino .sidebar-btn.active {
background: rgba(10, 132, 255, 0.12) !important;
color: #0a84ff !important;
border: 1px solid rgba(10, 132, 255, 0.2) !important;
}
/* Header Overrides */
.theme-cupertino .app-header {
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
}
/* Tables and Grids */
.theme-cupertino .events-table th {
color: #0a84ff !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.15) !important;
}
.theme-cupertino .events-table td {
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
}
.theme-cupertino .events-scroll-container {
border: 1px solid rgba(255, 255, 255, 0.1) !important;
background: rgba(28, 28, 30, 0.5) !important;
}
/* Progress bar override for Cupertino */
.theme-cupertino ~ .sync-progress-bar {
background: linear-gradient(90deg, #0a84ff, #007aff, #0a84ff) !important;
} }
+19 -25
View File
@@ -11,6 +11,12 @@ import LogEntriesList from './components/LogEntriesList.tsx'
import SettingsForm from './components/SettingsForm.tsx' import SettingsForm from './components/SettingsForm.tsx'
import InvitationAcceptance from './components/InvitationAcceptance.tsx' import InvitationAcceptance from './components/InvitationAcceptance.tsx'
import { getActiveMasterKey, logoutUser } from './services/auth.js' import { getActiveMasterKey, logoutUser } from './services/auth.js'
import {
applyAppearanceToDocument,
resolveAppTheme,
resolveColorScheme,
subscribeToSystemColorScheme
} from './services/appearance.js'
import { startBackgroundSync, stopBackgroundSync, syncAllLogbooks, subscribeToSyncState } from './services/sync.js' import { startBackgroundSync, stopBackgroundSync, syncAllLogbooks, subscribeToSyncState } from './services/sync.js'
import ReadOnlyViewer from './components/ReadOnlyViewer.tsx' import ReadOnlyViewer from './components/ReadOnlyViewer.tsx'
import PwaInstallPrompt from './components/PwaInstallPrompt.tsx' import PwaInstallPrompt from './components/PwaInstallPrompt.tsx'
@@ -28,7 +34,6 @@ function App() {
const [activeTab, setActiveTab] = useState<'vessel' | 'crew' | 'logs' | 'settings'>('logs') const [activeTab, setActiveTab] = useState<'vessel' | 'crew' | 'logs' | 'settings'>('logs')
const [online, setOnline] = useState(navigator.onLine) const [online, setOnline] = useState(navigator.onLine)
const [isSyncing, setIsSyncing] = useState(false) const [isSyncing, setIsSyncing] = useState(false)
const [appliedTheme, setAppliedTheme] = useState<'ocean' | 'material' | 'cupertino'>('ocean')
const [isAcceptingInvite, setIsAcceptingInvite] = useState(false) const [isAcceptingInvite, setIsAcceptingInvite] = useState(false)
// Viewer mode for read-only shared links // Viewer mode for read-only shared links
@@ -41,27 +46,16 @@ function App() {
[activeLogbookId] [activeLogbookId]
) )
const updateAppliedTheme = () => {
const configTheme = localStorage.getItem('active_theme') || 'auto'
if (configTheme === 'auto') {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera
if (/iPad|iPhone|iPod|Macintosh/.test(userAgent)) {
setAppliedTheme('cupertino')
} else if (/Android|Linux/.test(userAgent)) {
setAppliedTheme('material')
} else {
setAppliedTheme('ocean')
}
} else {
setAppliedTheme(configTheme as 'ocean' | 'material' | 'cupertino')
}
}
useEffect(() => { useEffect(() => {
updateAppliedTheme() const syncAppearance = () => {
window.addEventListener('theme-changed', updateAppliedTheme) applyAppearanceToDocument(resolveAppTheme(), resolveColorScheme())
}
syncAppearance()
window.addEventListener('appearance-changed', syncAppearance)
const unsubscribeSystem = subscribeToSystemColorScheme(syncAppearance)
return () => { return () => {
window.removeEventListener('theme-changed', updateAppliedTheme) window.removeEventListener('appearance-changed', syncAppearance)
unsubscribeSystem()
} }
}, []) }, [])
@@ -159,7 +153,7 @@ function App() {
if (isViewerMode) { if (isViewerMode) {
return ( return (
<div className={`theme-${appliedTheme}`} style={{ display: 'contents' }}> <div style={{ display: 'contents' }}>
<ReadOnlyViewer token={shareToken} hexKey={shareKey} /> <ReadOnlyViewer token={shareToken} hexKey={shareKey} />
</div> </div>
) )
@@ -167,7 +161,7 @@ function App() {
if (isAcceptingInvite) { if (isAcceptingInvite) {
return ( return (
<div className={`theme-${appliedTheme} auth-screen`}> <div className="auth-screen">
<InvitationAcceptance <InvitationAcceptance
onAccepted={(logbookId, title) => { onAccepted={(logbookId, title) => {
setIsAuthenticated(true) setIsAuthenticated(true)
@@ -187,7 +181,7 @@ function App() {
if (!isAuthenticated) { if (!isAuthenticated) {
return ( return (
<div className={`theme-${appliedTheme} auth-screen`}> <div className="auth-screen">
<AuthOnboarding onAuthenticated={handleAuthenticated} /> <AuthOnboarding onAuthenticated={handleAuthenticated} />
</div> </div>
) )
@@ -197,7 +191,7 @@ function App() {
if (!activeLogbookId) { if (!activeLogbookId) {
return ( return (
<div className={`theme-${appliedTheme}`} style={{ display: 'contents' }}> <div style={{ display: 'contents' }}>
{pwaInstallBanner} {pwaInstallBanner}
<LogbookDashboard <LogbookDashboard
onSelectLogbook={handleSelectLogbook} onSelectLogbook={handleSelectLogbook}
@@ -208,7 +202,7 @@ function App() {
} }
return ( return (
<div className={`theme-${appliedTheme}`} style={{ display: 'contents' }}> <div style={{ display: 'contents' }}>
{pwaInstallBanner} {pwaInstallBanner}
{isSyncing && <div className="sync-progress-bar" />} {isSyncing && <div className="sync-progress-bar" />}
<div className="app-layout"> <div className="app-layout">
+43 -7
View File
@@ -5,6 +5,7 @@ import { ensureLogbookKey } from '../services/logbookKeys.js'
import AccountDangerZone from './AccountDangerZone.tsx' import AccountDangerZone from './AccountDangerZone.tsx'
import PwaInstallPrompt from './PwaInstallPrompt.tsx' import PwaInstallPrompt from './PwaInstallPrompt.tsx'
import { useDialog } from './ModalDialog.tsx' import { useDialog } from './ModalDialog.tsx'
import { notifyAppearanceChanged } from '../services/appearance.js'
interface SettingsFormProps { interface SettingsFormProps {
logbookId?: string | null logbookId?: string | null
@@ -30,6 +31,7 @@ export default function SettingsForm({ logbookId }: SettingsFormProps) {
const { showConfirm, showAlert } = useDialog() const { showConfirm, showAlert } = useDialog()
const [apiKey, setApiKey] = useState(localStorage.getItem('owm_api_key') || '') const [apiKey, setApiKey] = useState(localStorage.getItem('owm_api_key') || '')
const [theme, setTheme] = useState(localStorage.getItem('active_theme') || 'auto') const [theme, setTheme] = useState(localStorage.getItem('active_theme') || 'auto')
const [colorScheme, setColorScheme] = useState(localStorage.getItem('active_color_scheme') || 'auto')
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [success, setSuccess] = useState(false) const [success, setSuccess] = useState(false)
@@ -245,17 +247,29 @@ export default function SettingsForm({ logbookId }: SettingsFormProps) {
} }
} }
const persistAppearance = (nextTheme: string, nextColorScheme: string) => {
localStorage.setItem('active_theme', nextTheme)
localStorage.setItem('active_color_scheme', nextColorScheme)
notifyAppearanceChanged()
}
const handleThemeChange = (nextTheme: string) => {
setTheme(nextTheme)
persistAppearance(nextTheme, colorScheme)
}
const handleColorSchemeChange = (nextColorScheme: string) => {
setColorScheme(nextColorScheme)
persistAppearance(theme, nextColorScheme)
}
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
setSaving(true) setSaving(true)
setSuccess(false) setSuccess(false)
// Save to localStorage
localStorage.setItem('owm_api_key', apiKey.trim()) localStorage.setItem('owm_api_key', apiKey.trim())
localStorage.setItem('active_theme', theme) persistAppearance(theme, colorScheme)
// Notify App of theme change
window.dispatchEvent(new Event('theme-changed'))
setSaving(false) setSaving(false)
setSuccess(true) setSuccess(true)
@@ -316,9 +330,8 @@ export default function SettingsForm({ logbookId }: SettingsFormProps) {
id="app-theme" id="app-theme"
className="input-text" className="input-text"
value={theme} value={theme}
onChange={(e) => setTheme(e.target.value)} onChange={(e) => handleThemeChange(e.target.value)}
disabled={saving} disabled={saving}
style={{ background: 'rgba(11, 12, 16, 0.85)', color: '#f1f5f9' }}
> >
<option value="auto">{t('settings.theme_auto')}</option> <option value="auto">{t('settings.theme_auto')}</option>
<option value="ocean">{t('settings.theme_ocean')}</option> <option value="ocean">{t('settings.theme_ocean')}</option>
@@ -328,6 +341,29 @@ export default function SettingsForm({ logbookId }: SettingsFormProps) {
</div> </div>
</div> </div>
<div className="member-editor-card glass mt-4">
<h3 style={{ marginTop: 0, marginBottom: '12px', color: 'var(--app-accent-light)', fontSize: '16px' }}>
{t('settings.color_scheme_title')}
</h3>
<p className="text-muted" style={{ fontSize: '13.5px', lineHeight: '145%', margin: '0 0 16px 0' }}>
{t('settings.color_scheme_label')}
</p>
<div className="input-group">
<select
id="app-color-scheme"
className="input-text"
value={colorScheme}
onChange={(e) => handleColorSchemeChange(e.target.value)}
disabled={saving}
>
<option value="auto">{t('settings.color_scheme_auto')}</option>
<option value="light">{t('settings.color_scheme_light')}</option>
<option value="dark">{t('settings.color_scheme_dark')}</option>
</select>
</div>
</div>
<div className="form-actions mt-4 mb-6"> <div className="form-actions mt-4 mb-6">
{success && ( {success && (
<div className="success-toast"> <div className="success-toast">
+5
View File
@@ -286,6 +286,11 @@
"theme_ocean": "Ocean (Glassmorphismus)", "theme_ocean": "Ocean (Glassmorphismus)",
"theme_material": "Material (Android)", "theme_material": "Material (Android)",
"theme_cupertino": "Cupertino (iOS)", "theme_cupertino": "Cupertino (iOS)",
"color_scheme_title": "Erscheinungsbild",
"color_scheme_label": "Hell- oder Dunkelmodus (Standard: Systemeinstellung)",
"color_scheme_auto": "Automatisch (System)",
"color_scheme_light": "Hell",
"color_scheme_dark": "Dunkel",
"share_title": "Logbuch teilen (Schreibgeschützt)", "share_title": "Logbuch teilen (Schreibgeschützt)",
"share_desc": "Aktivieren Sie diese Option, um einen öffentlichen, schreibgeschützten Link zu erstellen. Jeder mit dem Link kann Ihre Reisen, Yacht-Profile und Besatzung ansehen. Die Verschlüsselungsschlüssel werden niemals an den Server übertragen (sie bleiben im Hash-Teil der URL).", "share_desc": "Aktivieren Sie diese Option, um einen öffentlichen, schreibgeschützten Link zu erstellen. Jeder mit dem Link kann Ihre Reisen, Yacht-Profile und Besatzung ansehen. Die Verschlüsselungsschlüssel werden niemals an den Server übertragen (sie bleiben im Hash-Teil der URL).",
"share_enable": "Öffentlichen Link aktivieren", "share_enable": "Öffentlichen Link aktivieren",
+5
View File
@@ -286,6 +286,11 @@
"theme_ocean": "Ocean (Glassmorphism)", "theme_ocean": "Ocean (Glassmorphism)",
"theme_material": "Material (Android)", "theme_material": "Material (Android)",
"theme_cupertino": "Cupertino (iOS)", "theme_cupertino": "Cupertino (iOS)",
"color_scheme_title": "Appearance",
"color_scheme_label": "Light or dark mode (default: follow system)",
"color_scheme_auto": "Auto (System)",
"color_scheme_light": "Light",
"color_scheme_dark": "Dark",
"share_title": "Share Logbook (Read-Only)", "share_title": "Share Logbook (Read-Only)",
"share_desc": "Enable this to generate a public, read-only link. Anyone with the link can view your travels, yacht profile, and crew members. Decryption keys are never transmitted to the server (they stay in the hash part of the URL).", "share_desc": "Enable this to generate a public, read-only link. Anyone with the link can view your travels, yacht profile, and crew members. Decryption keys are never transmitted to the server (they stay in the hash part of the URL).",
"share_enable": "Enable Public Link", "share_enable": "Enable Public Link",
+4
View File
@@ -1,9 +1,13 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import './themes.css'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App.tsx'
import './i18n' import './i18n'
import { applyAppearanceToDocument } from './services/appearance.ts'
applyAppearanceToDocument()
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
+53
View File
@@ -0,0 +1,53 @@
export type ColorSchemePreference = 'auto' | 'light' | 'dark'
export type ResolvedColorScheme = 'light' | 'dark'
export type AppTheme = 'ocean' | 'material' | 'cupertino'
const THEME_CLASSES = ['theme-ocean', 'theme-material', 'theme-cupertino'] as const
const SCHEME_CLASSES = ['scheme-light', 'scheme-dark'] as const
export function getColorSchemePreference(): ColorSchemePreference {
const stored = localStorage.getItem('active_color_scheme')
if (stored === 'light' || stored === 'dark' || stored === 'auto') return stored
return 'auto'
}
export function resolveColorScheme(pref?: ColorSchemePreference): ResolvedColorScheme {
const preference = pref ?? getColorSchemePreference()
if (preference === 'light') return 'light'
if (preference === 'dark') return 'dark'
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
export function resolveAppTheme(): AppTheme {
const configTheme = localStorage.getItem('active_theme') || 'auto'
if (configTheme === 'material' || configTheme === 'cupertino' || configTheme === 'ocean') {
return configTheme
}
const userAgent = navigator.userAgent || navigator.vendor || ''
if (/iPad|iPhone|iPod|Macintosh/.test(userAgent)) return 'cupertino'
if (/Android|Linux/.test(userAgent)) return 'material'
return 'ocean'
}
export function applyAppearanceToDocument(
theme: AppTheme = resolveAppTheme(),
scheme: ResolvedColorScheme = resolveColorScheme()
): void {
const root = document.documentElement
root.classList.remove(...THEME_CLASSES, ...SCHEME_CLASSES)
root.classList.add(`theme-${theme}`, `scheme-${scheme}`)
root.style.colorScheme = scheme
}
export function subscribeToSystemColorScheme(onChange: () => void): () => void {
const media = window.matchMedia('(prefers-color-scheme: dark)')
const handler = () => {
if (getColorSchemePreference() === 'auto') onChange()
}
media.addEventListener('change', handler)
return () => media.removeEventListener('change', handler)
}
export function notifyAppearanceChanged(): void {
window.dispatchEvent(new Event('appearance-changed'))
}
+398
View File
@@ -0,0 +1,398 @@
/**
* Appearance tokens: scheme (light/dark) × theme (ocean/material/cupertino)
* Applied on document.documentElement via appearance.ts
*/
/* Fallback before JS hydrates (ocean · dark) */
html {
color-scheme: dark;
--app-body-bg: radial-gradient(circle at center, #1b264f 0%, #0b0c10 100%);
--app-text: #f1f5f9;
--app-text-heading: #f8fafc;
--app-text-muted: #94a3b8;
--app-text-subtle: #64748b;
--app-surface: rgba(11, 12, 16, 0.75);
--app-surface-alt: rgba(11, 12, 16, 0.6);
--app-surface-hover: rgba(11, 12, 16, 0.85);
--app-surface-inset: rgba(255, 255, 255, 0.02);
--app-border: rgba(212, 175, 55, 0.25);
--app-border-subtle: rgba(255, 255, 255, 0.08);
--app-border-muted: rgba(212, 175, 55, 0.15);
--app-input-bg: rgba(11, 12, 16, 0.85);
--app-input-bg-focus: #0b0c10;
--app-input-border: rgba(148, 163, 184, 0.25);
--app-input-text: #f1f5f9;
--app-accent: #d97706;
--app-accent-light: #fbbf24;
--app-accent-gradient: linear-gradient(135deg, #fef08a 0%, #d97706 100%);
--app-accent-bg: rgba(217, 119, 6, 0.1);
--app-accent-border: rgba(217, 119, 6, 0.2);
--app-accent-focus-ring: rgba(217, 119, 6, 0.2);
--app-btn-primary-text: #0b0c10;
--app-btn-secondary-bg: rgba(255, 255, 255, 0.05);
--app-btn-secondary-border: rgba(255, 255, 255, 0.12);
--app-btn-secondary-text: #e2e8f0;
--app-btn-secondary-hover-bg: rgba(255, 255, 255, 0.08);
--app-icon-btn-bg: rgba(255, 255, 255, 0.05);
--app-icon-btn-border: rgba(255, 255, 255, 0.1);
--app-divider: rgba(255, 255, 255, 0.06);
--app-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
--app-card-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
--app-error-bg: rgba(244, 63, 94, 0.08);
--app-error-text: #fda4af;
--app-error-border: #f43f5e;
--app-warning-text: #f43f5e;
--app-warning-bg: rgba(244, 63, 94, 0.08);
--app-warning-border: rgba(244, 63, 94, 0.2);
--app-empty-border: rgba(255, 255, 255, 0.08);
--app-empty-bg: rgba(255, 255, 255, 0.02);
--app-sidebar-active-bg: rgba(217, 119, 6, 0.12);
--app-sidebar-active-border: #d97706;
--app-sidebar-active-text: #fbbf24;
--app-header-border: rgba(212, 175, 55, 0.15);
--app-table-border: rgba(255, 255, 255, 0.08);
--app-progress-bar: linear-gradient(90deg, #d97706, #fbbf24, #d97706);
--app-backdrop: blur(20px);
--app-radius-card: 16px;
--app-radius-input: 10px;
--app-radius-btn: 10px;
}
/* ===== OCEAN · DARK (default) ===== */
html.scheme-dark.theme-ocean {
color-scheme: dark;
--app-body-bg: radial-gradient(circle at center, #1b264f 0%, #0b0c10 100%);
--app-text: #f1f5f9;
--app-text-heading: #f8fafc;
--app-text-muted: #94a3b8;
--app-text-subtle: #64748b;
--app-surface: rgba(11, 12, 16, 0.75);
--app-surface-alt: rgba(11, 12, 16, 0.6);
--app-surface-hover: rgba(11, 12, 16, 0.85);
--app-surface-inset: rgba(255, 255, 255, 0.02);
--app-border: rgba(212, 175, 55, 0.25);
--app-border-subtle: rgba(255, 255, 255, 0.08);
--app-border-muted: rgba(212, 175, 55, 0.15);
--app-input-bg: rgba(11, 12, 16, 0.85);
--app-input-bg-focus: #0b0c10;
--app-input-border: rgba(148, 163, 184, 0.25);
--app-input-text: #f1f5f9;
--app-accent: #d97706;
--app-accent-light: #fbbf24;
--app-accent-gradient: linear-gradient(135deg, #fef08a 0%, #d97706 100%);
--app-accent-bg: rgba(217, 119, 6, 0.1);
--app-accent-border: rgba(217, 119, 6, 0.2);
--app-accent-focus-ring: rgba(217, 119, 6, 0.2);
--app-btn-primary-text: #0b0c10;
--app-btn-secondary-bg: rgba(255, 255, 255, 0.05);
--app-btn-secondary-border: rgba(255, 255, 255, 0.12);
--app-btn-secondary-text: #e2e8f0;
--app-btn-secondary-hover-bg: rgba(255, 255, 255, 0.08);
--app-icon-btn-bg: rgba(255, 255, 255, 0.05);
--app-icon-btn-border: rgba(255, 255, 255, 0.1);
--app-divider: rgba(255, 255, 255, 0.06);
--app-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
--app-card-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
--app-error-bg: rgba(244, 63, 94, 0.08);
--app-error-text: #fda4af;
--app-error-border: #f43f5e;
--app-warning-text: #f43f5e;
--app-warning-bg: rgba(244, 63, 94, 0.08);
--app-warning-border: rgba(244, 63, 94, 0.2);
--app-empty-border: rgba(255, 255, 255, 0.08);
--app-empty-bg: rgba(255, 255, 255, 0.02);
--app-sidebar-active-bg: rgba(217, 119, 6, 0.12);
--app-sidebar-active-border: #d97706;
--app-sidebar-active-text: #fbbf24;
--app-header-border: rgba(212, 175, 55, 0.15);
--app-table-border: rgba(255, 255, 255, 0.08);
--app-progress-bar: linear-gradient(90deg, #d97706, #fbbf24, #d97706);
--app-backdrop: blur(20px);
--app-radius-card: 16px;
--app-radius-input: 10px;
--app-radius-btn: 10px;
}
/* ===== OCEAN · LIGHT ===== */
html.scheme-light.theme-ocean {
color-scheme: light;
--app-body-bg: linear-gradient(165deg, #dbeafe 0%, #f8fafc 42%, #e2e8f0 100%);
--app-text: #1e293b;
--app-text-heading: #0f172a;
--app-text-muted: #475569;
--app-text-subtle: #64748b;
--app-surface: rgba(255, 255, 255, 0.88);
--app-surface-alt: rgba(255, 255, 255, 0.78);
--app-surface-hover: rgba(255, 255, 255, 0.96);
--app-surface-inset: rgba(15, 23, 42, 0.03);
--app-border: rgba(217, 119, 6, 0.28);
--app-border-subtle: rgba(15, 23, 42, 0.1);
--app-border-muted: rgba(217, 119, 6, 0.18);
--app-input-bg: #ffffff;
--app-input-bg-focus: #ffffff;
--app-input-border: rgba(100, 116, 139, 0.35);
--app-input-text: #0f172a;
--app-accent: #b45309;
--app-accent-light: #d97706;
--app-accent-gradient: linear-gradient(135deg, #fcd34d 0%, #b45309 100%);
--app-accent-bg: rgba(217, 119, 6, 0.12);
--app-accent-border: rgba(217, 119, 6, 0.25);
--app-accent-focus-ring: rgba(217, 119, 6, 0.25);
--app-btn-primary-text: #0b0c10;
--app-btn-secondary-bg: rgba(15, 23, 42, 0.04);
--app-btn-secondary-border: rgba(15, 23, 42, 0.12);
--app-btn-secondary-text: #334155;
--app-btn-secondary-hover-bg: rgba(15, 23, 42, 0.07);
--app-icon-btn-bg: rgba(15, 23, 42, 0.04);
--app-icon-btn-border: rgba(15, 23, 42, 0.1);
--app-divider: rgba(15, 23, 42, 0.08);
--app-shadow: 0 16px 40px rgba(15, 23, 42, 0.12), inset 0 0 0 1px rgba(255, 255, 255, 0.6);
--app-card-shadow: 0 8px 24px rgba(15, 23, 42, 0.1);
--app-error-bg: rgba(244, 63, 94, 0.08);
--app-error-text: #be123c;
--app-error-border: #e11d48;
--app-warning-text: #be123c;
--app-warning-bg: rgba(244, 63, 94, 0.06);
--app-warning-border: rgba(244, 63, 94, 0.2);
--app-empty-border: rgba(15, 23, 42, 0.12);
--app-empty-bg: rgba(15, 23, 42, 0.02);
--app-sidebar-active-bg: rgba(217, 119, 6, 0.1);
--app-sidebar-active-border: #d97706;
--app-sidebar-active-text: #b45309;
--app-header-border: rgba(217, 119, 6, 0.2);
--app-table-border: rgba(15, 23, 42, 0.1);
--app-progress-bar: linear-gradient(90deg, #d97706, #fbbf24, #d97706);
--app-backdrop: blur(20px);
--app-radius-card: 16px;
--app-radius-input: 10px;
--app-radius-btn: 10px;
}
/* ===== MATERIAL · DARK ===== */
html.scheme-dark.theme-material {
color-scheme: dark;
--app-body-bg: #121212;
--app-text: #f1f5f9;
--app-text-heading: #f8fafc;
--app-text-muted: #94a3b8;
--app-text-subtle: #64748b;
--app-surface: #1e1e1e;
--app-surface-alt: #1e1e1e;
--app-surface-hover: #252525;
--app-surface-inset: #2a2a2a;
--app-border: #2d2d2d;
--app-border-subtle: #2d2d2d;
--app-border-muted: #2d2d2d;
--app-input-bg: #2a2a2a;
--app-input-bg-focus: #2a2a2a;
--app-input-border: #3d3d3d;
--app-input-text: #f1f5f9;
--app-accent: #00adb5;
--app-accent-light: #00adb5;
--app-accent-gradient: linear-gradient(135deg, #00adb5 0%, #008f95 100%);
--app-accent-bg: rgba(0, 173, 181, 0.12);
--app-accent-border: rgba(0, 173, 181, 0.3);
--app-accent-focus-ring: rgba(0, 173, 181, 0.2);
--app-btn-primary-text: #ffffff;
--app-btn-secondary-bg: #2a2a2a;
--app-btn-secondary-border: #3d3d3d;
--app-btn-secondary-text: #f1f5f9;
--app-btn-secondary-hover-bg: #333333;
--app-icon-btn-bg: #2a2a2a;
--app-icon-btn-border: #3d3d3d;
--app-divider: #2d2d2d;
--app-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
--app-card-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
--app-error-bg: rgba(244, 63, 94, 0.08);
--app-error-text: #fda4af;
--app-error-border: #f43f5e;
--app-warning-text: #f43f5e;
--app-warning-bg: rgba(244, 63, 94, 0.08);
--app-warning-border: rgba(244, 63, 94, 0.2);
--app-empty-border: #2d2d2d;
--app-empty-bg: #1a1a1a;
--app-sidebar-active-bg: rgba(0, 173, 181, 0.08);
--app-sidebar-active-border: #00adb5;
--app-sidebar-active-text: #00adb5;
--app-header-border: #2d2d2d;
--app-table-border: #2d2d2d;
--app-progress-bar: linear-gradient(90deg, #00adb5, #008f95, #00adb5);
--app-backdrop: none;
--app-radius-card: 4px;
--app-radius-input: 4px;
--app-radius-btn: 4px;
}
/* ===== MATERIAL · LIGHT ===== */
html.scheme-light.theme-material {
color-scheme: light;
--app-body-bg: #fafafa;
--app-text: #212121;
--app-text-heading: #111827;
--app-text-muted: #616161;
--app-text-subtle: #757575;
--app-surface: #ffffff;
--app-surface-alt: #ffffff;
--app-surface-hover: #f5f5f5;
--app-surface-inset: #f5f5f5;
--app-border: #e0e0e0;
--app-border-subtle: #eeeeee;
--app-border-muted: #e0e0e0;
--app-input-bg: #ffffff;
--app-input-bg-focus: #ffffff;
--app-input-border: #bdbdbd;
--app-input-text: #212121;
--app-accent: #00838f;
--app-accent-light: #00838f;
--app-accent-gradient: linear-gradient(135deg, #00838f 0%, #006064 100%);
--app-accent-bg: rgba(0, 131, 143, 0.1);
--app-accent-border: rgba(0, 131, 143, 0.25);
--app-accent-focus-ring: rgba(0, 131, 143, 0.2);
--app-btn-primary-text: #ffffff;
--app-btn-secondary-bg: #f5f5f5;
--app-btn-secondary-border: #e0e0e0;
--app-btn-secondary-text: #424242;
--app-btn-secondary-hover-bg: #eeeeee;
--app-icon-btn-bg: #f5f5f5;
--app-icon-btn-border: #e0e0e0;
--app-divider: #e0e0e0;
--app-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
--app-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
--app-error-bg: rgba(244, 63, 94, 0.08);
--app-error-text: #be123c;
--app-error-border: #e11d48;
--app-warning-text: #be123c;
--app-warning-bg: rgba(244, 63, 94, 0.06);
--app-warning-border: rgba(244, 63, 94, 0.2);
--app-empty-border: #e0e0e0;
--app-empty-bg: #fafafa;
--app-sidebar-active-bg: rgba(0, 131, 143, 0.08);
--app-sidebar-active-border: #00838f;
--app-sidebar-active-text: #00838f;
--app-header-border: #e0e0e0;
--app-table-border: #e0e0e0;
--app-progress-bar: linear-gradient(90deg, #00838f, #00adb5, #00838f);
--app-backdrop: none;
--app-radius-card: 4px;
--app-radius-input: 4px;
--app-radius-btn: 4px;
}
/* ===== CUPERTINO · DARK ===== */
html.scheme-dark.theme-cupertino {
color-scheme: dark;
--app-body-bg: #000000;
--app-text: #ffffff;
--app-text-heading: #ffffff;
--app-text-muted: #aeaeb2;
--app-text-subtle: #8e8e93;
--app-surface: rgba(28, 28, 30, 0.72);
--app-surface-alt: rgba(28, 28, 30, 0.72);
--app-surface-hover: rgba(44, 44, 46, 0.85);
--app-surface-inset: rgba(255, 255, 255, 0.05);
--app-border: rgba(255, 255, 255, 0.1);
--app-border-subtle: rgba(255, 255, 255, 0.1);
--app-border-muted: rgba(255, 255, 255, 0.08);
--app-input-bg: rgba(255, 255, 255, 0.05);
--app-input-bg-focus: rgba(255, 255, 255, 0.07);
--app-input-border: rgba(255, 255, 255, 0.12);
--app-input-text: #ffffff;
--app-accent: #0a84ff;
--app-accent-light: #0a84ff;
--app-accent-gradient: linear-gradient(135deg, #0a84ff 0%, #007aff 100%);
--app-accent-bg: rgba(10, 132, 255, 0.12);
--app-accent-border: rgba(10, 132, 255, 0.3);
--app-accent-focus-ring: rgba(10, 132, 255, 0.25);
--app-btn-primary-text: #ffffff;
--app-btn-secondary-bg: rgba(255, 255, 255, 0.08);
--app-btn-secondary-border: rgba(255, 255, 255, 0.12);
--app-btn-secondary-text: #ffffff;
--app-btn-secondary-hover-bg: rgba(255, 255, 255, 0.12);
--app-icon-btn-bg: rgba(255, 255, 255, 0.08);
--app-icon-btn-border: rgba(255, 255, 255, 0.12);
--app-divider: rgba(255, 255, 255, 0.08);
--app-shadow: none;
--app-card-shadow: none;
--app-error-bg: rgba(255, 69, 58, 0.12);
--app-error-text: #ff6961;
--app-error-border: #ff453a;
--app-warning-text: #ff6961;
--app-warning-bg: rgba(255, 69, 58, 0.12);
--app-warning-border: rgba(255, 69, 58, 0.25);
--app-empty-border: rgba(255, 255, 255, 0.1);
--app-empty-bg: rgba(255, 255, 255, 0.04);
--app-sidebar-active-bg: rgba(10, 132, 255, 0.15);
--app-sidebar-active-border: #0a84ff;
--app-sidebar-active-text: #0a84ff;
--app-header-border: rgba(255, 255, 255, 0.1);
--app-table-border: rgba(255, 255, 255, 0.1);
--app-progress-bar: linear-gradient(90deg, #0a84ff, #007aff, #0a84ff);
--app-backdrop: blur(25px);
--app-radius-card: 12px;
--app-radius-input: 8px;
--app-radius-btn: 9999px;
}
/* ===== CUPERTINO · LIGHT ===== */
html.scheme-light.theme-cupertino {
color-scheme: light;
--app-body-bg: #f2f2f7;
--app-text: #1c1c1e;
--app-text-heading: #000000;
--app-text-muted: #636366;
--app-text-subtle: #8e8e93;
--app-surface: rgba(255, 255, 255, 0.82);
--app-surface-alt: rgba(255, 255, 255, 0.82);
--app-surface-hover: rgba(255, 255, 255, 0.95);
--app-surface-inset: rgba(0, 0, 0, 0.03);
--app-border: rgba(0, 0, 0, 0.08);
--app-border-subtle: rgba(0, 0, 0, 0.06);
--app-border-muted: rgba(0, 0, 0, 0.08);
--app-input-bg: #ffffff;
--app-input-bg-focus: #ffffff;
--app-input-border: rgba(0, 0, 0, 0.12);
--app-input-text: #1c1c1e;
--app-accent: #007aff;
--app-accent-light: #007aff;
--app-accent-gradient: linear-gradient(135deg, #007aff 0%, #0a84ff 100%);
--app-accent-bg: rgba(0, 122, 255, 0.1);
--app-accent-border: rgba(0, 122, 255, 0.25);
--app-accent-focus-ring: rgba(0, 122, 255, 0.2);
--app-btn-primary-text: #ffffff;
--app-btn-secondary-bg: rgba(0, 0, 0, 0.05);
--app-btn-secondary-border: rgba(0, 0, 0, 0.08);
--app-btn-secondary-text: #1c1c1e;
--app-btn-secondary-hover-bg: rgba(0, 0, 0, 0.08);
--app-icon-btn-bg: rgba(0, 0, 0, 0.05);
--app-icon-btn-border: rgba(0, 0, 0, 0.08);
--app-divider: rgba(0, 0, 0, 0.08);
--app-shadow: none;
--app-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
--app-error-bg: rgba(255, 59, 48, 0.1);
--app-error-text: #d70015;
--app-error-border: #ff3b30;
--app-warning-text: #d70015;
--app-warning-bg: rgba(255, 59, 48, 0.08);
--app-warning-border: rgba(255, 59, 48, 0.2);
--app-empty-border: rgba(0, 0, 0, 0.08);
--app-empty-bg: rgba(0, 0, 0, 0.02);
--app-sidebar-active-bg: rgba(0, 122, 255, 0.1);
--app-sidebar-active-border: #007aff;
--app-sidebar-active-text: #007aff;
--app-header-border: rgba(0, 0, 0, 0.08);
--app-table-border: rgba(0, 0, 0, 0.08);
--app-progress-bar: linear-gradient(90deg, #007aff, #0a84ff, #007aff);
--app-backdrop: blur(25px);
--app-radius-card: 12px;
--app-radius-input: 8px;
--app-radius-btn: 9999px;
}
/* Utility classes for inline-style migration */
.text-muted { color: var(--app-text-muted); }
.text-subtle { color: var(--app-text-subtle); }
.text-heading { color: var(--app-text-heading); }
html.scheme-light #root {
border-inline-color: var(--app-border-subtle);
}