diff --git a/AGENTS.md b/AGENTS.md index 693db51..b20c856 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -195,9 +195,10 @@ A global `[hidden] { display: none !important }` rule exists. Toggle visibility Prepend an entry to `web-standalone/changelog.json` in the words of the person using the tool, not the words of the code. It feeds the "What's new" panel. -**One or two sentences, no more.** The panel is a narrow column people skim, not a place to -explain the reasoning — that is what the commit message and the issue are for. Say what changed -and why it matters to someone using the tool, and stop. +**One or two lines, no more** — roughly 120 characters, not 120 words. The panel is a narrow +column people skim, so two long sentences already render as five or six lines. The reasoning +belongs in the commit message and the issue. Say what changed and why it matters to someone +using the tool, and stop. --- diff --git a/web-standalone/.dockerignore b/web-standalone/.dockerignore index 9913164..dfa8ac1 100644 --- a/web-standalone/.dockerignore +++ b/web-standalone/.dockerignore @@ -4,3 +4,5 @@ !bag3d.py !index.html !changelog.json +!assets/ +!assets/** diff --git a/web-standalone/Dockerfile b/web-standalone/Dockerfile index 2c88333..6065301 100644 --- a/web-standalone/Dockerfile +++ b/web-standalone/Dockerfile @@ -8,6 +8,8 @@ LABEL org.opencontainers.image.source="https://github.com/khagele/meshcore-trian WORKDIR /app COPY server.py spamdetector.py bag3d.py index.html changelog.json ./ +# Shared DMC web fonts (DutchMeshCore-Branding); the page @font-face's these. +COPY assets/ ./assets/ # Inside the container we must bind all interfaces; restrict exposure when # publishing the port instead (e.g. -p 127.0.0.1:8000:8000). diff --git a/web-standalone/assets/fonts/aldrich-v22-latin-400.woff2 b/web-standalone/assets/fonts/aldrich-v22-latin-400.woff2 new file mode 100644 index 0000000..c92a531 Binary files /dev/null and b/web-standalone/assets/fonts/aldrich-v22-latin-400.woff2 differ diff --git a/web-standalone/assets/fonts/ibm-plex-mono-latin-400.woff2 b/web-standalone/assets/fonts/ibm-plex-mono-latin-400.woff2 new file mode 100644 index 0000000..52b6c75 Binary files /dev/null and b/web-standalone/assets/fonts/ibm-plex-mono-latin-400.woff2 differ diff --git a/web-standalone/assets/fonts/ibm-plex-mono-latin-600.woff2 b/web-standalone/assets/fonts/ibm-plex-mono-latin-600.woff2 new file mode 100644 index 0000000..c6759ef Binary files /dev/null and b/web-standalone/assets/fonts/ibm-plex-mono-latin-600.woff2 differ diff --git a/web-standalone/assets/fonts/ibm-plex-sans-latin-var.woff2 b/web-standalone/assets/fonts/ibm-plex-sans-latin-var.woff2 new file mode 100644 index 0000000..e5ebabb Binary files /dev/null and b/web-standalone/assets/fonts/ibm-plex-sans-latin-var.woff2 differ diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index 0438d52..a7e65a0 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -1,45 +1,57 @@ [ + { + "id": "2026-09-10-plain-scroll-zooms", + "date": "2026-09-10", + "title": "Scrolling zooms the map again", + "body": "On a desktop the wheel zooms the map straight away; holding cmd or ctrl is only needed on a phone, where the page has to scroll past it." + }, + { + "id": "2026-09-10-dmc-house-style", + "date": "2026-09-10", + "title": "The triangulator looks like the rest of DutchMeshCore", + "body": "The triangulator now wears the shared DMC house style, and has a light theme for the first time — switch it top right." + }, { "id": "2026-09-10-step-1-starts-active", "date": "2026-09-10", "title": "Step 1 is ready the moment the page opens", - "body": "Step 1 sat greyed out until you typed a prefix, which also made the Spam incident button unclickable in exactly the case it is for. It is active from the first paint." + "body": "Step 1 is ready the moment the page opens, instead of greyed out until you type." }, { "id": "2026-09-10-map-controls-with-the-map", "date": "2026-09-10", "title": "The map's buttons sit with the map", - "body": "Clear Map, the layer switches and the relief slider sat at the bottom of the left column, nearly a thousand pixels below the map they act on; they are now a strip along the top of the map. Steps fold shut and show what they hold, so on a phone the map is no longer three screens down." + "body": "The map's buttons moved from the bottom of the column to a strip on the map. Finished steps fold shut." }, { "id": "2026-09-10-shorter-observer-list", "date": "2026-09-10", "title": "The observer list says what you are choosing on", - "body": "Observer rows swapped coordinates to five decimals for the number you actually choose on: how far each repeater is from the centre of the cluster. The cluster summary went from one paragraph to six scannable lines, with a mark on the two that are a guess rather than a measurement." + "body": "Observer rows now show how far each repeater is from the cluster centre, which is what the automatic pick is based on." }, { "id": "2026-09-10-two-ways-into-step-1", "date": "2026-09-10", "title": "Two ways to start, and second-hop out of the way", - "body": "Loading a spam-detector incident is now one of two buttons at the top of Step 1 instead of a fold above the prefix field, and it fills the prefixes in so you can still change them. Second-hop prefixes fold away until you ask for them." + "body": "Loading a spam-detector incident is now a tab at the top of Step 1. Second-hop prefixes fold away until you need them." }, { "id": "2026-09-10-step-1-reads-properly", "date": "2026-09-10", "title": "Step 1 stops reading as one grey block", - "body": "Every line in Step 1 was the same colour and size, with the explanation under a field brighter than the label above it. Field names now lead, help sits quietly underneath, and what the tool says back gets its own line." + "body": "Step 1 reads properly now: field names lead and their explanation sits quietly underneath." }, { "id": "2026-09-10-add-prefix-adds-one-observer", "date": "2026-09-10", "title": "Adding a prefix adds one observer, not every node that carries it", - "body": "Typing a prefix into Add Prefixes In Locked Cluster ticked every node carrying it, so an ambiguous prefix counted twice from two places at once. It now adds one, and says when there was more than one to choose from." + "body": "Adding a prefix adds one observer, not every node that happens to carry it." }, { "id": "2026-09-10-done-resolving-applies", "date": "2026-09-10", "title": "Finishing on the map applies what you picked", - "body": "Swapping which node stands for a prefix on the map only ticked a box in a list you could not see from the map. “Done resolving” now applies the picks and says so." + "body": "“Done resolving” now applies the picks you made on the map, instead of leaving them in a list you cannot see." }, { "id": "2026-09-07-antenna-height-from-the-roof", diff --git a/web-standalone/index.html b/web-standalone/index.html index 633067c..71a29bf 100644 --- a/web-standalone/index.html +++ b/web-standalone/index.html @@ -4,6 +4,16 @@ MC Repeater Map + means dark. */ + :root[data-theme="light"] { + color-scheme: light; + + --bg: #f4f6fb; + --text: #1a1c2e; + --text-muted: #5c6487; + --text-faint: #8a90ab; + --heading: #1a1c2e; + --link: #3d6fdb; + --accent: #3d6fdb; + --accent-light: #5b86e5; + --accent-strong: #3455b0; + --border: #cdd2e8; + --border-strong: #b5bce0; + --surface-hover: #eef0f8; + --code-bg: rgba(20, 30, 60, 0.06); + + --success: #1a9e6e; + --warning: #a07008; + --danger: #c43a3a; + --danger-text: #c43a3a; + + --surface: #ffffff; + --surface-2: #eef0f8; + --surface-shadow: 0 10px 26px rgba(20, 30, 60, 0.08); + /* Flat ground, and the two overlays switched off by their tokens. */ + --body-bg: none; + --sheen-opacity: 0; + --grid-opacity: 0; + --scan-opacity: 0; + + --heading-shadow: none; + + --field-bg: #ffffff; + --menu-bg: #ffffff; + --chip-bg: #eef0f8; + --scrim: rgba(26, 28, 46, 0.35); + --raised: rgba(20, 30, 60, 0.06); } * { @@ -37,12 +179,73 @@ body { margin: 0; min-height: 100vh; - font-family: "Avenir Next", Avenir, "Segoe UI", sans-serif; + font-family: var(--font-sans); color: var(--text); - background: - radial-gradient(circle at top left, rgba(0, 184, 148, 0.18), transparent 32%), - radial-gradient(circle at top right, rgba(255, 159, 67, 0.16), transparent 30%), - linear-gradient(180deg, #0a1b24 0%, #071118 100%); + /* Red glow top-left, blue top-right, over a dark fade. The house + "glass over glow" ground (#139); it was teal and orange. */ + background-color: var(--bg); + background-image: var(--body-bg); + background-attachment: fixed; + position: relative; + } + + /* Grid and scanlines (STYLE-GUIDE §6). Both sit under the content and + above the ground, and both go to zero opacity in light mode. */ + body::before, + body::after { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + } + body::before { + background-image: + linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.024) 1px, transparent 1px); + background-size: 42px 42px; + -webkit-mask-image: radial-gradient(ellipse at 50% 30%, #000 0%, transparent 78%); + mask-image: radial-gradient(ellipse at 50% 30%, #000 0%, transparent 78%); + opacity: var(--grid-opacity); + } + body::after { + background-image: repeating-linear-gradient(0deg, + rgba(255, 255, 255, 0.043), + rgba(255, 255, 255, 0.043) 1px, + transparent 1px, + transparent 4px); + mix-blend-mode: overlay; + opacity: var(--scan-opacity); + } + /* Everything the operator touches sits above the two overlays. */ + .shell, + .intro-overlay, + .loading-overlay { + position: relative; + z-index: 1; + } + /* The topbar has to outrank the shell, not tie with it: its dropdowns hang + down over the map strip, and at equal z-index the later element in the + document wins — so the strip's buttons cut straight through the theme + and What's new panels. */ + .topbar { + position: relative; + z-index: 2; + } + + /* Headings and the brand run in the display face, uppercased, with a hard + drop shadow that light mode drops (STYLE-GUIDE §3). */ + h1, h2, h3, + .step-kicker, + .topbar-title { + font-family: var(--font-head); + text-transform: uppercase; + letter-spacing: 0.03em; + color: var(--heading); + text-shadow: var(--heading-shadow); + } + code, kbd, samp, pre { + font-family: var(--font-mono); } /* Full-width bar above everything: dutchmeshcore.nl branding + community @@ -57,19 +260,17 @@ flex-wrap: wrap; width: min(1200px, calc(100% - 32px)); margin: 16px auto 0; - padding: 10px 16px 10px 20px; - border-radius: 12px; - background: #0b0d12; - border: 1px solid rgba(255, 255, 255, 0.08); - } - .topbar-stripe { - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 4px; - border-radius: 12px 0 0 12px; - background: linear-gradient(180deg, #c8102e 0%, #c8102e 33%, #ffffff 33%, #ffffff 66%, #21468b 66%, #21468b 100%); + padding: 10px 16px; + border-radius: var(--radius); + background: var(--surface-2); + border: 1px solid var(--border); + } + /* Every DMC surface opens with this band (STYLE-GUIDE §9): full width, in + normal flow, above the navbar. It was a 4px vertical bar beside the + topbar, which is the same idea in the wrong orientation. */ + .page-stripe { + height: var(--stripe-h); + background: linear-gradient(90deg, var(--red), var(--white) 50%, var(--blue)); } .topbar-brand { display: flex; @@ -81,7 +282,7 @@ width: 24px; height: 24px; border-radius: 6px; - background: linear-gradient(135deg, #c8102e, #21468b); + background: linear-gradient(135deg, var(--red), var(--blue)); display: flex; align-items: center; justify-content: center; @@ -101,11 +302,11 @@ align-items: center; gap: 14px; font-size: 0.8rem; - color: var(--muted); + color: var(--text-muted); flex-wrap: wrap; } .topbar-nav a { - color: var(--muted); + color: var(--text-muted); text-decoration: none; white-space: nowrap; } @@ -135,15 +336,59 @@ gap: 6px 16px; padding: 12px 14px; min-width: 260px; - border-radius: 12px; - background: rgba(7, 25, 35, 0.97); - border: 1px solid rgba(143, 187, 201, 0.18); - box-shadow: var(--shadow); + border-radius: var(--radius); + background: var(--menu-bg); + border: 1px solid var(--border); + box-shadow: var(--surface-shadow); z-index: 20; } .topbar-dropdown-menu a { font-size: 0.82rem; } + /* Theme picker: chip trigger, checkable menu (STYLE-GUIDE §9). */ + .theme-dd > summary { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 9px; + border: 1px solid var(--border); + background: var(--surface-hover); + font-size: 0.8rem; + line-height: 1; + } + .theme-dd > summary::after { content: none; } + .theme-dd > summary:hover { border-color: var(--accent); } + .theme-menu { + left: auto; + right: 0; + min-width: 150px; + grid-template-columns: 1fr; + gap: 2px; + padding: 6px; + } + .theme-menu button { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + margin: 0; + padding: 7px 9px; + border: 0; + border-radius: var(--radius); + background: none; + color: var(--text-muted); + font: inherit; + font-size: 0.82rem; + cursor: pointer; + text-align: left; + } + .theme-menu button:hover { background: var(--surface-hover); color: var(--text); } + .theme-menu .chk { color: var(--accent); visibility: hidden; } + .theme-menu button[aria-checked="true"] { color: var(--text); } + .theme-menu button[aria-checked="true"] .chk { visibility: visible; } .topbar-meta { margin-left: auto; display: flex; @@ -160,14 +405,14 @@ justify-content: center; line-height: 1; font-size: 0.8rem; - border-radius: 9px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(255, 255, 255, 0.04); - color: var(--muted); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--surface-hover); + color: var(--text-muted); cursor: pointer; } .topbar-meta .is-icon:hover { - background: rgba(255, 255, 255, 0.09); + background: var(--raised); color: var(--text); } /* The panel hangs off the topbar now, so it opens downward from the right @@ -180,17 +425,17 @@ .topbar-github { flex: 0 0 auto; - border-radius: 10px; + border-radius: var(--radius); padding: 6px 12px; font-weight: 600; font-size: 0.8rem; - background: rgba(255, 255, 255, 0.04); - color: var(--muted); - border: 1px solid rgba(255, 255, 255, 0.12); + background: var(--surface-hover); + color: var(--text-muted); + border: 1px solid var(--border); text-decoration: none; } .topbar-github:hover { - background: rgba(255, 255, 255, 0.08); + background: var(--border); color: var(--text); } @@ -205,10 +450,10 @@ } .map-wrap { - background: var(--panel); - border: 1px solid var(--panel-border); - border-radius: 24px; - box-shadow: var(--shadow); + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--surface-shadow); backdrop-filter: blur(16px); } @@ -228,15 +473,15 @@ align-items: center; gap: 8px; padding: 8px 10px; - border-radius: 14px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: var(--radius); + background: var(--chip-bg); + border: 1px solid var(--border); } .layer-chips-label { font-size: 0.64rem; letter-spacing: 0.09em; text-transform: uppercase; - color: var(--faint); + color: var(--text-faint); margin-right: 2px; } /* Share is the one control here that is not about the map, so it sits at @@ -258,14 +503,14 @@ z-index: 20; width: 250px; padding: 10px 12px; - border-radius: 12px; - background: rgba(7, 25, 35, 0.97); - border: 1px solid rgba(143, 187, 201, 0.28); - box-shadow: var(--shadow); + border-radius: var(--radius); + background: var(--menu-bg); + border: 1px solid var(--border-strong); + box-shadow: var(--surface-shadow); } .controls-card button { margin: 0; - border-radius: 10px; + border-radius: var(--radius); padding: 7px 12px; font: inherit; font-weight: 600; @@ -275,8 +520,8 @@ /* 3D View is a toggle, not a one-way "open" (#29): the same control enters and exits the mode, so it needs a visible pressed state. */ .controls-card button[aria-pressed="true"] { - background: rgba(0, 184, 148, 0.16); - border-color: rgba(0, 184, 148, 0.55); + background: color-mix(in srgb, var(--accent) 16%, transparent); + border-color: color-mix(in srgb, var(--accent) 55%, transparent); color: var(--accent); } /* Share (#34): confirmation text and the manual-copy fallback both take @@ -290,7 +535,7 @@ flex: 1 0 100%; margin: 0; font-size: 0.74rem; - color: var(--muted); + color: var(--text-muted); } .share-status.is-copied { color: var(--accent); @@ -299,17 +544,17 @@ flex: 1 0 100%; margin: 0; font-size: 0.74rem; - color: var(--muted); + color: var(--text-muted); } .share-fallback input { width: 100%; margin-top: 4px; padding: 7px 9px; - border-radius: 9px; - border: 1px solid rgba(0, 184, 148, 0.35); - background: rgba(5, 15, 22, 0.9); + border-radius: var(--radius); + border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); + background: var(--field-bg); color: var(--text); - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-family: var(--font-mono); font-size: 0.72rem; } @@ -362,7 +607,7 @@ width: 8px; height: 8px; border-radius: 50%; - background: var(--accent-2); + background: var(--warning); } .whats-new-panel { position: absolute; @@ -375,14 +620,14 @@ max-height: 360px; overflow-y: auto; padding: 12px 14px; - border-radius: 16px; - background: rgba(7, 25, 35, 0.97); - border: 1px solid rgba(143, 187, 201, 0.18); - box-shadow: var(--shadow); + border-radius: var(--radius-lg); + background: var(--menu-bg); + border: 1px solid var(--border); + box-shadow: var(--surface-shadow); } .whats-new-entry { padding: 8px 0; - border-top: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid var(--border); } .whats-new-entry:first-child { border-top: none; @@ -390,7 +635,7 @@ .whats-new-entry time { display: block; font-size: 0.72rem; - color: var(--muted); + color: var(--text-muted); } .whats-new-entry strong { display: block; @@ -400,7 +645,7 @@ .whats-new-entry p { margin: 0; font-size: 0.82rem; - color: var(--muted); + color: var(--text-muted); line-height: 1.4; } @@ -429,7 +674,7 @@ gap: 4px; margin: 0; font-size: 0.72rem; - color: var(--muted); + color: var(--text-muted); } .radius-row input { width: 100%; } @@ -444,30 +689,30 @@ grid-template-columns: 1fr 1fr; gap: 3px; padding: 3px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.045); - border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: var(--radius); + background: var(--chip-bg); + border: 1px solid var(--border); margin-bottom: 14px; } /* Beats `.step-panel button` (0,1,1) on the accent fill and the margin. */ .step-panel .entry-tabs .entry-tab { margin: 0; border: 0; - border-radius: 9px; + border-radius: var(--radius); padding: 7px 6px; font-size: 0.78rem; font-weight: 500; background: transparent; /* --faint is for text that recedes under a label; a tab is a control you have to be able to read and click. */ - color: var(--muted); + color: var(--text-muted); box-shadow: none; } .step-panel .entry-tabs .entry-tab[aria-pressed="true"] { - background: rgba(0, 184, 148, 0.18); + background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--text); font-weight: 600; - box-shadow: inset 0 0 0 1px rgba(0, 184, 148, 0.35); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent); } .step-panel .entry-tabs .entry-tab:hover { color: var(--text); @@ -477,13 +722,13 @@ /* The uncommon half of Step 1, closed until asked for (#133). */ .hop2-details { margin-top: 14px; - border-top: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid var(--border); padding-top: 12px; } .hop2-details summary { cursor: pointer; list-style: none; - color: var(--muted); + color: var(--text-muted); font-size: 0.8rem; } .hop2-details summary::-webkit-details-marker { display: none; } @@ -498,9 +743,9 @@ .step-panel select.import-select { width: 100%; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(5, 15, 22, 0.9); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--field-bg); color: var(--text); padding: 12px; font: inherit; @@ -531,7 +776,7 @@ margin: 5px 0 0; font-size: 0.75rem; line-height: 1.45; - color: var(--faint); + color: var(--text-faint); } .step-panel p.hint:empty { display: none; @@ -540,14 +785,14 @@ margin: 6px 0 0; font-size: 0.72rem; line-height: 1.45; - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - color: var(--muted); + font-family: var(--font-mono); + color: var(--text-muted); padding-left: 9px; border-left: 2px solid var(--accent); } /* A caveat is not the same as a result (#54, #85). */ .step-panel p.hint.is-caveat { - border-left-color: var(--accent-2); + border-left-color: var(--warning); } /* A field group needs air before the next one starts, or the reply line still reads as the next label's opening words (#132). */ @@ -556,19 +801,19 @@ margin-top: 14px; } .step-panel .label-optional { - color: var(--faint); + color: var(--text-faint); font-weight: 400; font-size: 0.94em; } /* An inline example of what to type, instead of the literal backticks that used to ship as UI text. */ .step-panel .sample { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-family: var(--font-mono); font-size: 0.92em; - background: rgba(255, 255, 255, 0.06); + background: var(--chip-bg); border-radius: 4px; padding: 1px 5px; - color: var(--muted); + color: var(--text-muted); } /* Nothing styled links inside the panels, so the browser default #0000EE landed on a dark teal card (#132). */ @@ -576,7 +821,7 @@ .utility-panel a { color: var(--accent); text-underline-offset: 2px; - text-decoration-color: rgba(0, 184, 148, 0.45); + text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); } .step-panel a:hover, .utility-panel a:hover { @@ -592,7 +837,7 @@ .node-meta { margin: 0; - color: var(--muted); + color: var(--text-muted); line-height: 1.5; font-size: 0.93rem; } @@ -607,9 +852,10 @@ .step-panel, .utility-panel { padding: 14px 16px; - border-radius: 18px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: var(--radius-lg); + background: var(--surface); + border: 1px solid var(--border); + box-shadow: var(--surface-shadow); } .utility-panel { @@ -656,7 +902,7 @@ .step-panel .step-head h3 { grid-area: title; } .step-panel .step-head .step-chev { grid-area: chevron; - color: var(--faint); + color: var(--text-faint); font-size: 0.7rem; transition: transform 0.15s ease; } @@ -716,15 +962,15 @@ .prefix-panel label { display: block; margin-bottom: 8px; - color: var(--muted); + color: var(--text-muted); font-size: 0.84rem; } .prefix-panel input { width: 100%; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(5, 15, 22, 0.9); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--field-bg); color: var(--text); padding: 12px; font: inherit; @@ -732,7 +978,7 @@ .prefix-panel p { margin: 10px 0 0; - color: var(--muted); + color: var(--text-muted); font-size: 0.84rem; line-height: 1.45; } @@ -756,10 +1002,10 @@ .workflow-step { padding: 10px 12px; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); - color: var(--muted); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--surface-hover); + color: var(--text-muted); font-size: 0.84rem; line-height: 1.4; } @@ -772,13 +1018,13 @@ } .workflow-step.is-active { - border-color: rgba(155, 229, 100, 0.35); - background: rgba(155, 229, 100, 0.1); + border-color: color-mix(in srgb, var(--success) 35%, transparent); + background: color-mix(in srgb, var(--success) 10%, transparent); } .workflow-step.is-done { - border-color: rgba(0, 184, 148, 0.32); - background: rgba(0, 184, 148, 0.12); + border-color: color-mix(in srgb, var(--accent) 32%, transparent); + background: color-mix(in srgb, var(--accent) 12%, transparent); } .source-grid { @@ -801,9 +1047,9 @@ .step-panel input, .utility-panel input { width: 100%; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(5, 15, 22, 0.9); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--field-bg); color: var(--text); padding: 12px; font: inherit; @@ -814,11 +1060,11 @@ .step-panel button { margin-top: 12px; border: 0; - border-radius: 12px; + border-radius: var(--radius); padding: 10px 14px; font: inherit; font-weight: 600; - color: #071118; + color: var(--bg); background: var(--accent); cursor: pointer; } @@ -833,19 +1079,19 @@ display: block; text-align: center; text-decoration: none; - border: 1px solid rgba(255, 255, 255, 0.16); - border-radius: 12px; + border: 1px solid var(--raised); + border-radius: var(--radius); padding: 9px 14px; font: inherit; font-weight: 600; color: var(--text); - background: rgba(255, 255, 255, 0.05); + background: var(--chip-bg); cursor: pointer; } .utility-panel button:hover, .utility-panel .button-secondary:hover { - background: rgba(255, 255, 255, 0.10); - border-color: rgba(255, 255, 255, 0.26); + background: var(--raised); + border-color: var(--border-strong); } .button-row { @@ -857,19 +1103,19 @@ .button-secondary, .step-panel button.button-secondary { - background: rgba(255, 255, 255, 0.06); + background: var(--chip-bg); color: var(--text); - border: 1px solid rgba(255, 255, 255, 0.16); + border: 1px solid var(--raised); } .step-panel button.button-secondary:hover { - background: rgba(255, 255, 255, 0.10); + background: var(--raised); } .button-muted, .step-panel button.button-muted { - background: rgba(255, 255, 255, 0.04); - color: var(--muted); - border: 1px solid rgba(255, 255, 255, 0.12); + background: var(--surface-hover); + color: var(--text-muted); + border: 1px solid var(--border); } /* Focus visibility + disabled states (accessibility polish). */ @@ -896,7 +1142,7 @@ was never applied by anything, so it is reused rather than a second mode concept being invented next to it. */ .map-wrap.is-draw-mode { - outline: 2px solid rgba(155, 229, 100, 0.4); + outline: 2px solid color-mix(in srgb, var(--success) 40%, transparent); } .manual-banner { @@ -911,8 +1157,8 @@ max-width: min(420px, calc(100% - 70px)); padding: 6px 12px; border-radius: 999px; - background: rgba(7, 25, 35, 0.92); - border: 1px solid rgba(155, 229, 100, 0.5); + background: var(--menu-bg); + border: 1px solid color-mix(in srgb, var(--success) 50%, transparent); color: var(--text); font-size: 0.76rem; font-weight: 600; @@ -926,7 +1172,7 @@ .manual-banner button { margin: 0; padding: 3px 9px; - border: 1px solid rgba(155, 229, 100, 0.6); + border: 1px solid color-mix(in srgb, var(--success) 60%, transparent); border-radius: 999px; background: transparent; color: var(--text); @@ -945,7 +1191,7 @@ width: 7px; height: 7px; border-radius: 999px; - background: #9be564; + background: var(--success); flex: 0 0 auto; } @@ -953,7 +1199,7 @@ .step-panel p, .utility-panel p { margin: 10px 0 0; - color: var(--muted); + color: var(--text-muted); font-size: 0.84rem; line-height: 1.45; } @@ -986,10 +1232,10 @@ margin-top: 0; text-align: left; font-weight: 400; - background: rgba(7, 17, 24, 0.45); + background: var(--scrim); color: var(--text); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 14px; + border: 1px solid var(--border); + border-radius: var(--radius); padding: 10px 12px; cursor: pointer; } @@ -998,7 +1244,7 @@ which is near-invisible on a surface this dark. Hover lifts the card towards the panel rather than washing it white. */ .step-panel .candidate-card:hover { - background: rgba(7, 17, 24, 0.30); + background: var(--surface-hover); border-color: rgba(255, 255, 255, 0.22); filter: none; } @@ -1007,14 +1253,14 @@ on. The inset bar carries the state on its own, so the tint never has to be strong enough to hurt the text sitting on it. */ .step-panel .candidate-card.is-selected { - border-color: rgba(155, 229, 100, 0.55); + border-color: color-mix(in srgb, var(--success) 55%, transparent); /* Two layers, not two colours: the lime wash sits ON the same dark tint as a resting card, so selection changes the hue without giving back the contrast the dark tint bought. */ background: - linear-gradient(rgba(155, 229, 100, 0.12), rgba(155, 229, 100, 0.12)), - rgba(7, 17, 24, 0.45); - box-shadow: inset 3px 0 0 rgba(155, 229, 100, 0.8); + linear-gradient(color-mix(in srgb, var(--success) 12%, transparent), color-mix(in srgb, var(--success) 12%, transparent)), + var(--scrim); + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--success) 80%, transparent); } .step-panel .candidate-card strong, @@ -1031,7 +1277,7 @@ /* --muted measured against the composited card surface: 7.01:1 at rest, 6.41:1 on hover, 5.25:1 selected, taking the worst backdrop zone. */ .step-panel .candidate-card span { - color: var(--muted); + color: var(--text-muted); font-size: 0.8rem; line-height: 1.35; } @@ -1048,8 +1294,8 @@ gap: 8px; align-items: center; padding: 5px 8px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.04); + border-radius: var(--radius); + background: var(--surface-hover); border: 1px solid rgba(255, 255, 255, 0.07); } @@ -1079,7 +1325,7 @@ .observer-row.is-excluded { opacity: 0.65; - background: rgba(255, 255, 255, 0.02); + background: var(--surface-hover); } .observer-row input[type="checkbox"] { @@ -1110,7 +1356,7 @@ } .observer-copy span { - color: var(--muted); + color: var(--text-muted); font-size: 0.72rem; line-height: 1.3; white-space: nowrap; @@ -1126,26 +1372,26 @@ font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; - background: rgba(255, 159, 67, 0.16); - color: #ffb86b; - border: 1px solid rgba(255, 159, 67, 0.3); + background: color-mix(in srgb, var(--warning) 16%, transparent); + color: var(--warning); + border: 1px solid color-mix(in srgb, var(--warning) 35%, transparent); cursor: help; } .observer-reason.is-range { - background: rgba(124, 58, 237, 0.18); - color: #c4b5fd; - border-color: rgba(124, 58, 237, 0.35); + background: color-mix(in srgb, var(--accent) 18%, transparent); + color: var(--accent-strong); + border-color: color-mix(in srgb, var(--accent) 45%, transparent); } .observer-support { margin-top: 6px; - border-top: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid var(--border); padding-top: 4px; } .observer-support > summary { cursor: pointer; list-style: none; - color: #c4b5fd; + color: var(--text-muted); font-size: 0.74rem; padding: 5px 2px; user-select: none; @@ -1159,9 +1405,9 @@ was what made every observer row 60px (#134). */ .step-panel .observer-weight { width: 100%; - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(5, 15, 22, 0.9); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--field-bg); color: var(--text); padding: 5px 6px; font: inherit; @@ -1169,8 +1415,8 @@ } .node-label { - background: rgba(7, 25, 35, 0.92); - border: 1px solid rgba(143, 187, 201, 0.26); + background: var(--menu-bg); + border: 1px solid var(--border-strong); color: var(--text); padding: 2px 6px; border-radius: 999px; @@ -1196,9 +1442,9 @@ #map3d { width: 100%; height: 100%; - border-radius: 18px; + border-radius: var(--radius-lg); overflow: hidden; - background: #071118; + background: var(--bg); } /* MapLibre popups (#91). The old Leaflet popup got a dark text colour from @@ -1213,12 +1459,12 @@ specificity is what makes these survive the load order. Same trap as the candidate cards in #73, from the other direction. */ .maplibregl-popup .maplibregl-popup-content { - background: rgba(7, 25, 35, 0.96); + background: var(--menu-bg); color: var(--text); - border: 1px solid var(--panel-border); - border-radius: 14px; + border: 1px solid var(--border); + border-radius: var(--radius); padding: 12px 14px; - box-shadow: var(--shadow); + box-shadow: var(--surface-shadow); font: inherit; font-size: 0.82rem; } @@ -1227,16 +1473,16 @@ used depends on the popup's anchor. Setting all four means it matches whichever way MapLibre decides to point it. */ .maplibregl-popup .maplibregl-popup-tip { - border-top-color: rgba(7, 25, 35, 0.96); - border-bottom-color: rgba(7, 25, 35, 0.96); - border-left-color: rgba(7, 25, 35, 0.96); - border-right-color: rgba(7, 25, 35, 0.96); + border-top-color: var(--menu-bg); + border-bottom-color: var(--menu-bg); + border-left-color: var(--menu-bg); + border-right-color: var(--menu-bg); } .maplibregl-popup .maplibregl-popup-close-button { background: transparent; border: 0; - color: var(--muted); + color: var(--text-muted); font-size: 1.1rem; line-height: 1; padding: 2px 7px; @@ -1264,7 +1510,7 @@ font-size: 15px; line-height: 14px; text-align: center; - color: #8ef0d1; + color: var(--accent-light); /* Outlined both ways: the basemap under a link can be near-white farmland or near-black water. */ text-shadow: @@ -1277,16 +1523,16 @@ margin-top: 8px; width: 100%; padding: 6px 8px; - border-radius: 8px; - border: 1px solid rgba(225, 29, 72, 0.5); - background: rgba(225, 29, 72, 0.16); - color: #ff8aa6; + border-radius: var(--radius); + border: 1px solid color-mix(in srgb, var(--danger) 50%, transparent); + background: color-mix(in srgb, var(--danger) 16%, transparent); + color: var(--danger-text); font: inherit; font-size: 0.8rem; cursor: pointer; } .link-remove-btn:hover { - background: rgba(225, 29, 72, 0.28); + background: color-mix(in srgb, var(--danger) 28%, transparent); } /* Deep-link auto-discovery splash (#26): a visitor arriving via a @@ -1319,7 +1565,7 @@ position: fixed; inset: 0; z-index: 5100; /* above #loading-overlay (5000) */ - background: rgba(7, 17, 24, 0.62); + background: var(--scrim); backdrop-filter: blur(3px); padding: 16px; } @@ -1334,9 +1580,9 @@ width: min(440px, 92vw); max-height: 85vh; border-radius: 20px; - background: rgba(7, 25, 35, 0.97); - border: 1px solid var(--panel-border); - box-shadow: var(--shadow); + background: var(--menu-bg); + border: 1px solid var(--border); + box-shadow: var(--surface-shadow); } .intro-head { padding: 20px 20px 0; @@ -1349,7 +1595,7 @@ .intro-lede { margin: 0; font-size: 0.78rem; - color: var(--muted); + color: var(--text-muted); line-height: 1.45; } .intro-body { @@ -1376,7 +1622,7 @@ width: 20px; height: 20px; border-radius: 50%; - background: rgba(0, 184, 148, 0.16); + background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); font-size: 0.7rem; font-weight: 600; @@ -1384,7 +1630,7 @@ .intro-detail { margin: 0 0 8px 29px; font-size: 0.75rem; - color: var(--muted); + color: var(--text-muted); line-height: 1.5; } .intro-detail code { @@ -1399,12 +1645,12 @@ .intro-sub { margin: 0 0 6px 29px; padding-left: 9px; - border-left: 2px solid rgba(0, 184, 148, 0.3); + border-left: 2px solid color-mix(in srgb, var(--accent) 30%, transparent); } .intro-sub p { margin: 0; font-size: 0.75rem; - color: var(--muted); + color: var(--text-muted); line-height: 1.5; } .intro-sub p.intro-sub-title { @@ -1417,17 +1663,17 @@ gap: 10px; padding: 12px 20px 16px; margin-top: 4px; - border-top: 1px solid var(--panel-border); + border-top: 1px solid var(--border); } .intro-hint, .intro-loading { flex: 1; min-width: 0; font-size: 0.7rem; - color: var(--muted); + color: var(--text-muted); } .intro-hint strong { - color: var(--muted); + color: var(--text-muted); font-weight: 600; } .intro-loading { @@ -1448,7 +1694,7 @@ .intro-foot button { flex: none; margin: 0; - border-radius: 10px; + border-radius: var(--radius); padding: 7px 15px; font: inherit; font-weight: 600; @@ -1460,9 +1706,9 @@ width: min(260px, 80%); padding: 20px; border-radius: 20px; - background: rgba(7, 25, 35, 0.96); - border: 1px solid rgba(143, 187, 201, 0.18); - box-shadow: var(--shadow); + background: var(--menu-bg); + border: 1px solid var(--border); + box-shadow: var(--surface-shadow); text-align: center; } .loading-spinner { @@ -1470,7 +1716,7 @@ height: 28px; margin: 0 auto 12px; border-radius: 50%; - border: 3px solid rgba(0, 184, 148, 0.2); + border: 3px solid color-mix(in srgb, var(--accent) 20%, transparent); border-top-color: var(--accent); animation: deep-link-spin 0.8s linear infinite; } @@ -1487,7 +1733,7 @@ margin-top: 6px; font-size: 0.72rem; font-weight: 400; - color: var(--muted); + color: var(--text-muted); } @@ -1505,7 +1751,7 @@ align-items: baseline; gap: 8px; font-size: 0.74rem; - color: var(--muted); + color: var(--text-muted); margin-bottom: 6px; } @@ -1516,7 +1762,7 @@ .terrain-control input[type="range"] { width: 100%; - accent-color: #9be564; + accent-color: var(--accent); margin: 0; } @@ -1524,7 +1770,7 @@ margin: 7px 0 0; font-size: 0.66rem; line-height: 1.35; - color: var(--muted); + color: var(--text-muted); } @media (max-width: 860px) { @@ -1659,8 +1905,8 @@ +
-
DMC MeshCore Triangulator @@ -1682,6 +1928,14 @@ MC-Radar ↗
+
+ 🌙 + +