diff --git a/locales/en.json b/locales/en.json index a765d2e..d2e1fdd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -20,6 +20,9 @@ "settings.backgroundColor": "Background color", "settings.backgroundColor.white": "White", "settings.backgroundColor.black": "Black", + "settings.colorMode": "Color mode", + "settings.colorMode.light": "Light", + "settings.colorMode.dark": "Dark", "settings.interval": "Auto play interval", "settings.interval.unit": "s", "settings.direction.rtl": "Right to left", @@ -39,4 +42,4 @@ "seek.previewAlt": "Preview of page {{page}}", "loading": "Loading", "error.pageRequestFailed": "Request failed for page {{page}}" -} +} \ No newline at end of file diff --git a/locales/id.json b/locales/id.json index 80ade16..9986101 100644 --- a/locales/id.json +++ b/locales/id.json @@ -20,6 +20,9 @@ "settings.backgroundColor": "Warna latar", "settings.backgroundColor.white": "Putih", "settings.backgroundColor.black": "Hitam", + "settings.colorMode": "Mode warna", + "settings.colorMode.light": "Terang", + "settings.colorMode.dark": "Gelap", "settings.interval": "Interval putar otomatis", "settings.interval.unit": "dtk", "settings.direction.rtl": "Kanan", @@ -39,4 +42,4 @@ "seek.previewAlt": "Pratinjau halaman {{page}}", "loading": "Memuat", "error.pageRequestFailed": "Permintaan halaman {{page}} gagal" -} +} \ No newline at end of file diff --git a/locales/ja.json b/locales/ja.json index 3112adb..c989da5 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -20,6 +20,9 @@ "settings.backgroundColor": "背景色", "settings.backgroundColor.white": "白", "settings.backgroundColor.black": "黒", + "settings.colorMode": "カラーモード", + "settings.colorMode.light": "ライト", + "settings.colorMode.dark": "ダーク", "settings.interval": "自動再生の秒数", "settings.interval.unit": "秒", "settings.direction.rtl": "右", @@ -39,4 +42,4 @@ "seek.previewAlt": "{{page}}ページのプレビュー", "loading": "読み込み中", "error.pageRequestFailed": "{{page}}ページのリクエストに失敗しました" -} +} \ No newline at end of file diff --git a/locales/ko.json b/locales/ko.json index 0f31352..d686d23 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -20,6 +20,9 @@ "settings.backgroundColor": "배경색", "settings.backgroundColor.white": "흰색", "settings.backgroundColor.black": "검정", + "settings.colorMode": "컬러 모드", + "settings.colorMode.light": "라이트", + "settings.colorMode.dark": "다크", "settings.interval": "자동 재생 간격", "settings.interval.unit": "초", "settings.direction.rtl": "오른쪽", @@ -39,4 +42,4 @@ "seek.previewAlt": "{{page}}페이지 미리보기", "loading": "로딩 중", "error.pageRequestFailed": "{{page}}페이지 요청 실패" -} +} \ No newline at end of file diff --git a/locales/th.json b/locales/th.json index 2233e72..7b826a6 100644 --- a/locales/th.json +++ b/locales/th.json @@ -20,6 +20,9 @@ "settings.backgroundColor": "สีพื้นหลัง", "settings.backgroundColor.white": "ขาว", "settings.backgroundColor.black": "ดำ", + "settings.colorMode": "โหมดสี", + "settings.colorMode.light": "สว่าง", + "settings.colorMode.dark": "มืด", "settings.interval": "ระยะเวลาเล่นอัตโนมัติ", "settings.interval.unit": "วินาที", "settings.direction.rtl": "ขวา", @@ -39,4 +42,4 @@ "seek.previewAlt": "ตัวอย่างหน้า {{page}}", "loading": "กำลังโหลด", "error.pageRequestFailed": "ขอหน้า {{page}} ไม่สำเร็จ" -} +} \ No newline at end of file diff --git a/locales/zh-CN.json b/locales/zh-CN.json index de076df..309971b 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -20,6 +20,9 @@ "settings.backgroundColor": "背景颜色", "settings.backgroundColor.white": "白色", "settings.backgroundColor.black": "黑色", + "settings.colorMode": "颜色模式", + "settings.colorMode.light": "浅色", + "settings.colorMode.dark": "深色", "settings.interval": "自动播放间隔", "settings.interval.unit": "秒", "settings.direction.rtl": "向右", @@ -39,4 +42,4 @@ "seek.previewAlt": "第 {{page}} 页预览", "loading": "加载中", "error.pageRequestFailed": "第 {{page}} 页请求失败" -} +} \ No newline at end of file diff --git a/src/components/arrow-buttons.styles.ts b/src/components/arrow-buttons.styles.ts index 4eb72bb..1d57d96 100644 --- a/src/components/arrow-buttons.styles.ts +++ b/src/components/arrow-buttons.styles.ts @@ -94,7 +94,7 @@ export const arrowButtonsStyles = ` left: 50%; width: 20px; height: 20px; - color: #666; + color: var(--comimi-muted); transform: translate(-50%, -50%); pointer-events: none; } diff --git a/src/components/center-message.styles.ts b/src/components/center-message.styles.ts index e858b89..db254b4 100644 --- a/src/components/center-message.styles.ts +++ b/src/components/center-message.styles.ts @@ -18,10 +18,10 @@ export const centerMessageStyles = ` box-sizing: border-box; padding: 6px 12px; border-radius: 8px; - background: rgba(255, 255, 255, 0.5); + background: var(--comimi-overlay); box-shadow: var(--comimi-shadow); backdrop-filter: blur(5px); - color: #666; + color: var(--comimi-muted); font-size: 12px; font-weight: 400; line-height: 1.45; diff --git a/src/components/controls-dock.styles.ts b/src/components/controls-dock.styles.ts index 3dfd677..59b3891 100644 --- a/src/components/controls-dock.styles.ts +++ b/src/components/controls-dock.styles.ts @@ -97,13 +97,13 @@ export const controlsDockStyles = ` } .comimi-seek-current { - color: #333; + color: var(--comimi-fg); font-size: 12px; font-weight: 700; } .comimi-seek-total { - color: #999; + color: var(--comimi-soft); font-size: 12px; font-weight: 400; margin-left: 0.4em; @@ -122,7 +122,7 @@ export const controlsDockStyles = ` right: 0; height: 8px; border-radius: 999px; - background: #e0e0e0; + background: var(--comimi-line); overflow: hidden; transform: translateY(-50%); transition: height 0.36s var(--comimi-spring); @@ -139,7 +139,7 @@ export const controlsDockStyles = ` top: 0; height: 100%; border-radius: 999px; - background: #666; + background: var(--comimi-accent); } .comimi-seek-bar[data-direction="rtl"] .comimi-seek-fill { @@ -183,7 +183,7 @@ export const controlsDockStyles = ` margin-top: -5px; border: 0; border-radius: 50%; - background: #666; + background: var(--comimi-accent); box-shadow: none; transition: transform 0.36s var(--comimi-spring), @@ -205,7 +205,7 @@ export const controlsDockStyles = ` height: 18px; border: 0; border-radius: 50%; - background: #666; + background: var(--comimi-accent); box-shadow: none; transition: transform 0.36s var(--comimi-spring), @@ -264,7 +264,7 @@ export const controlsDockStyles = ` position: relative; width: 80px; aspect-ratio: 100 / 141; - background: #fff; + background: var(--comimi-page-surface); border-radius: 6px; overflow: hidden; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18); @@ -285,7 +285,7 @@ export const controlsDockStyles = ` justify-content: center; box-sizing: border-box; padding: 8px; - color: #aaa; + color: var(--comimi-icon-subtle); font-size: 11px; font-weight: 700; line-height: 1.4; @@ -293,8 +293,8 @@ export const controlsDockStyles = ` } .comimi-seek-preview-label { - color: #333; - background: rgba(255, 255, 255, 0.5); + color: var(--comimi-fg); + background: var(--comimi-overlay); backdrop-filter: blur(5px); box-shadow: var(--comimi-shadow); border-radius: 8px; @@ -327,7 +327,7 @@ export const controlsDockStyles = ` padding: 0; border: 0; background: transparent; - color: #666; + color: var(--comimi-muted); cursor: pointer; } @@ -375,7 +375,7 @@ export const controlsDockStyles = ` width: 140px; height: 6px; border-radius: 999px; - background: #e0e0e0; + background: var(--comimi-line); overflow: hidden; } @@ -384,7 +384,7 @@ export const controlsDockStyles = ` position: absolute; inset: 0; border-radius: 999px; - background: #666; + background: var(--comimi-accent); animation: comimi-autoplay-progress 3s linear 0s infinite; } @@ -404,7 +404,7 @@ export const controlsDockStyles = ` height: 30px; display: grid; grid-template-columns: 1fr 1fr; - background: #eeeeee; + background: var(--comimi-surface-2); border-radius: 8px; transform: translate(-50%, -50%); transition: width 0.36s var(--comimi-spring); @@ -425,7 +425,7 @@ export const controlsDockStyles = ` padding: 0; border: 0; background: transparent; - color: #666; + color: var(--comimi-muted); cursor: pointer; } @@ -466,7 +466,7 @@ export const controlsDockStyles = ` display: block; width: 100%; height: 100%; - color: #666; + color: var(--comimi-muted); transition: transform 0.36s var(--comimi-spring); } @@ -506,7 +506,7 @@ export const controlsDockStyles = ` z-index: 50; pointer-events: none; overflow: hidden; - background: rgba(0, 0, 0, 0.06); + background: var(--comimi-dim); opacity: 0; transition: opacity 0.16s linear; } @@ -519,7 +519,7 @@ export const controlsDockStyles = ` display: block; position: absolute; inset: 0; - background: #666; + background: var(--comimi-accent); animation: comimi-autoplay-progress 3s linear 0s infinite; } diff --git a/src/components/icons.styles.ts b/src/components/icons.styles.ts index 06cbfda..83eb768 100644 --- a/src/components/icons.styles.ts +++ b/src/components/icons.styles.ts @@ -32,7 +32,7 @@ export const iconStyles = ` width: 17px; height: 3px; border-radius: 999px; - background: #666; + background: var(--comimi-muted); } .comimi-menu-svg span:nth-child(1) { diff --git a/src/components/inputs.styles.ts b/src/components/inputs.styles.ts index 7e9fae3..bd94e19 100644 --- a/src/components/inputs.styles.ts +++ b/src/components/inputs.styles.ts @@ -9,7 +9,7 @@ export const inputsStyles = ` position: absolute; inset: 0; border-radius: 8px; - background: #eeeeee; + background: var(--comimi-surface-2); transition: inset 0.36s var(--comimi-spring); } @@ -43,8 +43,8 @@ export const inputsStyles = ` } .comimi-selectbox-select option { - color: #333; - background-color: #fff; + color: var(--comimi-fg); + background-color: var(--comimi-page-surface); } .comimi-selectbox-label { @@ -53,7 +53,7 @@ export const inputsStyles = ` box-sizing: border-box; width: 100%; padding: 8px 32px 8px 12px; - color: #333; + color: var(--comimi-fg); font-size: 12px; font-weight: 400; line-height: 1.45; @@ -68,7 +68,7 @@ export const inputsStyles = ` z-index: 1; width: 16px; height: 16px; - color: #aaa; + color: var(--comimi-icon-subtle); transform: translateY(-50%) rotate(90deg); pointer-events: none; } @@ -109,12 +109,12 @@ export const inputsStyles = ` width: 32px; height: 18px; border-radius: 999px; - background: #eeeeee; + background: var(--comimi-surface-2); transition: background-color 0.2s linear; } .comimi-toggle-switch[data-checked="true"] .comimi-toggle-track { - background: #666; + background: var(--comimi-accent); } .comimi-toggle-knob { @@ -124,14 +124,14 @@ export const inputsStyles = ` width: 14px; height: 14px; border-radius: 50%; - background: #666; + background: var(--comimi-accent); transition: transform 0.36s var(--comimi-spring), background-color 0.2s linear; } .comimi-toggle-switch[data-checked="true"] .comimi-toggle-knob { - background: #fff; + background: var(--comimi-contrast); transform: translateX(100%); } @@ -164,11 +164,11 @@ export const inputsStyles = ` } .comimi-toggle-label-on { - color: #333; + color: var(--comimi-fg); } .comimi-toggle-label-off { - color: #aaa; + color: var(--comimi-icon-subtle); transform: translateX(100%); } @@ -199,7 +199,7 @@ export const inputsStyles = ` right: 0; height: 6px; border-radius: 999px; - background: #eeeeee; + background: var(--comimi-surface-2); overflow: hidden; transform: translateY(-50%); transition: height 0.36s var(--comimi-spring); @@ -214,7 +214,7 @@ export const inputsStyles = ` .comimi-range-slider-fill { display: block; height: 100%; - background: #666; + background: var(--comimi-accent); border-radius: inherit; } @@ -246,7 +246,7 @@ export const inputsStyles = ` margin-top: -6px; border: 0; border-radius: 50%; - background: #666; + background: var(--comimi-accent); box-shadow: none; transition: transform 0.36s var(--comimi-spring), @@ -268,7 +268,7 @@ export const inputsStyles = ` height: 18px; border: 0; border-radius: 50%; - background: #666; + background: var(--comimi-accent); box-shadow: none; transition: transform 0.36s var(--comimi-spring), @@ -293,7 +293,7 @@ export const inputsStyles = ` .comimi-range-slider-value { width: 36px; - color: #666; + color: var(--comimi-muted); font-size: 11px; font-weight: 400; line-height: 1; diff --git a/src/components/loading-icon.styles.ts b/src/components/loading-icon.styles.ts index 0f4ea04..26760e7 100644 --- a/src/components/loading-icon.styles.ts +++ b/src/components/loading-icon.styles.ts @@ -22,12 +22,16 @@ export const loadingIconStyles = ` .comimi-loading-stroke { fill: none; - stroke: #ccc; + stroke: var(--comimi-loading-stroke); stroke-linecap: round; stroke-linejoin: round; stroke-width: 10px; } +.comimi-loading-body { + fill: var(--comimi-loading-body); +} + .comimi-loading-mimi { transform-origin: 56px 43px; } @@ -52,7 +56,7 @@ export const loadingIconStyles = ` } .comimi-loading-eye { - fill: #ccc; + fill: var(--comimi-loading-eye); transform-origin: center; transform-box: fill-box; animation: @@ -61,7 +65,7 @@ export const loadingIconStyles = ` } .comimi-loading-icon-text { - color: #aaa; + color: var(--comimi-loading-text); font-size: 12px; font-weight: 700; line-height: 1.45; diff --git a/src/components/loading-icon.ts b/src/components/loading-icon.ts index d40efcc..01c3444 100644 --- a/src/components/loading-icon.ts +++ b/src/components/loading-icon.ts @@ -9,7 +9,7 @@ const LOADING_SVG = ` - + diff --git a/src/components/menu-panel.styles.ts b/src/components/menu-panel.styles.ts index 0c99d53..806e286 100644 --- a/src/components/menu-panel.styles.ts +++ b/src/components/menu-panel.styles.ts @@ -87,7 +87,7 @@ export const menuPanelStyles = ` .comimi-menu-title { display: block; width: 100%; - color: #333; + color: var(--comimi-fg); font-size: 15px; font-weight: 700; overflow: hidden; @@ -98,7 +98,7 @@ export const menuPanelStyles = ` .comimi-menu-author { display: block; width: 100%; - color: #666; + color: var(--comimi-muted); font-size: 12px; font-weight: 400; overflow: hidden; @@ -116,7 +116,7 @@ export const menuPanelStyles = ` .comimi-menu-border { width: 100%; height: 1px; - background: #e0e0e0; + background: var(--comimi-line); } .comimi-menu-view { @@ -161,17 +161,17 @@ export const menuPanelStyles = ` border: 0; border-radius: 10px; background: transparent; - color: #666; + color: var(--comimi-muted); cursor: pointer; transition: background-color 0.24s linear; } .comimi-menu-link:hover { - background: #f1f1f1; + background: var(--comimi-hover); } .comimi-menu-link-text { - color: #666; + color: var(--comimi-muted); font-size: 14px; font-weight: 700; line-height: 1.45; @@ -182,7 +182,7 @@ export const menuPanelStyles = ` display: block; width: 20px; height: 20px; - color: #aaa; + color: var(--comimi-icon-subtle); } .comimi-menu-link-arrow > svg { @@ -211,7 +211,7 @@ export const menuPanelStyles = ` padding: 0; border: 0; background: transparent; - color: #666; + color: var(--comimi-muted); cursor: pointer; text-align: center; } @@ -221,9 +221,9 @@ export const menuPanelStyles = ` display: block; width: 100%; aspect-ratio: 100 / 141; - background: #fff; + background: var(--comimi-page-surface); border-radius: 6px; - outline: 3px solid #e0e0e0; + outline: 3px solid var(--comimi-line); } .comimi-page-list-thumb-html { @@ -235,7 +235,7 @@ export const menuPanelStyles = ` box-sizing: border-box; padding: 8px; text-align: center; - color: #aaa; + color: var(--comimi-icon-subtle); font-size: 11px; font-weight: 700; line-height: 1.4; @@ -252,7 +252,7 @@ export const menuPanelStyles = ` } .comimi-page-list-text { - color: #666; + color: var(--comimi-muted); font-size: 14px; font-weight: 700; } @@ -276,7 +276,7 @@ export const menuPanelStyles = ` } .comimi-shortcut-heading { - color: #666; + color: var(--comimi-muted); font-size: 12px; font-weight: 700; line-height: 1.45; @@ -310,11 +310,11 @@ export const menuPanelStyles = ` align-items: center; padding: 4px 8px; border-radius: 7px; - background: #eeeeee; + background: var(--comimi-surface-2); } .comimi-shortcut-key { - color: #666; + color: var(--comimi-muted); font-size: 13px; font-weight: 700; line-height: 1.45; @@ -323,14 +323,14 @@ export const menuPanelStyles = ` .comimi-shortcut-or::before { content: "or"; display: block; - color: #666; + color: var(--comimi-muted); font-size: 11px; font-weight: 400; line-height: 1.45; } .comimi-shortcut-label { - color: #666; + color: var(--comimi-muted); font-size: 13px; font-weight: 400; line-height: 1.45; @@ -355,7 +355,7 @@ export const menuPanelStyles = ` position: absolute; inset: 0; border-radius: 8px; - background: #eeeeee; + background: var(--comimi-surface-2); transition: inset 0.36s var(--comimi-spring), border-radius 0.36s var(--comimi-spring); } @@ -369,7 +369,7 @@ export const menuPanelStyles = ` top: 50%; left: 50%; transform: translate(-50%, -50%); - color: #666; + color: var(--comimi-muted); font-size: 14px; font-weight: 700; line-height: 1.45; @@ -382,7 +382,7 @@ export const menuPanelStyles = ` left: 8px; width: 22px; height: 22px; - color: #aaa; + color: var(--comimi-icon-subtle); transform: translateY(-50%) scaleX(-1); pointer-events: none; } diff --git a/src/components/move-direction-guide.styles.ts b/src/components/move-direction-guide.styles.ts index aa3d304..4287031 100644 --- a/src/components/move-direction-guide.styles.ts +++ b/src/components/move-direction-guide.styles.ts @@ -8,8 +8,8 @@ export const moveDirectionGuideStyles = ` height: var(--comimi-move-guide-size); transform: translate(-50%, -50%); border-radius: 16px; - background: rgba(255, 255, 255, 0.7); - box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1); + background: var(--comimi-overlay); + box-shadow: var(--comimi-shadow); backdrop-filter: blur(5px); pointer-events: none; z-index: 1; @@ -52,7 +52,7 @@ export const moveDirectionGuideStyles = ` width: 30px; height: 6px; border-radius: 999px; - background-color: #666; + background-color: var(--comimi-accent); animation: comimi-move-guide-arrow 0.6s ease-in-out 0.8s both; } @@ -66,7 +66,7 @@ export const moveDirectionGuideStyles = ` width: 16px; height: 6px; border-radius: 999px; - background-color: #666; + background-color: var(--comimi-accent); transform-origin: 3px center; } @@ -87,7 +87,7 @@ export const moveDirectionGuideStyles = ` font-weight: 400; text-align: center; line-height: 1.45; - color: #666; + color: var(--comimi-muted); } @keyframes comimi-move-guide-arrow { diff --git a/src/components/notifications.styles.ts b/src/components/notifications.styles.ts index 64dc33c..8e72def 100644 --- a/src/components/notifications.styles.ts +++ b/src/components/notifications.styles.ts @@ -20,7 +20,7 @@ export const notificationsStyles = ` width: fit-content; padding: 6px 13px 6px 8px; border-radius: 999px; - background-color: #666; + background-color: var(--comimi-accent); animation: comimi-toast-popup 0.37s ease-in-out 0s both; } @@ -29,11 +29,11 @@ export const notificationsStyles = ` width: 10px; height: 10px; border-radius: 50%; - background: #888; + background: var(--comimi-soft); } .comimi-toast-text { - color: #fff; + color: var(--comimi-contrast); font-size: 12px; font-weight: 700; line-height: 1.45; diff --git a/src/components/page-stage.styles.ts b/src/components/page-stage.styles.ts index 954b8d3..fa34f50 100644 --- a/src/components/page-stage.styles.ts +++ b/src/components/page-stage.styles.ts @@ -93,8 +93,8 @@ export const pageStageStyles = ` position: relative; width: min(100%, 960px); height: min(100%, 680px); - background: #fff; - color: #111; + background: var(--comimi-page-surface); + color: var(--comimi-page-fg); overflow: auto; } diff --git a/src/components/scroll-fade.styles.ts b/src/components/scroll-fade.styles.ts index 3d63a0a..890e864 100644 --- a/src/components/scroll-fade.styles.ts +++ b/src/components/scroll-fade.styles.ts @@ -20,12 +20,12 @@ export const scrollFadeStyles = ` .comimi-scrollfade::before { top: 0; - background: linear-gradient(to bottom, #fff, transparent); + background: linear-gradient(to bottom, var(--comimi-scrollfade), transparent); } .comimi-scrollfade::after { bottom: 0; - background: linear-gradient(to top, #fff, transparent); + background: linear-gradient(to top, var(--comimi-scrollfade), transparent); } .comimi-scrollfade[data-scroll-up="true"]::before { diff --git a/src/components/settings-panel.styles.ts b/src/components/settings-panel.styles.ts index 968d536..942a0bd 100644 --- a/src/components/settings-panel.styles.ts +++ b/src/components/settings-panel.styles.ts @@ -38,7 +38,7 @@ export const settingsPanelStyles = ` } .comimi-settings-panel-title { - color: #333; + color: var(--comimi-fg); font-size: 14px; font-weight: 700; text-align: center; @@ -51,13 +51,13 @@ export const settingsPanelStyles = ` } .comimi-settings-label { - color: #333; + color: var(--comimi-fg); font-size: 12px; font-weight: 400; } .comimi-settings-static-value { - color: #999; + color: var(--comimi-soft); font-size: 13px; font-weight: 500; } diff --git a/src/components/settings-panel.ts b/src/components/settings-panel.ts index 58a486d..d471aed 100644 --- a/src/components/settings-panel.ts +++ b/src/components/settings-panel.ts @@ -1,6 +1,7 @@ import { I18n } from "../i18n/i18n"; import type { BackgroundColor, + ColorMode, HideableControl, ReadingDirection, ViewerState @@ -19,12 +20,14 @@ export class SettingsPanel { private coverLabel: HTMLDivElement; private directionLabel: HTMLDivElement; private backgroundColorLabel: HTMLDivElement; + private colorModeLabel: HTMLDivElement; private intervalLabel: HTMLDivElement; private localeSelect: Selectbox; private coverToggle: ToggleSwitch; private directionSelect: Selectbox; private backgroundColorSelect: Selectbox; + private colorModeSelect: Selectbox; private intervalSlider: RangeSlider; private staticValues: Partial> = {}; @@ -65,6 +68,11 @@ export class SettingsPanel { backgroundColor: backgroundColor as BackgroundColor }) ); + this.colorModeSelect = new Selectbox((colorMode) => + this.callbacks.updateSettings({ + colorMode: colorMode as ColorMode + }) + ); this.intervalSlider = new RangeSlider((seconds) => this.callbacks.updateSettings({ autoPageTurnIntervalMs: Math.max(1, seconds) * 1000 @@ -77,6 +85,7 @@ export class SettingsPanel { this.coverLabel = this.createLabel(); this.directionLabel = this.createLabel(); this.backgroundColorLabel = this.createLabel(); + this.colorModeLabel = this.createLabel(); this.intervalLabel = this.createLabel(); this.inner.append( @@ -101,6 +110,11 @@ export class SettingsPanel { this.intervalLabel, this.intervalSlider.getElement() ), + this.buildSection( + "colorMode", + this.colorModeLabel, + this.colorModeSelect.getElement() + ), this.buildSection( "backgroundColor", this.backgroundColorLabel, @@ -122,6 +136,7 @@ export class SettingsPanel { this.backgroundColorLabel.textContent = this.i18n.t( "settings.backgroundColor" ); + this.colorModeLabel.textContent = this.i18n.t("settings.colorMode"); this.intervalLabel.textContent = this.i18n.t("settings.interval"); const localeOptions = [ @@ -146,6 +161,16 @@ export class SettingsPanel { value: "black" } ]; + const colorModeOptions = [ + { + label: this.i18n.t("settings.colorMode.light"), + value: "light" + }, + { + label: this.i18n.t("settings.colorMode.dark"), + value: "dark" + } + ]; const intervalUnit = this.i18n.t("settings.interval.unit"); const intervalSeconds = Math.round( state.settings.autoPageTurnIntervalMs / 1000 @@ -154,12 +179,14 @@ export class SettingsPanel { this.localeSelect.setOptions(localeOptions); this.directionSelect.setOptions(directionOptions); this.backgroundColorSelect.setOptions(backgroundColorOptions); + this.colorModeSelect.setOptions(colorModeOptions); this.intervalSlider.setUnit(intervalUnit); this.localeSelect.setValue(state.settings.locale); this.coverToggle.setChecked(state.settings.hasCover); this.directionSelect.setValue(state.settings.readingDirection); this.backgroundColorSelect.setValue(state.settings.backgroundColor); + this.colorModeSelect.setValue(state.settings.colorMode); this.intervalSlider.setValue(intervalSeconds); this.setStaticValue( @@ -176,6 +203,10 @@ export class SettingsPanel { "backgroundColor", this.labelFor(backgroundColorOptions, state.settings.backgroundColor) ); + this.setStaticValue( + "colorMode", + this.labelFor(colorModeOptions, state.settings.colorMode) + ); this.root.dataset.open = String(state.panel === "settings"); diff --git a/src/components/splash-screen.styles.ts b/src/components/splash-screen.styles.ts index 2f3f21a..2b6d71e 100644 --- a/src/components/splash-screen.styles.ts +++ b/src/components/splash-screen.styles.ts @@ -3,7 +3,7 @@ export const splashScreenStyles = ` position: absolute; inset: 0; z-index: 10; - background-color: #e0e0e0; + background-color: var(--comimi-splash-bg); overflow: hidden; animation: comimi-splash-clip 1s cubic-bezier(0.82, 0.01, 0.48, 1.02) 1s both; pointer-events: none; @@ -74,9 +74,17 @@ export const splashScreenStyles = ` overflow: visible; } +.comimi-splash-body { + fill: var(--comimi-splash-symbol-fill); +} + +.comimi-splash-typo-letter { + fill: var(--comimi-splash-typo-fill); +} + .comimi-splash-stroke { fill: none; - stroke: #fff; + stroke: var(--comimi-splash-stroke); stroke-linecap: round; stroke-linejoin: round; stroke-width: 10px; @@ -99,6 +107,7 @@ export const splashScreenStyles = ` } .comimi-splash-eye { + fill: var(--comimi-splash-eye-fill); transform-origin: center; transform-box: fill-box; animation: comimi-splash-eye 0.4s ease-in-out 0.2s forwards; @@ -127,12 +136,12 @@ export const splashScreenStyles = ` .comimi-splash-heart::before { transform: translateX(-32%) rotate(45deg); - background-color: #eee; + background-color: var(--comimi-splash-heart-left); } .comimi-splash-heart::after { transform: translateX(32%) rotate(-45deg); - background-color: #fff; + background-color: var(--comimi-splash-heart-right); } .comimi-splash-heart-1 { @@ -148,7 +157,7 @@ export const splashScreenStyles = ` bottom: 0; left: 50%; transform: translate(-50%, 120%); - color: #fff; + color: var(--comimi-splash-text); font-size: 16px; font-weight: 700; line-height: 1.45; diff --git a/src/components/splash-screen.ts b/src/components/splash-screen.ts index 0c9c45d..3fcdbc4 100644 --- a/src/components/splash-screen.ts +++ b/src/components/splash-screen.ts @@ -82,7 +82,7 @@ function buildDefaultSplashSymbol(): SVGSVGElement { "d", "M80.44,49.41H31.46c-12.97,6.71-21.6,18.57-21.6,32.1,0,5.8,1.59,11.3,4.43,16.21h83.34c2.83-4.92,4.43-10.41,4.43-16.21,0-13.53-8.63-25.4-21.6-32.1Z" ); - body.setAttribute("fill", "#e0e0e0"); + body.setAttribute("class", "comimi-splash-body"); svg.append(body); const eyes = document.createElementNS(SVG_NS, "g"); @@ -93,7 +93,6 @@ function buildDefaultSplashSymbol(): SVGSVGElement { eye.setAttribute("cx", cx); eye.setAttribute("cy", "78.21"); eye.setAttribute("r", "6"); - eye.setAttribute("fill", "#fff"); eyes.append(eye); } svg.append(eyes); @@ -121,7 +120,7 @@ function renderSplashTypo(): HTMLDivElement { for (const d of letters) { const path = document.createElementNS(SVG_NS, "path"); path.setAttribute("d", d); - path.setAttribute("fill", "#fff"); + path.setAttribute("class", "comimi-splash-typo-letter"); svg.append(path); } diff --git a/src/components/tooltip.styles.ts b/src/components/tooltip.styles.ts index 457518b..c1b756d 100644 --- a/src/components/tooltip.styles.ts +++ b/src/components/tooltip.styles.ts @@ -5,10 +5,10 @@ export const tooltipStyles = ` left: 50%; padding: 6px 10px; border-radius: 8px; - background: rgba(255, 255, 255, 0.5); + background: var(--comimi-overlay); box-shadow: var(--comimi-shadow); backdrop-filter: blur(5px); - color: #333; + color: var(--comimi-fg); font-size: 12px; font-weight: 600; line-height: 1.45; diff --git a/src/components/view-mode-switcher.styles.ts b/src/components/view-mode-switcher.styles.ts index 461a287..7a8d0a8 100644 --- a/src/components/view-mode-switcher.styles.ts +++ b/src/components/view-mode-switcher.styles.ts @@ -38,7 +38,7 @@ export const viewModeSwitcherStyles = ` width: 60px; height: 100%; border-radius: 16px; - background: #666; + background: var(--comimi-accent); transition: transform 0.36s var(--comimi-spring); } @@ -52,13 +52,13 @@ export const viewModeSwitcherStyles = ` padding: 0; border: 0; background: transparent; - color: #666; + color: var(--comimi-muted); cursor: pointer; transition: color 0.2s linear; } .comimi-view-switcher-button[data-selected="true"] { - color: #fff; + color: var(--comimi-contrast); } .comimi-view-switcher-icon-wrap { diff --git a/src/components/viewer-root.styles.ts b/src/components/viewer-root.styles.ts index 73ceb0f..0f2cfb1 100644 --- a/src/components/viewer-root.styles.ts +++ b/src/components/viewer-root.styles.ts @@ -6,10 +6,34 @@ export const viewerRootStyles = ` --comimi-fg: #333; --comimi-muted: #666; --comimi-soft: #999; + --comimi-accent: #666; + --comimi-contrast: #fff; + --comimi-icon-subtle: #aaa; --comimi-line: #e0e0e0; --comimi-shadow: 0 0 8px rgba(0, 0, 0, 0.1); --comimi-glass: rgba(255, 255, 255, 0.8); --comimi-glass-strong: rgba(255, 255, 255, 0.8); + --comimi-hover: #f1f1f1; + --comimi-overlay: rgba(255, 255, 255, 0.5); + --comimi-page-surface: #fff; + --comimi-page-fg: #111; + --comimi-loading-stroke: #ccc; + --comimi-loading-body: #fff; + --comimi-loading-eye: #ccc; + --comimi-loading-text: #aaa; + --comimi-splash-bg: #e0e0e0; + --comimi-splash-stroke: #fff; + --comimi-splash-heart-left: #eee; + --comimi-splash-heart-right: #fff; + --comimi-splash-text: #fff; + --comimi-splash-symbol-fill: #e0e0e0; + --comimi-splash-eye-fill: #fff; + --comimi-splash-typo-fill: #fff; + --comimi-scrollfade: #fff; + --comimi-dim: rgba(0, 0, 0, 0.06); + --comimi-handle-bg: #fff; + --comimi-handle-dot: #bbb; + --comimi-handle-dot-hover: #888; --comimi-spring: cubic-bezier(0.34, 1.56, 0.64, 1); background: var(--comimi-bg); color: var(--comimi-fg); @@ -42,8 +66,55 @@ export const viewerRootStyles = ` } } -.comimi-root[data-bg="black"] { +.comimi-root[data-color-mode="dark"] { --comimi-bg: #000; + --comimi-surface: #191919; + --comimi-surface-2: #272727; + --comimi-fg: #f2f2f2; + --comimi-muted: #cdcdcd; + --comimi-soft: #969696; + --comimi-accent: #d2d2d2; + --comimi-contrast: #121212; + --comimi-icon-subtle: #a8a8a8; + --comimi-line: #3a3a3a; + --comimi-shadow: 0 0 12px rgba(0, 0, 0, 0.45); + --comimi-glass: rgba(22, 22, 22, 0.82); + --comimi-glass-strong: rgba(16, 16, 16, 0.88); + --comimi-hover: rgba(255, 255, 255, 0.08); + --comimi-overlay: rgba(16, 16, 16, 0.62); + --comimi-page-surface: #111; + --comimi-page-fg: #f2f2f2; + --comimi-loading-stroke: #7b7b7b; + --comimi-loading-body: #000; + --comimi-loading-eye: #b5b5b5; + --comimi-loading-text: #b0b0b0; + --comimi-splash-bg: #1b1b1b; + --comimi-splash-stroke: #c8c8c8; + --comimi-splash-heart-left: #5a5a5a; + --comimi-splash-heart-right: #c8c8c8; + --comimi-splash-text: #d5d5d5; + --comimi-splash-symbol-fill: #1b1b1b; + --comimi-splash-eye-fill: #d8d8d8; + --comimi-splash-typo-fill: #d8d8d8; + --comimi-scrollfade: #111; + --comimi-dim: rgba(255, 255, 255, 0.08); + --comimi-handle-bg: #111; + --comimi-handle-dot: #666; + --comimi-handle-dot-hover: #888; +} + +.comimi-root[data-page-bg="white"] { + --comimi-bg: #fff; + --comimi-scrollfade: #fff; + --comimi-page-surface: #fff; + --comimi-page-fg: #111; +} + +.comimi-root[data-page-bg="black"] { + --comimi-bg: #111; + --comimi-scrollfade: #111; + --comimi-page-surface: #111; + --comimi-page-fg: #f2f2f2; } .comimi-root[data-layout="wide"] { @@ -75,7 +146,7 @@ export const viewerRootStyles = ` position: relative; width: 100%; height: 20px; - background: #fff; + background: var(--comimi-handle-bg); cursor: ns-resize; touch-action: none; } @@ -89,7 +160,7 @@ export const viewerRootStyles = ` width: 40px; height: 3px; border-radius: 999px; - background: #bbb; + background: var(--comimi-handle-dot); transition: width 0.36s cubic-bezier(0.34, 1.56, 0.64, 1), height 0.36s cubic-bezier(0.34, 1.56, 0.64, 1), @@ -100,7 +171,7 @@ export const viewerRootStyles = ` .comimi-resize-handle:hover::after { width: 52px; height: 5px; - background: #888; + background: var(--comimi-handle-dot-hover); } } diff --git a/src/defaults.ts b/src/defaults.ts index 82c361d..3618667 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -10,6 +10,7 @@ export const defaultSettings: ViewerSettings = { layoutMode: "inline", autoPageTurnIntervalMs: 5000, backgroundColor: "white", + colorMode: "light", zoom: { min: 1, max: 4, diff --git a/src/renderer/viewer-renderer.ts b/src/renderer/viewer-renderer.ts index 57db97c..e3ce23e 100644 --- a/src/renderer/viewer-renderer.ts +++ b/src/renderer/viewer-renderer.ts @@ -127,6 +127,7 @@ export class ViewerRenderer { this.cleanup = []; this.i18n.setLocale(state.settings.locale); this.root.dataset.layout = state.layout.mode; + this.root.dataset.colorMode = state.settings.colorMode; this.root.dataset.bg = state.settings.backgroundColor; if (state.layout.mode === "wide" && state.layout.wideHeightPx) { diff --git a/src/types.ts b/src/types.ts index 9d2dc3b..3b924d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ export type ReadingDirection = "rtl" | "ltr"; export type PageTurnMode = "single" | "spread"; export type BackgroundColor = "white" | "black"; +export type ColorMode = "light" | "dark"; export type LayoutMode = | "inline" | "wide" @@ -18,6 +19,7 @@ export type HideableControl = | "direction" | "interval" | "backgroundColor" + | "colorMode" // ツールバーの操作 | "pageMode" | "autoplay" @@ -69,6 +71,7 @@ export interface ViewerSettings { layoutMode: LayoutMode; autoPageTurnIntervalMs: number; backgroundColor: BackgroundColor; + colorMode: ColorMode; zoom: { min: number; max: number;