1{{/*2 legend — the whole sidebar: the search field, the toolbar, the collection3 tree, and the footer count.45 It is one region because it is one answer. Every filtering move changes the6 rows, the isolate chip and the count together, and splitting them into three7 regions would be three ways for them to disagree — which is exactly the8 defect the reference implementation carried until every surface was made to9 ask one place.1011 All of its algebra ran in Go before this file was given anything12 (internal/app/legend.go). There is no arithmetic here and no policy: a13 template that needs an `if` about what a collection means is a decision in14 the wrong place.15*/}}16{{define "legend"}}<aside id="atlas-legend" class="sidebar">17 <div class="search-wrap">18 <svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>19 <input id="pin-search" type="search" name="q" value="{{.Legend.Search}}"20 placeholder="Search locations" autocomplete="off" aria-label="Search locations"21 hx-post="/session/search" hx-trigger="input changed delay:150ms, search">22 <kbd>⌘K</kbd>23 </div>24 <div class="legend-toolbar">25 <span>Layers</span>26 <button type="button" class="solo-chip" id="solo-chip"{{if not .Legend.Solo}} hidden{{end}}27 title="Showing exclusively {{.Legend.Solo}} — click to show everything"28 hx-post="/session/solo">exclusively: {{.Legend.Solo}}</button>29 <div class="legend-actions">30 <button type="button" class="legend-action" id="expand-all"31 data-label="Unfold every group" aria-label="Unfold every group"32 hx-post="/session/sections" hx-vals:append='{"all":"unfold"}'>33 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>34 </button>35 <button type="button" class="legend-action" id="collapse-all"36 data-label="Fold every group" aria-label="Fold every group"37 hx-post="/session/sections" hx-vals:append='{"all":"fold"}'>38 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 10 4-4 4 4"/></svg>39 </button>40 <span class="legend-actions-break" aria-hidden="true"></span>41 <button type="button" class="legend-action" id="show-all"42 data-label="Show every category" aria-label="Show every category"43 hx-post="/session/collections" hx-vals:append='{"all":"show"}'>44 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M1 8s2.5-4.5 7-4.5S15 8 15 8s-2.5 4.5-7 4.5S1 8 1 8z"/><circle cx="8" cy="8" r="2"/></svg>45 </button>46 <button type="button" class="legend-action" id="hide-all"47 data-label="Hide every category" aria-label="Hide every category"48 hx-post="/session/collections" hx-vals:append='{"all":"hide"}'>49 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M1 8s2.5-4.5 7-4.5S15 8 15 8s-2.5 4.5-7 4.5S1 8 1 8z"/><path d="m2 14 12-12"/></svg>50 </button>51 </div>52 </div>53 <div class="layers" id="layers">54 <div class="legend" id="legend">55 {{range .Legend.Sections}}<section class="layer-section{{if .Collapsed}} is-collapsed{{end}}" data-layer-section="{{.Key}}">56 <div class="layer-header">57 <button type="button" class="layer-title" data-section="{{.Key}}"58 aria-expanded="{{if .Collapsed}}false{{else}}true{{end}}"59 hx-post="/session/sections"60 hx-vals:append='{"section":"{{.Key}}","open":"{{if .Collapsed}}1{{else}}0{{end}}"}'>61 <span class="layer-chevron" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m9 6 6 6-6 6"/></svg></span>62 <span>{{.Title}}</span>63 <span class="layer-count">{{.Count}}</span>64 </button>65 <button type="button" class="only-button" data-only-section="{{.Key}}"66 data-label="Exclusively {{.Title}}" aria-label="Exclusively {{.Title}}"67 hx-post="/session/solo" hx-vals:append='{"section":"{{.Key}}"}'>68 <svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="4.6"/><circle cx="8" cy="8" r="1.3" fill="currentColor" stroke="none"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg>69 </button>70 <label class="layer-switch">71 <input type="checkbox" data-section-toggle="{{.Key}}" aria-label="Show {{.Title}}"72 {{if .Shown}}checked{{end}}73 hx-post="/session/collections" hx-vals:append='{"section":"{{.Key}}"}'>74 <span aria-hidden="true"></span>75 </label>76 </div>77 <div class="category-toggles">78 {{range .Rows}}<label class="category-row{{if .Expanded}} is-expanded{{end}}" style="--pin-color: {{.Color}}">79 <input type="checkbox" data-collection="{{.ID}}"{{if .Visible}} checked{{end}}80 hx-post="/session/collections"81 hx-vals:append='{"collection":"{{.ID}}","visible":"{{if .Visible}}0{{else}}1{{end}}"}'>82 {{if .Shapes}}<button type="button" class="collection-expand" data-expand-collection="{{.ID}}"83 aria-expanded="{{.Expanded}}" aria-label="Unfold the {{.Title}} index"84 hx-post="/session/expand"85 hx-vals:append='{"collection":"{{.ID}}","open":"{{if .Expanded}}0{{else}}1{{end}}"}'>86 <svg class="collection-chevron" viewBox="0 0 16 16" aria-hidden="true"><path d="m6 4 4 4-4 4"/></svg>87 {{if eq .Kind "path"}}<svg class="collection-kind" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 12.5c3-6 8-2 11-9"/></svg>88 {{else}}<svg class="collection-kind collection-kind-area" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 4.2 12.6 11.6H3.4z"/></svg>{{end}}89 </button>90 {{else}}{{/*91 THE MARK A ROW WEARS is the collection's own artwork wherever92 there is any, and its initials only where there is not -- never93 both. The reference set `--pin-icon` and the `has-source-icon`94 class on this cell and emptied its text, or wrote the initials95 into it (frontend/src/theme.js, applyCategoryGlyph); the rule96 that draws one from the other came over with the stylesheet97 (category-rows.css). Which of the two arrived is Go's answer98 (legend.go, IconURL) -- this only spells it.99 */}}<span class="category-icon{{if .IconURL}} has-source-icon{{end}}"{{with .IconURL}} style="--pin-icon: url('{{.}}')"{{end}}{{if .Icon}} title="{{.Icon}}"{{else}} title="{{.Title}}"{{end}}>{{if not .IconURL}}{{.Glyph}}{{end}}</span>100 {{end}}101 <span class="category-name">{{.Title}}</span>102 {{with .Labels}}<button type="button" class="label-toggle" data-label-toggle="{{.Collection}}"103 data-label="{{.Action}}" aria-label="{{.Label}}" aria-pressed="{{.Speaking}}"104 hx-post="/session/labels" hx-vals:append='{"collection":"{{.Collection}}","flip":"1"}'>105 {{/*106 The mark says which question the button is asking. A bar and a107 stem for ground that draws its names on itself; a luggage tag108 for a point collection curated as text, whose names are the109 whole of how it draws. Which one is Go's answer (legend.go,110 labelToggle) -- this only spells them.111 */}}112 {{if eq .Glyph "tag"}}<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8.6 2.5H2.5v6.1l5.4 5.4a1.2 1.2 0 0 0 1.7 0l4.4-4.4a1.2 1.2 0 0 0 0-1.7z"/><path d="M5.6 5.6h.01"/></svg>113 {{else}}<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 3.5h9M8 3.5v9"/></svg>114 {{end}}115 </button>116 {{else}}<span class="label-toggle-spacer"></span>117 {{end}}118 <button type="button" class="only-button" data-only-collection="{{.ID}}"119 data-label="Exclusively {{.Title}}" aria-label="Exclusively {{.Title}}"120 hx-post="/session/solo" hx-vals:append='{"collection":"{{.ID}}"}'>121 <svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="4.6"/><circle cx="8" cy="8" r="1.3" fill="currentColor" stroke="none"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg>122 </button>123 <span class="category-count">{{.Count}}</span>124 </label>125 {{if .Shapes}}<div class="feature-index" data-feature-index="{{.ID}}"{{if not .Expanded}} hidden{{end}}>126 {{/*127 TWO VERBS, TWO BUTTONS OF THE MOUSE. A left click on a row of a128 feature index *goes* to that ground: it selects the feature,129 opens its card and flies the camera, which is the way a reader130 reaches a place they cannot see. The right button asks after the131 ground instead — highlight it, and narrow every other surface to132 what it holds.133134 They are two elements because one element carries one request.135 The row is the button and answers the click; the wrapper around136 it hears the contextmenu the click bubbles past and answers that.137 `prevent` is what keeps the platform's own menu out of the way.138139 AND A THIRD VERB, which is the one a right click cannot spell:140 *only this ground*. Highlights accumulate, so asking after one141 zone alone meant clearing the set by hand or isolating the whole142 collection -- a different question, and a heavier one. The143 crosshair is the same control a collection row and a section head144 wear, asking the same thing one row further in, and it carries145 the state it would leave: an exclusive zone's is pressed, and146 pressing it again gives every highlight back.147148 IT IS A SIBLING OF THE ROW, NOT A CHILD OF IT, and that is the149 whole of how the two clicks stay apart -- an event travels to its150 ancestors and never to its siblings, so this one reaches the151 wrapper, which listens for a contextmenu and hears no click. The152 collection's own only-button needs no modifier either, for the153 adjacent reason: a button is interactive content, so a label does154 not forward its activation.155 */}}156 {{range .Index}}<div class="zone-index-row"157 hx-post="/session/highlight" hx-trigger="contextmenu prevent"158 hx-vals:append='{"feature":"{{.ID}}"}'>159 <button type="button" class="zone-index-item{{if .Highlighted}} is-highlighted{{end}}{{if .Current}} is-current{{end}}"160 data-zone="{{.ID}}" aria-pressed="{{.Highlighted}}"161 style="--zone-color: {{.Color}}; --zone-depth: {{.Depth}}"162 hx-post="/session/select" hx-trigger="click"163 hx-vals:append='{"feature":"{{.ID}}","focus":"1"}'>164 <span class="zone-index-marker" aria-hidden="true"></span>165 <span>{{.Title}}</span>166 </button>167 {{/*168 THE MARK IS `data-zone-only` AND NOT `data-only-zone`, which169 is not a taste: html/template infers an attribute's context170 from its name, strips a `data-` prefix before it does, and171 reads anything left beginning with `on` as an event handler --172 so `data-only-zone` is JS context and its value comes out173 quoted, `"93"`. The two older marks in this file wear174 that quoting today; they are only ever selected on by presence,175 and renaming them would reach outside this change.176 */}}177 <button type="button" class="only-button" data-zone-only="{{.ID}}"178 data-label="Exclusively {{.Title}}" aria-label="Exclusively {{.Title}}"179 aria-pressed="{{.Exclusive}}"180 hx-post="/session/highlight"181 hx-vals:append='{"feature":"{{.ID}}","only":"1"}'>182 <svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="4.6"/><circle cx="8" cy="8" r="1.3" fill="currentColor" stroke="none"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg>183 </button>184 </div>185 {{end}}186 </div>187 {{end}}{{end}}188 </div>189 </section>190 {{end}}191 </div>192 </div>193 <footer class="sidebar-footer">194 <span class="live-dot"></span>195 <span id="visible-count">{{.Legend.Count}}</span>196 </footer>197</aside>198{{end}}