From 7e7367e2588b9bfd09f6337a0f6c00f8a793b652 Mon Sep 17 00:00:00 2001 From: Cai Chen Date: Mon, 27 Apr 2026 18:02:24 -0500 Subject: [PATCH] feat: SMAPI web dark mode implementation --- src/SMAPI.Web/Views/Shared/_Layout.cshtml | 26 ++++ .../wwwroot/Content/css/file-upload.css | 7 +- src/SMAPI.Web/wwwroot/Content/css/index.css | 4 +- .../wwwroot/Content/css/json-validator.css | 18 +-- .../wwwroot/Content/css/log-parser.css | 89 +++++++------- src/SMAPI.Web/wwwroot/Content/css/main.css | 115 ++++++++++++++++-- src/SMAPI.Web/wwwroot/Content/css/mods.css | 32 ++--- 7 files changed, 208 insertions(+), 83 deletions(-) diff --git a/src/SMAPI.Web/Views/Shared/_Layout.cshtml b/src/SMAPI.Web/Views/Shared/_Layout.cshtml index d1930880d..b0390a4be 100644 --- a/src/SMAPI.Web/Views/Shared/_Layout.cshtml +++ b/src/SMAPI.Web/Views/Shared/_Layout.cshtml @@ -9,6 +9,23 @@ @ViewData["Title"] - SMAPI.io + @RenderSection("Head", required: false) @@ -28,6 +45,15 @@ @RenderSection("SidebarExtra", required: false) + +

Theme

+
diff --git a/src/SMAPI.Web/wwwroot/Content/css/file-upload.css b/src/SMAPI.Web/wwwroot/Content/css/file-upload.css index f29d46aa9..2d46aaff5 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/file-upload.css +++ b/src/SMAPI.Web/wwwroot/Content/css/file-upload.css @@ -9,7 +9,9 @@ margin: auto; box-sizing: border-box; border-radius: 5px; - border: 1px solid #000088; + border: 1px solid var(--surface-border); + background: var(--surface); + color: var(--fg); outline: none; box-shadow: inset 0 0 1px 1px rgba(0, 0, 192, .2); } @@ -21,5 +23,6 @@ box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, .2); cursor: pointer; border: 1px solid #008800; - background-color: #cfc; + background-color: var(--ok-bg); + color: var(--fg); } diff --git a/src/SMAPI.Web/wwwroot/Content/css/index.css b/src/SMAPI.Web/wwwroot/Content/css/index.css index cac83b0fb..af06a25ee 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/index.css +++ b/src/SMAPI.Web/wwwroot/Content/css/index.css @@ -4,7 +4,7 @@ h1 { text-align: center; font-size: 6em; - color: #000; + color: var(--fg); } #blurb { @@ -110,7 +110,7 @@ h1 { ** Subsections *********/ .area { - background: rgba(0, 170, 0, 0.2); + background: var(--area-bg); padding: 0 1em 1em 1em; margin-bottom: 1em; } diff --git a/src/SMAPI.Web/wwwroot/Content/css/json-validator.css b/src/SMAPI.Web/wwwroot/Content/css/json-validator.css index 04512cf3e..c9f15fe87 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/json-validator.css +++ b/src/SMAPI.Web/wwwroot/Content/css/json-validator.css @@ -61,12 +61,12 @@ .banner.success { border-color: green; - background: #CFC; + background: var(--ok-bg); } .banner.error { border-color: red; - background: #FCC; + background: var(--error-bg); } .save-metadata { @@ -79,7 +79,7 @@ ** Validation results *********/ .table { - border-bottom: 1px dashed #888888; + border-bottom: 1px dashed var(--border-strong); margin-bottom: 5px; } @@ -89,25 +89,25 @@ } .table { - border: 1px solid #000000; - background: #ffffff; + border: 1px solid var(--surface-border); + background: var(--surface); border-radius: 5px; border-spacing: 1px; overflow: hidden; cursor: default; - box-shadow: 1px 1px 1px 1px #dddddd; + box-shadow: 1px 1px 1px 1px var(--border); } .table tr { - background: #eee; + background: var(--surface-alt); } .table tr:nth-child(even) { - background: #fff; + background: var(--surface); } #output div.sunlight-line-highlight-active { - background-color: #eeeacc; + background-color: var(--warn-bg); } .footer-tip { diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css index 89efdc0b6..5b0af423a 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css +++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css @@ -39,12 +39,12 @@ table caption { .banner.success { border-color: green; - background: #CFC; + background: var(--ok-bg); } .banner.error { border-color: red; - background: #FCC; + background: var(--error-bg); } .save-metadata { @@ -63,8 +63,8 @@ table caption { #fix-list li { padding: 0.5em; - background: #FFC; - border: 1px solid #880; + background: var(--warn-bg); + border: 1px solid var(--warn-fg); border-radius: 5px; list-style-type: none; } @@ -74,13 +74,13 @@ table caption { } #fix-list li.notice { - background: #EEFFEE; - border-color: #080; + background: var(--notice-bg); + border-color: var(--ok-fg); } #fix-list li.important { - background: #FCC; - border-color: #800; + background: var(--error-bg); + border-color: var(--error-fg); } #fix-list li::before { @@ -92,7 +92,7 @@ table caption { ** Log metadata & filters *********/ .table, #filters { - border-bottom: 1px dashed #888888; + border-bottom: 1px dashed var(--border-strong); margin-bottom: 5px; } @@ -102,13 +102,13 @@ table caption { } .table { - border: 1px solid #000000; - background: #ffffff; + border: 1px solid var(--surface-border); + background: var(--surface); border-radius: 5px; border-spacing: 1px; overflow: hidden; cursor: default; - box-shadow: 1px 1px 1px 1px #dddddd; + box-shadow: 1px 1px 1px 1px var(--border); } .mod-entry { @@ -144,7 +144,7 @@ table caption { } .table tr { - background: #eee; + background: var(--surface-alt); } #mods span.notice { @@ -157,12 +157,12 @@ table caption { #mods span.notice.btn { cursor: pointer; - border: 1px solid #000; + border: 1px solid var(--surface-border); border-radius: 5px; position: relative; top: -1px; padding: 0 2px; - background: #eee; + background: var(--surface-alt); } #mods span.notice.txt { @@ -190,7 +190,7 @@ table caption { } .table tr:nth-child(even) { - background: #fff; + background: var(--surface); } #filters { @@ -219,7 +219,7 @@ table caption { } #filters .filter-error { - color: #880000; + color: var(--error-fg); } #filters .filter-error, @@ -232,7 +232,7 @@ table caption { position: fixed; top: 0; left: 0em; - background: #fff; + background: var(--bg); margin: 0; padding: 0.5em; width: calc(100% - 1em); @@ -257,14 +257,13 @@ table caption { #filters span { padding: 3px 1em; display: inline-block; - border: 1px solid #000000; + border: 1px solid var(--error-fg); border-radius: 3px; font-family: monospace; cursor: pointer; font-weight: bold; - color: #000; - border-color: #880000; - background-color: #fcc; + color: var(--fg); + background-color: var(--error-bg); user-select: none; } @@ -279,16 +278,16 @@ table caption { } #filters span:hover { - background-color: #fee; + background-color: var(--error-bg-hover); } #filters span.active { - background: #cfc; - border-color: #008800; + background: var(--ok-bg); + border-color: var(--ok-fg); } #filters span.active:hover { - background: #efe; + background: var(--ok-bg-hover); } #filters .pager { @@ -306,8 +305,8 @@ table caption { } #filters .pager span { - background-color: #eee; - border-color: #888; + background-color: var(--pager-bg); + border-color: var(--pager-border); } #filters .pager span.active { @@ -323,7 +322,7 @@ table caption { } #filters .pager span:not(.disabled):hover { - background-color: #fff; + background-color: var(--pager-hover); } @@ -336,15 +335,15 @@ table caption { } #log .trace { - color: #999; + color: var(--log-trace); } #log .debug { - color: #595959; + color: var(--log-debug); } #log .info { - color: #000; + color: var(--fg); } #log .alert { @@ -370,7 +369,7 @@ table caption { } #log .section-toggle-message { - color: blue; + color: var(--link); } #log .log-message-text { @@ -378,7 +377,8 @@ table caption { } #log .log-message-text strong { - background-color: yellow; + background-color: var(--highlight-bg); + color: var(--highlight-fg); font-weight: normal; } @@ -387,14 +387,14 @@ table caption { } #log tr { - background: #fff; + background: var(--bg); } #log td { padding: 0 1px; background: inherit; - border-bottom: 1px dotted #ccc; - border-top: 2px solid #fff; + border-bottom: 1px dotted var(--border); + border-top: 2px solid var(--bg); vertical-align: top; } @@ -415,15 +415,15 @@ table caption { display: block; position: absolute; border-radius: 4px; - box-shadow: 1px 1px 2px #ccc; + box-shadow: 1px 1px 2px var(--log-tooltip-shadow); background: inherit; - border: 1px solid #ccc; - background: #efefef; + border: 1px solid var(--log-tooltip-border); + background: var(--log-tooltip-bg); padding: 1px 1px 0 1px; font-size: 10pt; top: -2px; left: 2px; - color: #000; + color: var(--fg); } #log td:last-child { @@ -445,17 +445,24 @@ table caption { #os-instructions [role="tablist"] { border: 0; + border-bottom-color: var(--border); } #os-instructions [role="tab"] { display: block; border: 0; + border-top-color: var(--border); position: relative; } #os-instructions [role="tab"][aria-selected="true"] { font-weight: bold; border-radius: 0 10px 10px 0; + background-color: var(--surface-alt); +} + +#os-instructions [role="tab"]:hover:not([aria-selected="true"]) { + background-color: var(--surface); } [role="tab"][aria-selected="true"]::after { diff --git a/src/SMAPI.Web/wwwroot/Content/css/main.css b/src/SMAPI.Web/wwwroot/Content/css/main.css index 6818a1a78..63bd40aa7 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/main.css +++ b/src/SMAPI.Web/wwwroot/Content/css/main.css @@ -1,3 +1,82 @@ +/* theme */ +:root { + /* base */ + --bg: #FFF; + --fg: #000; + --fg-muted: #888; + --border: #CCC; + --border-strong: #AAA; + --link: #006; + --link-muted: #77B; + + /* surfaces */ + --surface: #f8f9fa; + --surface-alt: #eaecf0; + --surface-border: #a2a9b1; + --area-bg: rgba(0, 170, 0, 0.2); + + /* status (rows, banners, chips, fix-list) */ + --ok-bg: #BFB; + --ok-bg-hover: #efe; + --ok-fg: #080; + --warn-bg: #FFC; + --warn-fg: #880; + --error-bg: #FBB; + --error-bg-hover: #fee; + --error-fg: #880000; + --notice-bg: #EEFFEE; + --neutral-bg: #BBB; + + /* log parser */ + --log-trace: #999; + --log-debug: #595959; + --log-tooltip-bg: #efefef; + --log-tooltip-border: #ccc; + --log-tooltip-shadow: #ccc; + --highlight-bg: yellow; + --highlight-fg: inherit; + --pager-bg: #eee; + --pager-border: #888; + --pager-hover: #fff; +} + +[data-theme="dark"] { + --bg: #1D1D1D; + --fg: #fff; + --fg-muted: #999; + --border: #333; + --border-strong: #444; + --link: #8ab4f8; + --link-muted: #aab; + + --surface: #232323; + --surface-alt: #2c2c2c; + --surface-border: #444; + --area-bg: rgba(38, 44, 38, 0.9); + + --ok-bg: #1f3a1f; + --ok-bg-hover: #244a24; + --ok-fg: #4a8a4a; + --warn-bg: #3a3a1a; + --warn-fg: #aa9b00; + --error-bg: #3a1f1f; + --error-bg-hover: #4a2424; + --error-fg: #ff8a8a; + --notice-bg: #1f3a1f; + --neutral-bg: #2a2a2a; + + --log-trace: #777; + --log-debug: #aaa; + --log-tooltip-bg: #2c2c2c; + --log-tooltip-border: #555; + --log-tooltip-shadow: #000; + --highlight-bg: #806000; + --highlight-fg: #fff; + --pager-bg: #2c2c2c; + --pager-border: #666; + --pager-hover: #3a3a3a; +} + /* tags */ html { height: 100%; @@ -6,6 +85,8 @@ html { body { height: 100%; font-family: sans-serif; + background: var(--bg); + color: var(--fg); } h1, h2, h3 { @@ -16,24 +97,24 @@ h1, h2, h3 { h1 { font-size: 1.5em; - color: #888; + color: var(--fg-muted); margin-bottom: 0; } h2 { font-size: 1.5em; - border-bottom: 1px solid #AAA; + border-bottom: 1px solid var(--border-strong); } h3 { font-size: 1.2em; - border-bottom: 1px solid #AAA; + border-bottom: 1px solid var(--border-strong); width: 55em; max-width: 100%; } a { - color: #006; + color: var(--link); } /* content */ @@ -48,8 +129,8 @@ a { width: calc(100% - 2em); max-width: 60em; padding: 0 1em 1em 1em; - border-left: 1px solid #CCC; - background: #FFF; + border-left: 1px solid var(--border); + background: var(--bg); font-size: 0.9em; } @@ -58,7 +139,7 @@ a { } .section { - border: 1px solid #CCC; + border: 1px solid var(--border); padding: 0.5em; margin-bottom: 1em; } @@ -122,19 +203,19 @@ a { min-height: 75%; width: 12em; background: url("../images/sidebar-bg.svg") no-repeat top right; - color: #666; + color: var(--fg-muted); } #sidebar h4 { margin: 1.5em 0 0.2em 0; width: 10em; - border-bottom: 1px solid #CCC; + border-bottom: 1px solid var(--border); font-size: 0.8em; font-weight: normal; } #sidebar a { - color: #77B; + color: var(--link-muted); border: 0; } @@ -143,13 +224,21 @@ a { padding: 0; list-style: none none; font-size: 0.9em; - color: #888; + color: var(--fg-muted); } #sidebar li { margin-left: 1em; } +/* theme picker */ +html[data-theme-mode="system"] #theme-picker [data-theme-mode="system"], +html[data-theme-mode="light"] #theme-picker [data-theme-mode="light"], +html[data-theme-mode="dark"] #theme-picker [data-theme-mode="dark"] { + font-weight: bold; + text-decoration: none; +} + /* quick navigation */ #quickNav { @@ -170,11 +259,11 @@ a { margin: 1em; padding: 1em; font-size: 0.6em; - color: gray; + color: var(--fg-muted); } #footer a { - color: #669; + color: var(--link-muted); } /* mobile fixes */ diff --git a/src/SMAPI.Web/wwwroot/Content/css/mods.css b/src/SMAPI.Web/wwwroot/Content/css/mods.css index 62f8aa398..f494f135f 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/mods.css +++ b/src/SMAPI.Web/wwwroot/Content/css/mods.css @@ -28,14 +28,14 @@ margin: 2px; display: inline-block; border-radius: 3px; - color: #000; - border-color: #880000; - background-color: #fcc; + color: var(--fg); + border-color: var(--error-fg); + background-color: var(--error-bg); font-size: 0.9em; } #options #filters span.active { - background: #cfc; + background: var(--ok-bg); } #mod-count { @@ -45,7 +45,7 @@ div.error { padding: 2em 0; - color: red; + color: var(--error-fg); font-weight: bold; } @@ -53,23 +53,23 @@ div.error { ** Mod list *********/ table.wikitable { - background-color:#f8f9fa; - color:#222; - border:1px solid #a2a9b1; - border-collapse:collapse + background-color: var(--surface); + color: var(--fg); + border: 1px solid var(--surface-border); + border-collapse: collapse; } table.wikitable > tr > th, table.wikitable > tr > td, table.wikitable > * > tr > th, table.wikitable > * > tr > td { - border:1px solid #a2a9b1; - padding:0.2em 0.4em + border: 1px solid var(--surface-border); + padding: 0.2em 0.4em; } table.wikitable > tr > th, table.wikitable > * > tr > th { - background-color:#eaecf0; + background-color: var(--surface-alt); } table.wikitable > caption { @@ -118,21 +118,21 @@ table.wikitable > caption { #mod-list tr[data-status="ok"], #mod-list tr[data-status="optional"] { - background: #BFB; + background: var(--ok-bg); } #mod-list tr[data-status="workaround"], #mod-list tr[data-status="unofficial"] { - background: #FFFEC6; + background: var(--warn-bg); } #mod-list tr[data-status="broken"] { - background: #FBB; + background: var(--error-bg); } #mod-list tr[data-status="obsolete"], #mod-list tr[data-status="abandoned"] { - background: #BBB; + background: var(--neutral-bg); opacity: 0.7; }