diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index 2762e96..ddb290b 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -1,4 +1,28 @@ [ + { + "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 lived at the bottom of the left column, below all three steps — nearly a thousand pixels under the map they act on, so turning off a layer meant scrolling the map out of sight. They are now a strip along the top of the map. Relief became a button that opens its slider, so the strip stays one line. The help and What's new buttons moved up to the bar at the top of the page. Every step can now be folded shut and shows what it holds when it is (Step 1 · 8 prefixes), and on a phone the observer list folds itself once you have an estimate, so the map is no longer three screens down." + }, + { + "id": "2026-09-10-shorter-observer-list", + "date": "2026-09-10", + "title": "The observer list says what you are choosing on", + "body": "Every row spent three lines on coordinates to five decimals, which is one-metre precision in a list where you pick between repeaters kilometres apart. Rows now show how far each one is from the centre of the cluster, which is the number the automatic pick is actually made on, next to its proven reach. The reason a row is unticked moved up beside the name as a short tag. Coordinates are still in the node's popup on the map. A row went from 60 to 42 pixels, so the whole column is about 350 pixels shorter. The cluster summary was one paragraph of six caveats; it is now six lines with the count in front, and the two that are a guess rather than a measurement are marked." + }, + { + "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 was tucked into a fold above the prefix field, so the exception route opened the panel. It is now one of two buttons at the top of Step 1: type prefixes, or pick an incident. Loading one fills the prefix field and switches you back to it, with a line saying which incident the values came from, so you can still change them before searching. The second-hop field folds away until you ask for it, together with its own km setting, because most searches never use it. A shared link that carries second-hop prefixes opens that fold for you." + }, + { + "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 within a pixel of the same size, and the explanation under a field was actually brighter than the label above it. Field names now lead, their explanation sits quietly underneath and stays there after you type, and what the tool says back gets its own monospaced line with a green rule beside it. The link to the spam detector was rendering in the browser's default blue on a dark panel; it is readable now. The prompt under the search button no longer repeats the sentence directly above it." + }, { "id": "2026-09-10-add-prefix-adds-one-observer", "date": "2026-09-10", diff --git a/web-standalone/index.html b/web-standalone/index.html index 47676a6..c3a41e0 100644 --- a/web-standalone/index.html +++ b/web-standalone/index.html @@ -22,6 +22,9 @@ --panel-border: rgba(143, 187, 201, 0.18); --text: #f3fbff; --muted: #9eb6c0; + /* Help text sits a step below --muted so a field's label always reads + louder than the sentence explaining it (#132). */ + --faint: #6b8794; --accent: #00b894; --accent-2: #ff9f43; --shadow: 0 18px 50px rgba(0, 0, 0, 0.28); @@ -141,8 +144,41 @@ .topbar-dropdown-menu a { font-size: 0.82rem; } - .topbar-github { + .topbar-meta { margin-left: auto; + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + } + .topbar-meta .is-icon { + width: 28px; + height: 28px; + padding: 0; + display: inline-flex; + align-items: center; + 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); + cursor: pointer; + } + .topbar-meta .is-icon:hover { + background: rgba(255, 255, 255, 0.09); + color: var(--text); + } + /* The panel hangs off the topbar now, so it opens downward from the right + edge rather than from a card in the column. */ + .topbar-meta .whats-new-panel { + top: calc(100% + 10px); + left: auto; + right: 0; + } + + .topbar-github { flex: 0 0 auto; border-radius: 10px; padding: 6px 12px; @@ -191,11 +227,42 @@ flex-wrap: wrap; align-items: center; gap: 8px; - padding: 10px 12px; + padding: 8px 10px; border-radius: 14px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); } + .layer-chips-label { + font-size: 0.64rem; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--faint); + margin-right: 2px; + } + /* Share is the one control here that is not about the map, so it sits at + the far end rather than in the run of map buttons. */ + .controls-card .controls-meta { + margin-left: auto; + } + + /* Relief is a slider with a paragraph of explanation; as a chip plus a + popover the strip stays one line (#135). */ + .relief-wrap { + position: relative; + display: inline-block; + } + .relief-wrap .terrain-control { + position: absolute; + top: calc(100% + 8px); + right: 0; + 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); + } .controls-card button { margin: 0; border-radius: 10px; @@ -371,31 +438,64 @@ three controls on one row left the list unreadable. */ /* Spam-detector import folded under one line (#117): the first-hop field is what a first visit needs, the import is the shortcut. */ - .import-details { - margin-bottom: 12px; + /* Two ways into Step 1, as a segmented control (#133). */ + .entry-tabs { + display: grid; + 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); + margin-bottom: 14px; } - .import-details summary { - cursor: pointer; + /* 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; + 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); - font-size: 0.84rem; - padding: 8px 0; - list-style: none; + box-shadow: none; } - .import-details summary::-webkit-details-marker { - display: none; + .step-panel .entry-tabs .entry-tab[aria-pressed="true"] { + background: rgba(0, 184, 148, 0.18); + color: var(--text); + font-weight: 600; + box-shadow: inset 0 0 0 1px rgba(0, 184, 148, 0.35); } - .import-details summary::before { - content: "▸"; - display: inline-block; - width: 1em; - transition: transform 0.15s ease; + .step-panel .entry-tabs .entry-tab:hover { + color: var(--text); + filter: none; } - .import-details[open] summary::before { - transform: rotate(90deg); + + /* 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); + padding-top: 12px; } - .import-details[open] summary { - margin-bottom: 8px; + .hop2-details summary { + cursor: pointer; + list-style: none; + color: var(--muted); + font-size: 0.8rem; + } + .hop2-details summary::-webkit-details-marker { display: none; } + .hop2-details summary::before { + content: "+ "; + color: var(--accent); + font-weight: 600; } + .hop2-details[open] summary::before { content: "– "; } + .hop2-details[open] summary { margin-bottom: 12px; } + .hop2-details summary:hover { color: var(--text); } + .step-panel select.import-select { width: 100%; border-radius: 12px; @@ -415,11 +515,72 @@ } .step-panel .import-row button { margin-top: 0; } /* Tighter help text to keep the step panels compact. */ + /* Three roles under a field, and they have to look like three things + (#132). Before this they were all one colour within a pixel of each + other, and p.hint inherited --text at .85 opacity, which rendered + BRIGHTER than the --muted label above it: the explanation outshouted + the field it explained. + + .field-help static explanation. Never overwritten, so it is still + there after you type. Quietest of the three. + p.hint what the app says back. Monospaced with an accent rule so + it cannot be mistaken for the next field's label, which is + exactly what happened when "Parsed observers: ..." replaced + the help text and sat flush against the label below. */ + .step-panel p.field-help { + margin: 5px 0 0; + font-size: 0.75rem; + line-height: 1.45; + color: var(--faint); + } + .step-panel p.hint:empty { + display: none; + } .step-panel p.hint { margin: 6px 0 0; - font-size: 0.78rem; - line-height: 1.4; - opacity: 0.85; + font-size: 0.72rem; + line-height: 1.45; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + color: var(--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); + } + /* A field group needs air before the next one starts, or the reply line + still reads as the next label's opening words (#132). */ + .step-panel p + label, + .step-panel p + .radius-row { + margin-top: 14px; + } + .step-panel .label-optional { + color: var(--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-size: 0.92em; + background: rgba(255, 255, 255, 0.06); + border-radius: 4px; + padding: 1px 5px; + color: var(--muted); + } + /* Nothing styled links inside the panels, so the browser default #0000EE + landed on a dark teal card (#132). */ + .step-panel a, + .utility-panel a { + color: var(--accent); + text-underline-offset: 2px; + text-decoration-color: rgba(0, 184, 148, 0.45); + } + .step-panel a:hover, + .utility-panel a:hover { + text-decoration-color: var(--accent); } .dot { @@ -467,13 +628,69 @@ pointer-events: none; } + /* The head is the control that folds the panel (#135). Step 1 already + collapsed on its own when a cluster locked, but there was no way to open + it again and nothing said what it held. */ + /* `.step-panel button` (0,1,1) paints every button in the panel with the + accent fill, so the head has to outspecify it or it renders as a big + green primary action. */ + .step-panel .step-head { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + grid-template-areas: + "kicker chevron" + "title chevron"; + align-items: center; + gap: 0 10px; + width: 100%; + margin: 0; + padding: 0; + border: 0; + background: none; + text-align: left; + color: inherit; + font: inherit; + cursor: pointer; + } + .step-panel .step-head .step-kicker { grid-area: kicker; } + .step-panel .step-head h3 { grid-area: title; } + .step-panel .step-head .step-chev { + grid-area: chevron; + color: var(--faint); + font-size: 0.7rem; + transition: transform 0.15s ease; + } + .step-panel .step-head .step-chev::before { content: "▾"; } + @media (prefers-reduced-motion: reduce) { + .step-panel .step-head .step-chev { transition: none; } + } + .step-panel .step-head:hover .step-chev { color: var(--text); } + /* The summary only earns its place once the panel is folded; open, the + panel itself is the summary. */ + .step-summary { + grid-area: title; + justify-self: end; + align-self: center; + font-size: 0.74rem; + color: var(--accent); + font-variant-numeric: tabular-nums; + white-space: nowrap; + display: none; + } + .step-panel.is-collapsed .step-body { display: none; } + .step-panel.is-collapsed .step-chev { transform: rotate(-90deg); } + .step-panel.is-collapsed .step-summary:not(:empty) { display: block; } + .step-panel.is-collapsed h3 { margin-bottom: 0; } .step-panel.is-collapsed { padding-bottom: 12px; } + /* Nothing to fold before a step is reachable. */ + .step-panel.is-locked .step-head { cursor: default; } + .step-panel.is-locked .step-chev { visibility: hidden; } .step-kicker { display: inline-block; @@ -574,9 +791,10 @@ .step-panel label, .utility-panel label { display: block; - color: var(--muted); - font-size: 0.84rem; - margin-bottom: 6px; + color: var(--text); + font-weight: 600; + font-size: 0.845rem; + margin-bottom: 5px; } .source-panel input, @@ -826,7 +1044,7 @@ .observer-row { display: grid; - grid-template-columns: auto minmax(0, 1fr) 64px; + grid-template-columns: auto minmax(0, 1fr) 58px; gap: 8px; align-items: center; padding: 5px 8px; @@ -835,6 +1053,26 @@ border: 1px solid rgba(255, 255, 255, 0.07); } + .cluster-status .cluster-headline { + margin: 0 0 10px; + font-size: 0.845rem; + line-height: 1.45; + color: var(--text); + } + /* The count leads each line so the list can be scanned down its left edge + instead of read as prose (#134). */ + .cluster-status p.hint { + display: flex; + gap: 8px; + align-items: baseline; + } + .cluster-status p.hint b { + flex: 0 0 auto; + color: var(--text); + font-weight: 600; + font-variant-numeric: tabular-nums; + } + .observer-copy { min-width: 0; } @@ -856,8 +1094,16 @@ } .observer-copy strong { + display: flex; + align-items: center; + gap: 6px; font-size: 0.8rem; - line-height: 1.2; + line-height: 1.25; + min-width: 0; + } + /* The name gives way to the tag instead of pushing it out of the row. */ + .observer-name { + min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -866,19 +1112,24 @@ .observer-copy span { color: var(--muted); font-size: 0.72rem; - line-height: 1.25; + line-height: 1.3; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .observer-reason { - display: inline-block; - margin-top: 1px; + flex: 0 0 auto; padding: 0 6px; border-radius: 999px; - font-size: 0.66rem; + font-size: 0.62rem; 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); + cursor: help; } .observer-reason.is-range { @@ -903,7 +1154,10 @@ .observer-support[open] > summary::before { content: "▾ "; } .observer-support[open] { display: grid; gap: 4px; } - .observer-weight { + /* `.step-panel input` (0,1,1) was outspecifying this (0,1,0), so the field + kept the panel's 12px padding and stood 48px tall — it, not the text, + 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); @@ -1245,17 +1499,6 @@ It lives in the controls card next to the 3D toggle rather than floating over the map. It is a setting for that view, so it belongs with the control that opens it, and the map is for the map. */ - .terrain-control { - /* The controls card is flex-wrap; this is how its other full-width rows - claim a line of their own (see .share-status). */ - flex: 1 0 100%; - margin-top: 2px; - padding: 9px 11px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.12); - } - .terrain-control label { display: flex; justify-content: space-between; @@ -1438,44 +1681,97 @@ Mesh-Hunter ↗ MC-Radar ↗ - GitHub ↗ +
+ +
+ + +
+ GitHub ↗ +
- Step 1 -

Clues + Regions

-
-
- Load a spam-detector incident instead + +
+ +
+ + +
+ +
- - -

`PREFIX` or `PREFIX:count` — 2/4/6 hex, comma-separated. Count = times heard (weight).

- - -

Weaker, wider clue. Down-weighted automatically.

-
- - +

+
+ +
+ + +

+ Repeaters that heard the source directly. 2C or + 2C:4, comma separated — 2, 4 or 6 hex, and the number is + how often it was heard. +

+

+ + +
+ Add second-hop prefixes + + +

Heard a first-hop relay instead. Weaker and wider, so it counts for less.

+
+ +
+
+ +
+ +
+ +

Resolves the prefixes globally and picks the strongest geographic cluster.

+

- -

Resolves the prefixes globally and picks the strongest geographic cluster.

- Step 2 -

Choose Cluster

-

Candidate clusters will appear here after discovery.

+ +
+
Candidate clusters will appear here after discovery.
@@ -1484,56 +1780,63 @@

Choose Cluster

+
- Step 3 -

Unknown Node Estimate

- - -

Run a weighted observer estimate first. Terrain and LOS are an optional refinement after that.

-
-
- - - - +
+ +
+ + + +
+ +
+ + + +