diff --git a/static/css/base.css b/static/css/base.css index bc2a3545..3656b7fd 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -1,21 +1,20 @@ +@import 'tokens/index.css'; + html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; - /* Disable default scrolling */ } -/* 针对Firefox */ * { scrollbar-width: thin; - scrollbar-color: var(--border-color) transparent; + scrollbar-color: var(--border-base) transparent; } -/* 针对Webkit browsers (Chrome, Safari等) */ ::-webkit-scrollbar { - width: 8px; + width: var(--scrollbar-width, 8px); } ::-webkit-scrollbar-track { @@ -24,116 +23,128 @@ body { } ::-webkit-scrollbar-thumb { - background-color: var(--border-color); - border-radius: 4px; + background-color: var(--border-base); + border-radius: var(--radius-xs); } :root { - --bg-color: #ffffff; - --text-color: #333333; - --text-muted: #6c757d; - --card-bg: #ffffff; - --border-color: #e0e0e0; --header-height: 48px; - - /* Color Components */ - --lora-accent-l: 68%; - --lora-accent-c: 0.28; - --lora-accent-h: 256; - --lora-warning-l: 75%; - --lora-warning-c: 0.25; - --lora-warning-h: 80; - --lora-success-l: 70%; - --lora-success-c: 0.2; - --lora-success-h: 140; - - /* Composed Colors */ - --lora-accent: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h)); - --lora-surface: oklch(97% 0 0 / 0.95); - --lora-border: oklch(72% 0.03 256 / 0.45); - --lora-text: oklch(95% 0.02 256); - --lora-error: oklch(75% 0.32 29); - --lora-error-bg: color-mix(in oklch, var(--lora-error) 20%, transparent); - --lora-error-border: color-mix(in oklch, var(--lora-error) 50%, transparent); - --lora-warning: oklch(var(--lora-warning-l) var(--lora-warning-c) var(--lora-warning-h)); - --lora-success: oklch(var(--lora-success-l) var(--lora-success-c) var(--lora-success-h)); - --badge-update-bg: oklch(72% 0.2 220); - --badge-update-text: oklch(28% 0.03 220); - --badge-update-glow: oklch(72% 0.2 220 / 0.28); - --badge-skip-refresh-bg: oklch(82% 0.12 45); - --badge-skip-refresh-text: oklch(35% 0.02 45); - --badge-skip-refresh-glow: oklch(82% 0.12 45 / 0.15); - - /* Spacing Scale */ - --space-1: calc(8px * 1); - --space-2: calc(8px * 2); - --space-3: calc(8px * 3); - --space-4: calc(8px * 4); - - /* Z-index Scale */ - --z-base: 10; - --z-header: 100; - --z-modal: 1000; - --z-overlay: 2000; - - /* Border Radius */ - --border-radius-base: 12px; - --border-radius-md: 12px; - --border-radius-sm: 8px; - --border-radius-xs: 4px; - --scrollbar-width: 8px; - /* 添加滚动条宽度变量 */ - /* Shortcut styles */ - --shortcut-bg: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.12); - --shortcut-border: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.25); - --shortcut-text: var(--text-color); + --shortcut-bg: var(--color-accent-subtle); + --shortcut-border: var(--color-accent-border); + --shortcut-text: var(--text-primary); + + --lora-accent-transparent: var(--color-accent-transparent); + + /* Legacy spacing aliases: 8px base grid to match existing component usage */ + --space-1: 8px; + --space-2: 16px; + --space-3: 24px; + --space-4: 32px; + + /* Legacy border-radius aliases to match existing component usage */ + --border-radius-xs: 4px; + --border-radius-sm: 6px; + --border-radius-base: 8px; + --border-radius-md: 12px; + --border-radius-lg: 16px; +} + +:root { + --bg-color: var(--bg-base); + --text-color: var(--text-primary); + --text-muted: var(--text-secondary); + --card-bg: var(--surface-base); + --border-color: var(--border-base); + + --lora-accent: var(--color-accent); + --lora-surface: var(--bg-elevated); + --lora-border: var(--border-subtle); + --lora-text: var(--text-primary); + --lora-error: var(--color-error); + --lora-error-bg: var(--color-error-bg); + --lora-error-border: var(--color-error-border); + --lora-warning: var(--color-warning); + --lora-success: var(--color-success); + + --badge-update-bg: var(--color-info-bg); + --badge-update-text: var(--color-info-text); + --badge-update-glow: var(--color-info-glow); + --badge-skip-refresh-bg: var(--color-skip-refresh-bg); + --badge-skip-refresh-text: var(--color-skip-refresh-text); + --badge-skip-refresh-glow: var(--color-skip-refresh-glow); +} + +[data-theme="dark"] { + --bg-color: var(--bg-base); + --text-color: var(--text-primary); + --text-muted: var(--text-secondary); + --card-bg: var(--surface-base); + --border-color: var(--border-base); + + --lora-accent: var(--color-accent); + --lora-surface: var(--bg-elevated); + --lora-border: var(--border-subtle); + --lora-text: var(--text-primary); + --lora-error: var(--color-error); + --lora-error-bg: var(--color-error-bg); + --lora-error-border: var(--color-error-border); + --lora-warning: var(--color-warning); + --lora-success: var(--color-success); + + --badge-update-bg: var(--color-info-bg); + --badge-update-text: var(--color-info-text); + --badge-update-glow: var(--color-info-glow); + --badge-skip-refresh-bg: var(--color-skip-refresh-bg); + --badge-skip-refresh-text: var(--color-skip-refresh-text); + --badge-skip-refresh-glow: var(--color-skip-refresh-glow); } html[data-theme="dark"] { - background-color: #1a1a1a !important; + background-color: var(--bg-base) !important; color-scheme: dark; } html[data-theme="light"] { - background-color: #ffffff !important; + background-color: var(--bg-base) !important; color-scheme: light; } -[data-theme="dark"] { - --bg-color: #1a1a1a; - --text-color: #e0e0e0; - --text-muted: #a0a0a0; - --card-bg: #2d2d2d; - --border-color: #404040; - - --lora-accent: oklch(68% 0.28 256); - --lora-surface: oklch(25% 0.02 256 / 0.98); - --lora-border: oklch(90% 0.02 256 / 0.15); - --lora-text: oklch(98% 0.02 256); - --lora-warning: oklch(75% 0.25 80); - /* Modified to be used with oklch() */ - --lora-error-bg: color-mix(in oklch, var(--lora-error) 15%, transparent); - --lora-error-border: color-mix(in oklch, var(--lora-error) 40%, transparent); - --badge-update-bg: oklch(62% 0.18 220); - --badge-update-text: oklch(98% 0.02 240); - --badge-update-glow: oklch(62% 0.18 220 / 0.4); - --badge-skip-refresh-bg: oklch(82% 0.12 45); - --badge-skip-refresh-text: oklch(98% 0.02 45); - --badge-skip-refresh-glow: oklch(82% 0.12 45 / 0.15); -} - body { - font-family: 'Segoe UI', sans-serif; - background: var(--bg-color); - color: var(--text-color); + font-family: var(--font-body); + background: var(--bg-base); + color: var(--text-primary); display: flex; flex-direction: column; padding-top: 0; - /* Remove the padding-top */ } .hidden { display: none !important; -} \ No newline at end of file +} + +:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +button:focus:not(:focus-visible), +input:focus:not(:focus-visible), +select:focus:not(:focus-visible) { + outline: none; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + html { + scroll-behavior: auto !important; + } +} diff --git a/static/css/components/card.css b/static/css/components/card.css index cda903a7..b6301cc6 100644 --- a/static/css/components/card.css +++ b/static/css/components/card.css @@ -1,12 +1,12 @@ -/* 卡片网格布局 */ +/* Card grid layout */ .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); /* Base size */ gap: 12px; /* Consistent gap for both row and column spacing */ row-gap: 20px; /* Increase vertical spacing between rows */ margin-top: var(--space-2); - padding-top: 4px; /* 添加顶部内边距,为悬停动画提供空间 */ - padding-bottom: 4px; /* 添加底部内边距,为悬停动画提供空间 */ + padding-top: 4px; + padding-bottom: 4px; width: 100%; /* Ensure it takes full width of container */ max-width: 1400px; /* Base container width */ margin-left: auto; @@ -353,21 +353,26 @@ } .card-actions { + flex-shrink: 0; display: flex; - gap: var(--space-1); /* Use gap instead of margin for spacing between icons */ - align-items: center; + gap: var(--space-1); + align-items: flex-end; + align-self: flex-end; } -.card-actions i:hover { +.card-actions i:hover, +.card-actions i:focus-visible { opacity: 0.9; transform: scale(1.1); background-color: rgba(255, 255, 255, 0.1); + outline: 2px solid var(--lora-accent); + outline-offset: 2px; + border-radius: var(--border-radius-xs); } -/* Style for active favorites */ .favorite-active { - color: #ffc107 !important; /* Gold color for favorites */ - text-shadow: 0 0 5px rgba(255, 193, 7, 0.5); + color: var(--favorite-color) !important; + text-shadow: 0 0 5px var(--favorite-glow); } @media (max-width: 1200px) { @@ -391,14 +396,6 @@ } } -.card-actions { - flex-shrink: 0; /* Prevent actions from shrinking */ - display: flex; - gap: var(--space-1); - align-items: flex-end; /* 将图标靠下对齐 */ - align-self: flex-end; /* 将整个actions容器靠下对齐 */ -} - .model-link { margin-top: var(--space-1); } @@ -411,9 +408,13 @@ text-shadow: none; } -.model-link a:hover { +.model-link a:hover, +.model-link a:focus-visible { opacity: 0.8; text-decoration: none; + outline: 2px solid var(--lora-accent); + outline-offset: 2px; + border-radius: var(--border-radius-xs); } /* Updated model name to fix text cutoff issues */ @@ -438,7 +439,7 @@ .base-model { display: inline-block; - background: #f0f0f0; + background: var(--surface-hover, oklch(95% 0 0)); padding: 2px 6px; border-radius: var(--border-radius-xs); margin-right: 6px; diff --git a/static/css/components/header.css b/static/css/components/header.css index b0b60378..3d9d503d 100644 --- a/static/css/components/header.css +++ b/static/css/components/header.css @@ -191,9 +191,11 @@ } .header-search .search-options-toggle:hover, -.header-search .search-filter-toggle:hover { - background: var(--lora-surface-hover, oklch(95% 0.02 256)); - color: var(--lora-accent); +.header-search .search-filter-toggle:hover, +.header-search .search-filter-toggle:focus-visible { + background: var(--lora-surface-hover, oklch(95% 0.02 256)); + color: var(--lora-accent); + outline: none; } .header-search .filter-badge { @@ -366,9 +368,18 @@ flex-shrink: 0; } -.hamburger-menu-btn:hover { - background: var(--lora-accent); - color: white; +.hamburger-menu-btn:hover, +.hamburger-menu-btn:focus-visible { + background: var(--lora-surface-hover, oklch(95% 0.02 256)); + color: var(--lora-accent); + outline: none; +} + +.hamburger-dropdown .dropdown-item:hover, +.hamburger-dropdown .dropdown-item:focus-visible { + background: var(--lora-surface-hover, oklch(95% 0.02 256)); + color: var(--lora-accent); + outline: none; } /* Hamburger dropdown menu */ diff --git a/static/css/components/loading.css b/static/css/components/loading.css index 5c898ec5..9e23bad7 100644 --- a/static/css/components/loading.css +++ b/static/css/components/loading.css @@ -18,7 +18,7 @@ border-radius: var(--border-radius-base); text-align: center; border: 1px solid var(--lora-border); - width: min(400px, 90vw); /* 固定最大宽度,但保持响应式 */ + width: min(400px, 90vw); } .loading-spinner { @@ -33,7 +33,7 @@ .loading-status { margin-bottom: 1rem; - color: var(--text-color); /* 使用主题文本颜色 */ + color: var(--text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -42,11 +42,11 @@ } .progress-container { - width: 280px; /* 固定进度条宽度 */ - background-color: var(--lora-border); /* 使用主题边框颜色 */ + width: 280px; + background-color: var(--lora-border); border-radius: 4px; overflow: hidden; - margin: 0 auto; /* 居中显示 */ + margin: 0 auto; } .progress-bar { diff --git a/static/css/components/lora-modal/lora-modal.css b/static/css/components/lora-modal/lora-modal.css index b5e85cd1..60ff513f 100644 --- a/static/css/components/lora-modal/lora-modal.css +++ b/static/css/components/lora-modal/lora-modal.css @@ -105,14 +105,14 @@ .info-item { padding: var(--space-2); - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); } -/* 调整深色主题下的样式 */ +/* Dark theme info item styles */ [data-theme="dark"] .info-item { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } @@ -271,13 +271,13 @@ } } -/* 修改 back-to-top 按钮样式,使其固定在 modal 内部 */ +/* Back-to-top button pinned inside modal */ .modal-content .back-to-top { - position: sticky; /* 改用 sticky 定位 */ - float: right; /* 使用 float 确保按钮在右侧 */ - bottom: 20px; /* 距离底部的距离 */ - margin-right: 20px; /* 右侧间距 */ - margin-top: -56px; /* 负边距确保不占用额外空间 */ + position: sticky; + float: right; + bottom: 20px; + margin-right: 20px; + margin-top: -56px; width: 36px; height: 36px; border-radius: 50%; @@ -334,7 +334,7 @@ outline: none; } -/* 合并编辑按钮样式 */ +/* Consolidated edit button styles */ .edit-model-name-btn, .edit-file-name-btn, .edit-base-model-btn, @@ -369,7 +369,7 @@ .edit-base-model-btn:hover, .edit-model-description-btn:hover, .edit-version-name-btn:hover { - opacity: 0.8 !important; + opacity: 0.8; background: rgba(0, 0, 0, 0.05); } @@ -387,7 +387,7 @@ } .base-wrapper { - flex: 2; /* 分配更多空间给base model */ + flex: 2; /* Allocate more space to base model */ } /* Base model display and editing styles */ @@ -447,7 +447,7 @@ margin: 0; padding: var(--space-1); border-radius: var(--border-radius-xs); - font-size: 1.5em !important; + font-size: 1.5em; font-weight: 600; line-height: 1.2; color: var(--text-color); @@ -888,7 +888,7 @@ align-items: center; gap: 10px; padding: 2px 10px; - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); max-width: fit-content; @@ -899,7 +899,7 @@ [data-theme="dark"] .creator-info, [data-theme="dark"] .civitai-view, [data-theme="dark"] .modal-send-btn { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } @@ -958,7 +958,7 @@ align-items: center; gap: 6px; padding: 6px 12px; - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); color: var(--text-color); @@ -981,7 +981,7 @@ align-items: center; gap: 6px; padding: 6px 12px; - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); color: var(--text-color); @@ -992,7 +992,7 @@ } [data-theme="dark"] .modal-send-btn { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } diff --git a/static/css/components/lora-modal/showcase.css b/static/css/components/lora-modal/showcase.css index d540e5ae..7d199d0f 100644 --- a/static/css/components/lora-modal/showcase.css +++ b/static/css/components/lora-modal/showcase.css @@ -455,9 +455,9 @@ } .import-formats { - font-size: 0.8em !important; - opacity: 0.6 !important; - margin-top: var(--space-2) !important; + font-size: 0.8em; + opacity: 0.6; + margin-top: var(--space-2); } .select-files-btn { @@ -481,7 +481,7 @@ /* For dark theme */ [data-theme="dark"] .import-container { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); } /* Setup Guidance State - When example images path is not configured */ diff --git a/static/css/components/lora-modal/tag.css b/static/css/components/lora-modal/tag.css index 3794552d..1bae00bc 100644 --- a/static/css/components/lora-modal/tag.css +++ b/static/css/components/lora-modal/tag.css @@ -21,7 +21,7 @@ .model-tag-compact { /* Updated styles to match info-item appearance */ - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-xs); padding: 2px 8px; @@ -45,7 +45,7 @@ /* Adjust dark theme tag styles */ [data-theme="dark"] .model-tag-compact { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } @@ -101,7 +101,7 @@ .tooltip-tag { /* Updated styles to match info-item appearance */ - background: rgba(0, 0, 0, 0.03); + background: var(--surface-hover); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-xs); padding: 3px 8px; @@ -111,7 +111,7 @@ /* Adjust dark theme tooltip tag styles */ [data-theme="dark"] .tooltip-tag { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-hover); border: 1px solid var(--lora-border); } diff --git a/static/css/components/lora-modal/triggerwords.css b/static/css/components/lora-modal/triggerwords.css index 1c695b08..f6c39878 100644 --- a/static/css/components/lora-modal/triggerwords.css +++ b/static/css/components/lora-modal/triggerwords.css @@ -1,14 +1,14 @@ /* Update Trigger Words styles */ .info-item.trigger-words { padding: var(--space-2); - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); } -/* 调整 trigger words 样式 */ +/* Trigger words styles */ [data-theme="dark"] .info-item.trigger-words { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } diff --git a/static/css/components/lora-modal/versions.css b/static/css/components/lora-modal/versions.css index 9239218d..80a0b011 100644 --- a/static/css/components/lora-modal/versions.css +++ b/static/css/components/lora-modal/versions.css @@ -186,7 +186,7 @@ height: 88px; border-radius: var(--border-radius-xs); overflow: hidden; - background: rgba(0, 0, 0, 0.03); + background: var(--surface-hover); display: flex; align-items: center; justify-content: center; diff --git a/static/css/components/menu.css b/static/css/components/menu.css index 230bd1f6..437ec79c 100644 --- a/static/css/components/menu.css +++ b/static/css/components/menu.css @@ -21,9 +21,11 @@ background: var(--lora-surface); } -.context-menu-item:hover { +.context-menu-item:hover, +.context-menu-item:focus-visible { background-color: var(--lora-accent); color: var(--lora-text); + outline: none; } .context-menu-separator { diff --git a/static/css/components/modal/_base.css b/static/css/components/modal/_base.css index be4f21b0..53c1a998 100644 --- a/static/css/components/modal/_base.css +++ b/static/css/components/modal/_base.css @@ -1,4 +1,4 @@ -/* modal 基础样式 */ +/* Modal base styles */ .modal { display: none; position: fixed; @@ -6,19 +6,19 @@ left: 0; width: 100%; height: calc(100% - var(--header-height, 48px)); /* Adjust height to exclude header */ - background: rgba(0, 0, 0, 0.2); /* 调整为更淡的半透明黑色 */ + background: rgba(0, 0, 0, 0.2); z-index: var(--z-modal); overflow: auto; /* Change from hidden to auto to allow scrolling */ } -/* 当模态窗口打开时,禁止body滚动 */ +/* Prevent body scroll when modal is open */ body.modal-open { position: fixed; width: 100%; - padding-right: var(--scrollbar-width, 0px); /* 补偿滚动条消失导致的页面偏移 */ + padding-right: var(--scrollbar-width, 0px); } -/* modal-content 样式 */ +/* Modal content styles */ .modal-content { position: relative; max-width: 800px; @@ -29,12 +29,9 @@ body.modal-open { border-radius: var(--border-radius-base); padding: var(--space-3); border: 1px solid var(--lora-border); - box-shadow: - 0 4px 6px -1px rgba(0, 0, 0, 0.1), - 0 2px 4px -1px rgba(0, 0, 0, 0.06), - 0 10px 15px -3px rgba(0, 0, 0, 0.05); + box-shadow: var(--shadow-md); overflow-y: auto; - overflow-x: hidden; /* 防止水平滚动条 */ + overflow-x: hidden; scrollbar-gutter: stable both-edges; /* Reserve space to prevent layout shift when scrollbar toggles */ } @@ -42,10 +39,10 @@ body.modal-open { min-height: 480px; } -/* 当 modal 打开时锁定 body */ +/* Lock body when modal is open */ body.modal-open { - overflow: hidden !important; /* 覆盖 base.css 中的 scroll */ - padding-right: var(--scrollbar-width, 8px); /* 使用滚动条宽度作为补偿 */ + overflow: hidden !important; + padding-right: var(--scrollbar-width, 8px); } @keyframes modalFadeIn { @@ -67,12 +64,18 @@ body.modal-open { } .cancel-btn, .delete-btn, .exclude-btn, .confirm-btn { - padding: 8px var(--space-2); - border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-2); + border-radius: var(--border-radius-sm); border: none; cursor: pointer; font-weight: 500; + font-size: 0.95em; min-width: 100px; + transition: background-color 0.2s, opacity 0.2s; } .cancel-btn { @@ -92,16 +95,20 @@ body.modal-open { color: white; } -.cancel-btn:hover { +.cancel-btn:hover, +.cancel-btn:focus-visible { background: var(--lora-border); } -.delete-btn:hover { - opacity: 0.9; +.delete-btn:hover, +.delete-btn:focus-visible { + background: oklch(from var(--lora-error) l c h / 85%); } -.exclude-btn:hover, .confirm-btn:hover { - opacity: 0.9; +.exclude-btn:hover, +.exclude-btn:focus-visible, +.confirm-btn:hover, +.confirm-btn:focus-visible { background: oklch(from var(--lora-accent, #4f46e5) l c h / 85%); } @@ -125,43 +132,37 @@ body.modal-open { z-index: 10; } -.close:hover { +.close:hover, +.close:focus-visible { opacity: 1; + outline: 2px solid var(--lora-accent); + outline-offset: 2px; + border-radius: var(--border-radius-xs); } -/* 统一各个 section 的样式 */ +/* Unified section styles */ .support-section, .changelog-section, .update-info, .info-item, .path-preview { - background: rgba(0, 0, 0, 0.03); - border: 1px solid rgba(0, 0, 0, 0.1); + background: var(--surface-subtle); + border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); padding: var(--space-2); } -/* 深色主题统一样式 */ +/* Dark theme unified styles */ [data-theme="dark"] .modal-content { background: var(--lora-surface); border: 1px solid var(--lora-border); } -[data-theme="dark"] .support-section, -[data-theme="dark"] .changelog-section, -[data-theme="dark"] .update-info, -[data-theme="dark"] .info-item, -[data-theme="dark"] .path-preview, -[data-theme="dark"] #bulkDownloadMissingLorasModal .bulk-download-loras-preview { - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--lora-border); -} - .primary-btn { display: flex; align-items: center; gap: 8px; - padding: 8px 16px; + padding: var(--space-1) var(--space-2); background-color: var(--lora-accent); color: var(--lora-text); border: none; @@ -171,9 +172,11 @@ body.modal-open { font-size: 0.95em; } -.primary-btn:hover { +.primary-btn:hover, +.primary-btn:focus-visible { background-color: oklch(from var(--lora-accent) l c h / 85%); color: var(--lora-text); + outline: none; } /* Secondary button styles */ @@ -181,9 +184,9 @@ body.modal-open { display: flex; align-items: center; gap: 8px; - padding: 8px 16px; + padding: var(--space-1) var(--space-2); background-color: var(--card-bg); - color: var (--text-color); + color: var(--text-color); border: 1px solid var(--border-color); border-radius: var(--border-radius-sm); cursor: pointer; @@ -191,9 +194,11 @@ body.modal-open { font-size: 0.95em; } -.secondary-btn:hover { +.secondary-btn:hover, +.secondary-btn:focus-visible { background-color: var(--border-color); color: var(--text-color); + outline: none; } /* Disabled button styles */ @@ -244,7 +249,7 @@ button:disabled, display: flex; align-items: center; gap: 8px; - padding: 8px 16px; + padding: var(--space-1) var(--space-2); background-color: var(--lora-error); color: white; border: none; @@ -254,25 +259,22 @@ button:disabled, font-size: 0.95em; } -.danger-btn:hover { +.danger-btn:hover, +.danger-btn:focus-visible { background-color: oklch(from var(--lora-error) l c h / 85%); color: white; + outline: none; } /* Metadata archive status styles */ .metadata-archive-status { - background: rgba(0, 0, 0, 0.03); - border: 1px solid rgba(0, 0, 0, 0.1); + background: var(--surface-subtle); + border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); padding: var(--space-2); margin-bottom: var(--space-2); } -[data-theme="dark"] .metadata-archive-status { - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--lora-border); -} - .archive-status-item { display: flex; justify-content: space-between; @@ -312,17 +314,12 @@ button:disabled, } .backup-status { - background: rgba(0, 0, 0, 0.03); - border: 1px solid rgba(0, 0, 0, 0.1); + background: var(--surface-subtle); + border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); padding: var(--space-3); } -[data-theme="dark"] .backup-status { - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--lora-border); -} - .backup-summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); @@ -331,17 +328,12 @@ button:disabled, } .backup-summary-card { - background: rgba(255, 255, 255, 0.5); - border: 1px solid rgba(0, 0, 0, 0.06); + background: var(--lora-surface); + border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); padding: var(--space-2); } -[data-theme="dark"] .backup-summary-card { - background: rgba(255, 255, 255, 0.02); - border-color: rgba(255, 255, 255, 0.05); -} - .backup-summary-label { color: var(--text-color); font-size: 0.85rem; @@ -404,14 +396,9 @@ button:disabled, } .backup-location-details { - border: 1px solid rgba(0, 0, 0, 0.1); + border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); - background: rgba(0, 0, 0, 0.02); -} - -[data-theme="dark"] .backup-location-details { - border-color: var(--lora-border); - background: rgba(255, 255, 255, 0.02); + background: var(--surface-subtle); } .backup-location-details summary { @@ -442,16 +429,12 @@ button:disabled, max-width: 100%; padding: 6px 8px; border-radius: var(--border-radius-sm); - background: rgba(0, 0, 0, 0.05); + background: var(--surface-subtle); color: var(--text-color); overflow-wrap: anywhere; word-break: break-word; } -[data-theme="dark"] .backup-location-path { - background: rgba(255, 255, 255, 0.05); -} - @media (max-width: 768px) { .backup-status-row { grid-template-columns: 1fr; @@ -519,8 +502,8 @@ button:disabled, } #bulkDownloadMissingLorasModal .bulk-download-loras-preview { - background: rgba(0, 0, 0, 0.03); - border: 1px solid rgba(0, 0, 0, 0.1); + background: var(--surface-subtle); + border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); padding: var(--space-3); margin-bottom: var(--space-3); @@ -578,7 +561,7 @@ button:disabled, align-items: flex-start; gap: var(--space-2); padding: var(--space-2); - background: rgba(59, 130, 246, 0.1); + background: oklch(from var(--lora-accent) l c h / 0.1); border-radius: var(--border-radius-sm); font-size: 0.9em; color: var(--text-color); diff --git a/static/css/components/modal/doctor-modal.css b/static/css/components/modal/doctor-modal.css index 3ddc7915..ebbb3bbb 100644 --- a/static/css/components/modal/doctor-modal.css +++ b/static/css/components/modal/doctor-modal.css @@ -48,8 +48,7 @@ padding: var(--space-3); border-radius: var(--border-radius-sm); border: 1px solid var(--lora-border); - background: rgba(0, 0, 0, 0.03); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); } .doctor-kicker { @@ -128,7 +127,7 @@ .doctor-issue-card { border: 1px solid rgba(0, 0, 0, 0.1); - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border-radius: var(--border-radius-sm); padding: var(--space-3); box-shadow: none; @@ -242,7 +241,7 @@ [data-theme="dark"] .doctor-hero, [data-theme="dark"] .doctor-issue-card { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border-color: var(--lora-border); box-shadow: none; } diff --git a/static/css/components/modal/help-modal.css b/static/css/components/modal/help-modal.css index 6e0c4492..0343c16a 100644 --- a/static/css/components/modal/help-modal.css +++ b/static/css/components/modal/help-modal.css @@ -303,5 +303,5 @@ /* Dark theme adjustments */ [data-theme="dark"] .video-container { - background-color: rgba(255, 255, 255, 0.03); + background-color: var(--surface-hover); } \ No newline at end of file diff --git a/static/css/components/modal/settings-modal.css b/static/css/components/modal/settings-modal.css index 66fc53a6..0b30a5e2 100644 --- a/static/css/components/modal/settings-modal.css +++ b/static/css/components/modal/settings-modal.css @@ -927,7 +927,7 @@ input:checked + .toggle-slider:before { /* Path Template Settings Styles */ .template-preview { - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-xs); padding: var(--space-1); @@ -939,7 +939,7 @@ input:checked + .toggle-slider:before { } [data-theme="dark"] .template-preview { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } diff --git a/static/css/components/modal/support-modal.css b/static/css/components/modal/support-modal.css index f52e5106..9d7440fe 100644 --- a/static/css/components/modal/support-modal.css +++ b/static/css/components/modal/support-modal.css @@ -58,8 +58,6 @@ } .support-section { - background: rgba(0, 0, 0, 0.02); - border: 1px solid rgba(0, 0, 0, 0.08); border-radius: var(--border-radius-sm); padding: var(--space-2); margin-bottom: var(--space-2); @@ -258,7 +256,7 @@ color: white; /* Icon color changes to white on hover */ } -/* 增强hover状态的视觉反馈 */ +/* Enhanced hover visual feedback */ .social-link:hover, .update-link:hover, .folder-item:hover { diff --git a/static/css/components/modal/update-modal.css b/static/css/components/modal/update-modal.css index c71fe222..affcce69 100644 --- a/static/css/components/modal/update-modal.css +++ b/static/css/components/modal/update-modal.css @@ -171,7 +171,7 @@ /* Update progress styles */ .update-progress { - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); border-radius: var(--border-radius-sm); padding: var(--space-2); @@ -179,7 +179,7 @@ } [data-theme="dark"] .update-progress { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); } .progress-info { @@ -234,8 +234,6 @@ /* Changelog section */ .changelog-section { - background: rgba(0, 0, 0, 0.02); - border: 1px solid rgba(0, 0, 0, 0.08); border-radius: var(--border-radius-sm); padding: var(--space-3); } @@ -429,7 +427,7 @@ } [data-theme="dark"] .banner-history-item { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); } .banner-history-title { diff --git a/static/css/components/progress-panel.css b/static/css/components/progress-panel.css index 0dd8d4b9..2182ccea 100644 --- a/static/css/components/progress-panel.css +++ b/static/css/components/progress-panel.css @@ -67,9 +67,17 @@ position: relative; } -.icon-button:hover { - opacity: 1; - background: rgba(0, 0, 0, 0.05); +.icon-button:hover, +.icon-button:focus-visible { + background: var(--lora-surface-hover, oklch(95% 0.02 256)); + color: var(--lora-accent); + transform: scale(1.05); + outline: none; +} + +[data-theme="dark"] .icon-button:hover, +[data-theme="dark"] .icon-button:focus-visible { + background: oklch(35% 0.02 256 / 0.98); } [data-theme="dark"] .icon-button:hover { diff --git a/static/css/components/recipe-modal.css b/static/css/components/recipe-modal.css index 23be8846..518a0542 100644 --- a/static/css/components/recipe-modal.css +++ b/static/css/components/recipe-modal.css @@ -99,7 +99,7 @@ font-size: 0.9em; } -/* 删除不再需要的按钮样式 */ +/* Remove obsolete button styles */ .editor-actions { display: none; } @@ -144,7 +144,7 @@ } .recipe-tag-compact { - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-xs); padding: 2px 8px; @@ -154,7 +154,7 @@ } [data-theme="dark"] .recipe-tag-compact { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } @@ -203,7 +203,7 @@ } .tooltip-tag { - background: rgba(0, 0, 0, 0.03); + background: var(--surface-hover); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-xs); padding: 3px 8px; @@ -212,7 +212,7 @@ } [data-theme="dark"] .tooltip-tag { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-hover); border: 1px solid var(--lora-border); } @@ -251,7 +251,7 @@ align-items: center; gap: 6px; padding: 6px 12px; - background: rgba(0, 0, 0, 0.03); + background: var(--surface-subtle); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); color: var(--text-color); @@ -263,7 +263,7 @@ } [data-theme="dark"] .recipe-source-url-btn { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-subtle); border: 1px solid var(--lora-border); } @@ -1114,9 +1114,9 @@ color: #777; } -/* 标题输入框特定的样式 */ +/* Title input specific styles */ .title-input { - font-size: 1.2em !important; /* 调整为更合适的大小 */ + font-size: 1.2em; line-height: 1.2; font-weight: 500; } diff --git a/static/css/components/search-filter.css b/static/css/components/search-filter.css index beed5b74..5eec03b1 100644 --- a/static/css/components/search-filter.css +++ b/static/css/components/search-filter.css @@ -7,7 +7,7 @@ gap: 4px; } -/* 调整搜索框样式以匹配其他控件 */ +/* Match search input styles to other controls */ .search-container input { width: 100%; padding: 6px 35px 6px 12px; /* Reduced right padding */ @@ -35,7 +35,7 @@ line-height: 1; } -/* 修改清空按钮样式 */ +/* Clear button styles */ .search-clear { position: absolute; right: 105px; /* Adjusted further left to avoid overlapping */ diff --git a/static/css/components/shared/edit-metadata.css b/static/css/components/shared/edit-metadata.css index 4341e96d..d0674757 100644 --- a/static/css/components/shared/edit-metadata.css +++ b/static/css/components/shared/edit-metadata.css @@ -31,7 +31,7 @@ /* Edit Container */ .metadata-edit-container { padding: var(--space-2); - background: rgba(0, 0, 0, 0.03); + background: var(--surface-hover); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: var(--border-radius-sm); margin-top: var(--space-2); @@ -42,7 +42,7 @@ } [data-theme="dark"] .metadata-edit-container { - background: rgba(255, 255, 255, 0.03); + background: var(--surface-hover); border: 1px solid var(--lora-border); } diff --git a/static/css/components/sidebar.css b/static/css/components/sidebar.css index 801cb255..6b714d3f 100644 --- a/static/css/components/sidebar.css +++ b/static/css/components/sidebar.css @@ -707,19 +707,25 @@ color: var(--text-muted); } -.sidebar-create-folder-btn:hover { +.sidebar-create-folder-btn:hover, +.sidebar-create-folder-btn:focus-visible { background: var(--lora-surface); color: var(--text-color); + outline: none; } -.sidebar-create-folder-confirm:hover { +.sidebar-create-folder-confirm:hover, +.sidebar-create-folder-confirm:focus-visible { background: oklch(from var(--success-color) l c h / 0.15); color: var(--success-color); + outline: none; } -.sidebar-create-folder-cancel:hover { +.sidebar-create-folder-cancel:hover, +.sidebar-create-folder-cancel:focus-visible { background: oklch(from var(--error-color) l c h / 0.15); color: var(--error-color); + outline: none; } .sidebar-create-folder-hint { diff --git a/static/css/components/toast.css b/static/css/components/toast.css index f8b692f8..713ab1f3 100644 --- a/static/css/components/toast.css +++ b/static/css/components/toast.css @@ -15,18 +15,18 @@ /* Toast Notifications */ .toast { position: fixed; - top: 20px; /* 改为从顶部显示 */ - right: 20px; /* 改为右对齐 */ - left: auto; /* 移除左对齐 */ - transform: translateX(120%); /* 初始位置在屏幕右侧外 */ - min-width: 300px; /* 设置最小宽度 */ - max-width: 400px; /* 设置最大宽度 */ + top: 20px; + right: 20px; + left: auto; + transform: translateX(120%); + min-width: 300px; + max-width: 400px; background: var(--lora-surface); color: var(--text-color); padding: 12px 16px; border-radius: var(--border-radius-sm); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); - z-index: calc(var(--z-overlay) + 10); /* 让toast显示在最上层 */ + z-index: calc(var(--z-overlay) + 10); opacity: 0; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); @@ -36,11 +36,10 @@ } .toast.show { - transform: translateX(0); /* 显示时滑入到正确位置 */ + transform: translateX(0); opacity: 1; } -/* 添加图标容器 */ .toast::before { content: ''; width: 20px; @@ -51,7 +50,7 @@ background-size: contain; } -/* 不同类型的toast样式 */ +/* Toast type variants */ .toast-success { border-left: 4px solid oklch(65% 0.2 142); } @@ -76,15 +75,15 @@ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%232196f3'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z'/%3E%3C/svg%3E"); } -/* 多个toast堆叠显示 */ +/* Stacked toast spacing */ .toast + .toast { margin-top: 10px; } -/* 响应式调整 */ +/* Responsive adjustments */ @media (max-width: 768px) { .toast { - width: calc(100% - 40px); /* 左右各留20px间距 */ + width: calc(100% - 40px); max-width: none; right: 20px; } diff --git a/static/css/layout.css b/static/css/layout.css index 23cb71c5..d81e957e 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -22,7 +22,7 @@ z-index: calc(var(--z-header) - 1); background: var(--bg-color); padding: var(--space-1) 0; - box-shadow: 0 1px 3px rgba(0,0,0,0.05); + box-shadow: var(--shadow-xs); } /* Responsive container for larger screens */ @@ -80,19 +80,21 @@ font-size: 0.85em; transition: all 0.2s ease; cursor: pointer; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: var(--shadow-xs); } -.control-group button:hover { +.control-group button:hover, +.control-group button:focus-visible { border-color: var(--lora-accent); background: var(--bg-color); transform: translateY(-1px); - box-shadow: 0 3px 5px rgba(0, 0, 0, 0.08); + box-shadow: var(--shadow-lg); + outline: none; } .control-group button:active { transform: translateY(0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: var(--shadow-xs); } .control-group button i { @@ -100,7 +102,8 @@ transition: opacity 0.2s ease; } -.control-group button:hover i { +.control-group button:hover i, +.control-group button:focus-visible i { opacity: 1; } @@ -131,7 +134,7 @@ .control-group button.favorite-filter i { margin-right: 4px; - color: #ffc107; + color: var(--favorite-color); } .control-group button.update-filter i { @@ -220,7 +223,7 @@ background-size: 14px; cursor: pointer; transition: all 0.2s ease; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: var(--shadow-xs); } /* Style for optgroups */ @@ -252,7 +255,7 @@ border-color: var(--lora-accent); background-color: var(--bg-color); transform: translateY(-1px); - box-shadow: 0 3px 5px rgba(0, 0, 0, 0.08); + box-shadow: var(--shadow-lg); } .control-group select:focus { @@ -294,7 +297,7 @@ transform: translateY(10px); transition: all 0.3s ease; z-index: var(--z-overlay); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + box-shadow: var(--shadow-sm); } .back-to-top.visible { @@ -307,7 +310,7 @@ background: var(--lora-accent); color: white; transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + box-shadow: var(--shadow-md); } /* Prevent text selection in control and header areas */ @@ -336,7 +339,7 @@ .dropdown-main { border-top-right-radius: 0; border-bottom-right-radius: 0; - border-right: 1px solid rgba(0, 0, 0, 0.1); + border-right: 1px solid var(--border-color); } .dropdown-toggle { @@ -364,7 +367,7 @@ background-color: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius-xs); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: var(--shadow-xl); } .dropdown-group.active .dropdown-menu { diff --git a/static/css/style.css b/static/css/style.css index 17370b2b..2e7b8e3f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -54,7 +54,7 @@ text-align: center; } -/* 使用已有的loading-spinner样式 */ +/* Reuse existing loading-spinner styles */ .initialization-notice .loading-spinner { margin-bottom: var(--space-2); } diff --git a/static/css/tokens/MIGRATION.md b/static/css/tokens/MIGRATION.md new file mode 100644 index 00000000..dd46ebed --- /dev/null +++ b/static/css/tokens/MIGRATION.md @@ -0,0 +1,142 @@ +# Lora-Manager UI Token Migration Guide + +## Overview + +The design token system has been created in `static/css/tokens/`. `base.css` now imports the tokens and provides backward-compatible aliases for existing component CSS. + +## Token Files + +| File | Purpose | +|------|---------| +| `tokens/colors.css` | OKLch color primitives + semantic light/dark tokens | +| `tokens/typography.css` | Font stacks, type scale, weights, line heights | +| `tokens/spacing.css` | 4px-base grid with legacy aliases | +| `tokens/effects.css` | Border radius, shadows, transitions | +| `tokens/breakpoints.css` | Named breakpoint variables | +| `tokens/z-index.css` | Stacking context scale | +| `tokens/index.css` | Aggregator that imports all token files | + +## Backward Compatibility + +Old variable names in component CSS still work via aliases in `base.css`: + +| Old Name | Maps To | +|----------|---------| +| `--bg-color` | `--bg-base` | +| `--text-color` | `--text-primary` | +| `--text-muted` | `--text-secondary` | +| `--card-bg` | `--surface-base` | +| `--border-color` | `--border-base` | +| `--lora-accent` | `--color-accent` | +| `--lora-surface` | `--bg-elevated` | +| `--lora-border` | `--border-subtle` | +| `--space-1` (8px) | `--space-1-legacy` | +| `--border-radius-base` | `--radius-lg` | + +## Phase 2: Component Audit Checklist + +Below are the hardcoded values found across component CSS that should be replaced with tokens. + +### Critical Fixes (P0) + +- [ ] **card.css line 441**: `.base-model { background: #f0f0f0; }` → use `--bg-hover` or new `--surface-variant` +- [ ] **card.css line 369**: `.favorite-active { color: #ffc107 !important; }` → use `--favorite-color` (already defined in tokens) +- [ ] **layout.css line 134**: `.control-group button.favorite-filter i { color: #ffc107; }` → use `--favorite-color` +- [ ] **header.css lines 233-250**: Hardcoded dark theme colors (`#3a3a3a`, `#888888`, `#555555`) → use `--bg-disabled`, `--text-secondary`, `--border-base` + +### Spacing Normalization (P1) + +Replace hard pixel values with token equivalents: + +- [ ] `padding: 4px 10px` → `padding: var(--space-1) var(--space-3)` +- [ ] `gap: 6px` → `gap: var(--space-1-legacy)` or `gap: var(--space-2)` +- [ ] `gap: 8px` → `gap: var(--space-2)` +- [ ] `gap: 12px` → `gap: var(--space-3)` +- [ ] `padding: 15px` → `padding: var(--space-4)` +- [ ] `padding: 16px` → `padding: var(--space-4)` +- [ ] `margin-top: 2px` → `margin-top: var(--space-0-5)` +- [ ] `padding: 2px 6px` → `padding: var(--space-0-5) var(--space-2)` +- [ ] `border-radius: 50%` → `border-radius: var(--radius-full)` + +### Shadow Standardization (P1) + +Replace hardcoded shadows with token equivalents: + +- [ ] `box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)` → `box-shadow: var(--shadow-xs)` +- [ ] `box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05)` → `box-shadow: var(--shadow-sm)` +- [ ] `box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)` → `box-shadow: var(--shadow-md)` +- [ ] `box-shadow: 0 3px 5px rgba(0, 0, 0, 0.08)` → `box-shadow: var(--shadow-lg)` +- [ ] `box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15)` → `box-shadow: var(--shadow-xl)` +- [ ] `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08)` → combine or add new token + +### Typography Normalization (P1) + +Replace scattered font sizes with type scale: + +- [ ] `font-size: 0.8em` → `font-size: var(--text-xs)` +- [ ] `font-size: 0.85em` → `font-size: var(--text-sm)` +- [ ] `font-size: 0.9em` → `font-size: var(--text-sm)` +- [ ] `font-size: 0.95em` → `font-size: var(--text-md)` +- [ ] `font-size: 1.1em` → `font-size: var(--text-lg)` +- [ ] `font-size: 11px` → `font-size: var(--text-xs)` + +### Breakpoint Normalization (P2) + +Replace magic numbers with named breakpoints: + +- [ ] `@media (min-width: 2150px)` → `@media (min-width: var(--bp-ultrawide))` +- [ ] `@media (min-width: 3000px)` → `@media (min-width: var(--bp-4k))` +- [ ] `@media (max-width: 768px)` → `@media (max-width: var(--bp-mobile))` +- [ ] `@media (max-width: 1200px)` → `@media (max-width: var(--bp-desktop))` + +### Z-Index Cleanup (P2) + +Replace magic z-index values with tokens: + +- [ ] `z-index: 2` / `z-index: 3` / `z-index: 4` in card.css → use `--z-base` + calc +- [ ] `z-index: 200` in header.css (hamburger dropdown) → use `--z-dropdown` + +### Remaining Hardcoded Colors (P2) + +- [ ] `rgba(0, 184, 122, 0.05)` and `#00B87A` in import-modal.css → use `--color-success` +- [ ] `rgba(255, 255, 255, 0.12)` in card.css (base-model-label background) → use token +- [ ] `rgba(255, 255, 255, 0.25)` in card.css (separator) → use `--border-inverse` +- [ ] `rgba(0, 0, 0, 0.5)` and `rgba(0, 0, 0, 0.7)` in card.css (toggle blur btn) → use `--bg-overlay` variants +- [ ] `rgba(46, 204, 113, 0.3)` and `rgba(231, 76, 60, 0.3)` in card.css → use success/error tokens + +## New Tokens Added + +The following tokens were added beyond the existing system: + +| Token | Value | Use Case | +|-------|-------|----------| +| `--color-accent-hover` | oklch(58% 0.28 256) | Hover states for accent buttons | +| `--color-accent-subtle` | accent @ 12% opacity | Subtle accent backgrounds | +| `--color-accent-border` | accent @ 25% opacity | Accent borders | +| `--color-accent-transparent` | accent @ 60% opacity | Glow effects, pulse animations | +| `--bg-hover` | oklch(95% 0.02 256) / dark: oklch(35% 0.02 256) | Hover backgrounds | +| `--bg-disabled` | #f5f5f5 / dark: #3a3a3a | Disabled input backgrounds | +| `--bg-overlay` | oklch(0% 0 0 / 0.75) | Modal overlays, gradients | +| `--surface-hover` | oklch(95% 0.02 256) / dark: oklch(35% 0.02 256) | Card/panel hover | +| `--favorite-color` | #d4a017 | Accessible gold for favorites | +| `--shadow-focus` | 0 0 0 1px accent | Focus ring shadow | +| `--shadow-glow` | 0 2px 6px info-glow | Badge glow effects | +| `--transition-bounce` | 200ms cubic-bezier | Playful hover transitions | + +## Migration Order Recommendation + +1. **Start with colors**: Replace `#ffc107` and `#f0f0f0` (highest visual impact) +2. **Then spacing**: Unify padding/gap values (biggest consistency win) +3. **Then shadows**: Replace rgba shadows with tokens +4. **Then typography**: Standardize font sizes +5. **Finally breakpoints + z-index**: Lower priority but good for maintainability + +## Testing Checklist + +After each component file is migrated: + +- [ ] Light theme renders correctly +- [ ] Dark theme renders correctly +- [ ] No visual regressions in card grid, header, modals +- [ ] Focus states still visible +- [ ] Hover transitions still work (unless prefers-reduced-motion) diff --git a/static/css/tokens/breakpoints.css b/static/css/tokens/breakpoints.css new file mode 100644 index 00000000..3d03aff2 --- /dev/null +++ b/static/css/tokens/breakpoints.css @@ -0,0 +1,8 @@ +:root { + --bp-mobile: 768px; + --bp-tablet: 1024px; + --bp-desktop: 1400px; + --bp-wide: 1920px; + --bp-ultrawide: 2150px; + --bp-4k: 3000px; +} diff --git a/static/css/tokens/colors.css b/static/css/tokens/colors.css new file mode 100644 index 00000000..f50b5e95 --- /dev/null +++ b/static/css/tokens/colors.css @@ -0,0 +1,117 @@ +:root { + --color-accent-l: 68%; + --color-accent-c: 0.28; + --color-accent-h: 256; + --color-warning-l: 75%; + --color-warning-c: 0.25; + --color-warning-h: 80; + --color-success-l: 70%; + --color-success-c: 0.2; + --color-success-h: 140; + --color-error-l: 75%; + --color-error-c: 0.32; + --color-error-h: 29; + --color-info-l: 72%; + --color-info-c: 0.2; + --color-info-h: 220; + --color-neutral-h: 250; +} + +:root { + --color-accent: oklch(var(--color-accent-l) var(--color-accent-c) var(--color-accent-h)); + --color-accent-hover: oklch(58% var(--color-accent-c) var(--color-accent-h)); + --color-accent-subtle: oklch(var(--color-accent-l) var(--color-accent-c) var(--color-accent-h) / 0.12); + --color-accent-border: oklch(var(--color-accent-l) var(--color-accent-c) var(--color-accent-h) / 0.25); + --color-accent-transparent: oklch(var(--color-accent-l) var(--color-accent-c) var(--color-accent-h) / 0.6); + + --color-warning: oklch(var(--color-warning-l) var(--color-warning-c) var(--color-warning-h)); + --color-warning-bg: oklch(var(--color-warning-l) var(--color-warning-c) var(--color-warning-h) / 0.15); + --color-warning-border: oklch(var(--color-warning-l) var(--color-warning-c) var(--color-warning-h) / 0.3); + + --color-success: oklch(var(--color-success-l) var(--color-success-c) var(--color-success-h)); + --color-success-bg: oklch(var(--color-success-l) var(--color-success-c) var(--color-success-h) / 0.2); + --color-success-border: oklch(var(--color-success-l) var(--color-success-c) var(--color-success-h) / 0.3); + + --color-error: oklch(var(--color-error-l) var(--color-error-c) var(--color-error-h)); + --color-error-bg: color-mix(in oklch, var(--color-error) 20%, transparent); + --color-error-border: color-mix(in oklch, var(--color-error) 50%, transparent); + + --color-info: oklch(var(--color-info-l) var(--color-info-c) var(--color-info-h)); + --color-info-bg: oklch(72% 0.2 220); + --color-info-text: oklch(28% 0.03 220); + --color-info-glow: oklch(72% 0.2 220 / 0.28); + + --color-skip-refresh-bg: oklch(82% 0.12 45); + --color-skip-refresh-text: oklch(35% 0.02 45); + --color-skip-refresh-glow: oklch(82% 0.12 45 / 0.15); +} + +:root { + --bg-base: #ffffff; + --bg-elevated: oklch(97% 0 0 / 0.95); + --bg-overlay: oklch(0% 0 0 / 0.75); + --bg-hover: oklch(95% 0.02 256); + --bg-disabled: #f5f5f5; + + --text-primary: #333333; + --text-secondary: #6c757d; + --text-inverse: #ffffff; + --text-muted-on-dark: rgba(255, 255, 255, 0.8); + + --surface-base: #ffffff; + --surface-elevated: oklch(97% 0 0 / 0.95); + --surface-hover: oklch(95% 0.02 256); + --surface-subtle: oklch(0% 0 0 / 0.03); + + --border-base: #e0e0e0; + --border-subtle: oklch(72% 0.03 256 / 0.45); + --border-inverse: rgba(255, 255, 255, 0.25); + + --status-success-text: oklch(75% 0.12 230); + --status-success-bg: oklch(55% 0.15 240 / 0.25); + --status-success-border: oklch(60% 0.18 250 / 0.3); + --status-info-text: oklch(78% 0.10 185); + --status-info-bg: oklch(50% 0.10 190 / 0.25); + --status-info-border: oklch(55% 0.12 195 / 0.3); + + --favorite-color: #d4a017; + --favorite-glow: oklch(65% 0.15 85 / 0.5); +} + +[data-theme="dark"] { + --bg-base: #1a1a1a; + --bg-elevated: oklch(25% 0.02 256 / 0.98); + --bg-overlay: oklch(0% 0 0 / 0.75); + --bg-hover: oklch(35% 0.02 256); + --bg-disabled: #3a3a3a; + + --text-primary: #e0e0e0; + --text-secondary: #a0a0a0; + --text-inverse: #1a1a1a; + --text-muted-on-dark: rgba(255, 255, 255, 0.8); + + --surface-base: #2d2d2d; + --surface-elevated: oklch(25% 0.02 256 / 0.98); + --surface-hover: oklch(35% 0.02 256); + --surface-subtle: oklch(100% 0 0 / 0.03); + + --border-base: #404040; + --border-subtle: oklch(90% 0.02 256 / 0.15); + --border-inverse: rgba(255, 255, 255, 0.25); + + --status-success-text: oklch(75% 0.12 230); + --status-success-bg: oklch(55% 0.15 240 / 0.25); + --status-success-border: oklch(60% 0.18 250 / 0.3); + --status-info-text: oklch(78% 0.10 185); + --status-info-bg: oklch(50% 0.10 190 / 0.25); + --status-info-border: oklch(55% 0.12 195 / 0.3); + + --color-info-bg: oklch(62% 0.18 220); + --color-info-text: oklch(98% 0.02 240); + --color-info-glow: oklch(62% 0.18 220 / 0.4); + + --color-error-bg: color-mix(in oklch, var(--color-error) 15%, transparent); + --color-error-border: color-mix(in oklch, var(--color-error) 40%, transparent); + + --favorite-color: #ffc107; +} diff --git a/static/css/tokens/effects.css b/static/css/tokens/effects.css new file mode 100644 index 00000000..c68707b2 --- /dev/null +++ b/static/css/tokens/effects.css @@ -0,0 +1,25 @@ +:root { + --radius-none: 0px; + --radius-xs: 4px; + --radius-sm: 6px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-full: 9999px; + + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05); + --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 3px 5px rgba(0, 0, 0, 0.08); + --shadow-xl: 0 4px 16px rgba(0, 0, 0, 0.15); + --shadow-focus: 0 0 0 1px var(--color-accent); + --shadow-glow: 0 2px 6px var(--color-info-glow); + + --transition-fast: 150ms ease; + --transition-base: 200ms ease; + --transition-slow: 300ms ease; + --transition-bounce: 200ms cubic-bezier(0.34, 1.56, 0.64, 1); + + --border-width-thin: 1px; + --border-width-thick: 2px; +} diff --git a/static/css/tokens/index.css b/static/css/tokens/index.css new file mode 100644 index 00000000..edc3d4a7 --- /dev/null +++ b/static/css/tokens/index.css @@ -0,0 +1,6 @@ +@import 'colors.css'; +@import 'typography.css'; +@import 'spacing.css'; +@import 'effects.css'; +@import 'breakpoints.css'; +@import 'z-index.css'; diff --git a/static/css/tokens/spacing.css b/static/css/tokens/spacing.css new file mode 100644 index 00000000..c8265400 --- /dev/null +++ b/static/css/tokens/spacing.css @@ -0,0 +1,19 @@ +:root { + --space-0-5: 2px; + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + --space-10: 40px; + --space-12: 48px; + --space-16: 64px; + --space-20: 80px; + + --space-1-legacy: calc(8px * 1); + --space-2-legacy: calc(8px * 2); + --space-3-legacy: calc(8px * 3); + --space-4-legacy: calc(8px * 4); +} diff --git a/static/css/tokens/typography.css b/static/css/tokens/typography.css new file mode 100644 index 00000000..01f8900b --- /dev/null +++ b/static/css/tokens/typography.css @@ -0,0 +1,20 @@ +:root { + --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, Menlo, monospace; + + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-md: 0.95rem; + --text-lg: 1.1rem; + --text-xl: 1.25rem; + + --leading-tight: 1.2; + --leading-normal: 1.4; + --leading-relaxed: 1.5; + + --weight-normal: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; +} diff --git a/static/css/tokens/z-index.css b/static/css/tokens/z-index.css new file mode 100644 index 00000000..f3347da0 --- /dev/null +++ b/static/css/tokens/z-index.css @@ -0,0 +1,11 @@ +:root { + --z-base: 10; + --z-sticky: 50; + --z-header: 100; + --z-dropdown: 200; + --z-modal-backdrop: 500; + --z-modal: 1000; + --z-overlay: 2000; + --z-toast: 3000; + --z-tooltip: 4000; +}