From 6326895fc832026ed53329241befaeb51c9770f7 Mon Sep 17 00:00:00 2001 From: soundgoof Date: Sat, 12 Sep 2026 11:43:09 +0200 Subject: [PATCH 1/7] www: add persistent light and dark theme switch --- nipap-www/nipapwww/static/css/nipap.css | 4 + nipap-www/nipapwww/static/css/theme.css | 131 ++++++++++++++++++ .../static/templates/prefix_edit.html | 2 +- nipap-www/nipapwww/static/theme.js | 69 +++++++++ nipap-www/nipapwww/templates/base.html | 7 + nipap-www/nipapwww/templates/macros.html | 2 +- 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 nipap-www/nipapwww/static/css/theme.css create mode 100644 nipap-www/nipapwww/static/theme.js diff --git a/nipap-www/nipapwww/static/css/nipap.css b/nipap-www/nipapwww/static/css/nipap.css index ce3a943a8..a9f312b04 100644 --- a/nipap-www/nipapwww/static/css/nipap.css +++ b/nipap-www/nipapwww/static/css/nipap.css @@ -1009,3 +1009,7 @@ tbody.listing > tr:hover { /* * $("#length_info_row").animate({ backgroundColor: "#ffffff" }, 1).delay(200).animate({ backgroundColor: "#dddd33" }, 300).delay(200).animate({ backgroundColor: "#ffffee" }, 1000); */ + +#floating_prefix_header { + background-color: #FFFFFF; +} diff --git a/nipap-www/nipapwww/static/css/theme.css b/nipap-www/nipapwww/static/css/theme.css new file mode 100644 index 000000000..8677a36b6 --- /dev/null +++ b/nipap-www/nipapwww/static/css/theme.css @@ -0,0 +1,131 @@ +:root { + color-scheme: light; +} + +#theme-switch { + display: inline-block; + position: relative; + width: 44px; + height: 24px; + padding: 0; + border: 1px solid #8793a4; + border-radius: 14px; + background: #566171; + vertical-align: middle; + cursor: pointer; + touch-action: none; +} + +#theme-switch[aria-checked="true"] { + background: #2868c5; + border-color: #93c5fd; +} + +.theme-switch-thumb { + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #fff; + pointer-events: none; + transition: transform 120ms ease; +} + +#theme-switch[aria-checked="true"] .theme-switch-thumb { + transform: translateX(20px); +} + +#theme-switch:focus-visible { + outline: 2px solid #93c5fd; + outline-offset: 3px; +} + +@media (prefers-reduced-motion: reduce) { + .theme-switch-thumb { transition: none; } +} + +:root[data-theme="dark"] { + color-scheme: dark; + --theme-bg: #151a22; + --theme-panel: #202835; + --theme-hover: #2c394b; + --theme-text: #e3e9f2; + --theme-muted: #adb9cb; + --theme-border: #48556a; + background: var(--theme-bg); +} + +[data-theme="dark"] body { + background: var(--theme-bg); + color: var(--theme-text); +} + +[data-theme="dark"] a { color: #93c5fd; } +[data-theme="dark"] a:hover { color: #c5dfff; } + +[data-theme="dark"] :is(.options-group, .options-content, .options-group-heading, + .options-guide, .page-title, #search_box, .popup_menu, .selector, .prefix_select_container, + .selector_filterbar, .ui-widget-content, .ui-widget-header, .ui-dialog, + .ui-dialog-question, .modal-content, .panel, .panel-heading, .well, + .dropdown-menu, .table-striped > tbody > tr:nth-of-type(odd), pre, + .minibutton, .vrf_filter_entry_rt, .preflist_vrf_rt) { + background: var(--theme-panel); + color: var(--theme-text); + border-color: var(--theme-border); + text-shadow: none; +} + +[data-theme="dark"] :is(input:not([type="submit"]):not([type="button"]), + textarea, select, .form-control, .btn-default, + ul.tagit, tags-input .tags, .ui-state-default) { + background: var(--theme-bg); + color: var(--theme-text); + border-color: var(--theme-border); +} + +[data-theme="dark"] :is(input, textarea)::placeholder { color: var(--theme-muted); } +[data-theme="dark"] :is(.row_match, .selector_entry_name, .selector > div > a, .popup_menu > a) { + color: var(--theme-text); +} +[data-theme="dark"] :is(.row_collateral, .prefix_hidden_text, .prefix_vrf, .version > a, .text-muted) { + color: var(--theme-muted); +} +[data-theme="dark"] :is(.option:hover, .prefix_entry:hover, tbody.listing > tr:hover, + .dropdown-menu > li > a:hover, .minibutton:hover) { + background: var(--theme-hover); +} +[data-theme="dark"] :is(.table > tbody > tr > td, + .table > thead > tr > th, .rule, hr, .top_menu) { + border-color: var(--theme-border); +} +[data-theme="dark"] :is(.required, .text-danger) { color: #ff9c9c; } +[data-theme="dark"] .prefix_added_entry { background: #204535; color: #d6f5e1; } +[data-theme="dark"] .popup_menu h3 { background: var(--theme-panel); text-shadow: none; } + +[data-theme="dark"] #query_string { + background: var(--theme-bg); + color: var(--theme-text); + border-color: var(--theme-border); +} + +[data-theme="dark"] #query_string:focus { + border-color: #93c5fd; +} + +[data-theme="dark"] #floating_prefix_header { + background-color: var(--theme-panel); + border-color: var(--theme-border); +} + +[data-theme="dark"] .preflist_vrf_panel { + color: var(--theme-muted); + border-color: var(--theme-border); +} + +[data-theme="dark"] .prefix_list_header { + background: var(--theme-panel); + color: var(--theme-text); + border-color: var(--theme-border); +} diff --git a/nipap-www/nipapwww/static/templates/prefix_edit.html b/nipap-www/nipapwww/static/templates/prefix_edit.html index 0c0baee91..413eb21df 100644 --- a/nipap-www/nipapwww/static/templates/prefix_edit.html +++ b/nipap-www/nipapwww/static/templates/prefix_edit.html @@ -8,7 +8,7 @@

Result

-
+
The prefix {{ prefix.prefix }} was successfully edited.
diff --git a/nipap-www/nipapwww/static/theme.js b/nipap-www/nipapwww/static/theme.js new file mode 100644 index 000000000..62ca12ec0 --- /dev/null +++ b/nipap-www/nipapwww/static/theme.js @@ -0,0 +1,69 @@ +(function () { + 'use strict'; + var preference; + var systemTheme = window.matchMedia('(prefers-color-scheme: dark)'); + try { + preference = localStorage.getItem('nipap-theme'); + } catch (error) { + // The switch still works when browser storage is unavailable. + } + if (preference !== 'dark' && preference !== 'light') preference = null; + + function applyTheme() { + var dark = preference ? preference === 'dark' : systemTheme.matches; + document.documentElement.dataset.theme = dark ? 'dark' : 'light'; + var control = document.getElementById('theme-switch'); + if (control) control.setAttribute('aria-checked', String(dark)); + } + + applyTheme(); + systemTheme.addEventListener('change', applyTheme); + document.addEventListener('DOMContentLoaded', function () { + var control = document.getElementById('theme-switch'); + var startX = null; + var dragged = false; + function select(dark) { + preference = dark ? 'dark' : 'light'; + try { + localStorage.setItem('nipap-theme', preference); + } catch (error) { + // Retain the selection for this page even without storage. + } + applyTheme(); + } + applyTheme(); + control.addEventListener('click', function () { + if (dragged) { + dragged = false; + return; + } + select(document.documentElement.dataset.theme !== 'dark'); + }); + control.addEventListener('pointerdown', function (event) { + if (!event.isPrimary || event.button !== 0) return; + startX = event.clientX; + dragged = false; + control.setPointerCapture(event.pointerId); + }); + control.addEventListener('pointerup', function (event) { + if (startX === null) return; + var distance = event.clientX - startX; + startX = null; + if (Math.abs(distance) > 8) { + dragged = true; + select(distance > 0); + } + }); + control.addEventListener('pointercancel', function () { + startX = null; + dragged = false; + }); + control.addEventListener('keydown', function (event) { + dragged = false; + if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { + event.preventDefault(); + select(event.key === 'ArrowRight'); + } + }); + }); +}()); diff --git a/nipap-www/nipapwww/templates/base.html b/nipap-www/nipapwww/templates/base.html index 49d3a3477..5d746ed84 100644 --- a/nipap-www/nipapwww/templates/base.html +++ b/nipap-www/nipapwww/templates/base.html @@ -4,6 +4,7 @@ {{ g.page_title|default('NIPAP') }} + @@ -64,6 +65,7 @@ ng_compile = angular.element($('html')).injector().get('$compile'); }); + {% block head %} {% endblock %} @@ -95,6 +97,11 @@
    +
  1. + +
  2. Log out
  3. diff --git a/nipap-www/nipapwww/templates/macros.html b/nipap-www/nipapwww/templates/macros.html index efb6479ca..ecfa016df 100644 --- a/nipap-www/nipapwww/templates/macros.html +++ b/nipap-www/nipapwww/templates/macros.html @@ -56,7 +56,7 @@
    Description
-