1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Twin towns of the world</title>
<link rel="stylesheet" href="/static/style.css">
<!-- Applied before the first paint, and therefore inline and in <head>:
     map.js is loaded at the end of <body>, so setting the theme there would
     show a frame of the wrong one. A saved "light" would flash dark first,
     which is exactly the flicker the preference exists to avoid.
     No stored choice leaves color-scheme alone, i.e. following the OS. -->
<script>
(function () {
    try {
        var t = (JSON.parse(localStorage.getItem("twin-towns.view.v1")) || {}).theme;
        if (t === "light" || t === "dark") document.documentElement.style.colorScheme = t;
    } catch (e) { /* storage disabled or corrupt: the OS setting is a fine answer */ }
})();
</script>
</head>
<body>
<div class="layout">
  <div id="mapwrap"><svg id="map"></svg></div>
  <aside>
    <div class="head">
      <h1>Twin towns of the world</h1>
      <!-- Revealed by map.js; see #theme in style.css for why it starts
           hidden. The label and title are set from the current state. -->
      <button type="button" id="theme" hidden></button>
    </div>
    <div class="sub">Sister cities, from Wikidata P190</div>

    <div class="search">
      <input type="search" id="q" placeholder="Search a town…"
             autocomplete="off" spellcheck="false" aria-label="search for a town">
      <ul id="results" hidden></ul>
    </div>

    <div class="timeline">
      <label>Twinned <span id="yearlabel">…</span></label>
      <div id="hist"></div>
      <!-- Two overlaid range inputs make the dual-handle slider: there is no
           native two-handle control, and this keeps both handles keyboard
           reachable. min/max/value are set from the data by buildHistogram,
           so no stale bound flashes before it loads. -->
      <div class="dual">
        <div id="yearfill"></div>
        <input type="range" id="yearfrom" aria-label="earliest year">
        <input type="range" id="yearto" aria-label="latest year">
      </div>
      <div class="axis"><span id="axismin"></span><span id="axismax"></span></div>
      <div id="histmeta" class="meta"></div>
      <label class="chk"><input type="checkbox" id="undated" checked> show undated</label>
    </div>

    <div id="info">
      <p class="hint">Loading…</p>
    </div>
    <footer>
      {{ .Plottable }} places mapped ({{ .Places }} total) ·
      {{ .Pairs }} twinnings, {{ .Dated }} dated{{ if .Ended }},
      {{ .Ended }} dissolved{{ end }}<br>
      {{ if .FirstYear }}Dated {{ .FirstYear }}–{{ .LastYear }}<br>{{ end }}
      {{ with .IngestedAt }}Data fetched {{ . }}<br>{{ end }}
      Source: <a href="https://www.wikidata.org/wiki/Property:P190">Wikidata P190</a>,
      CC0. <kbd>Esc</kbd> clears.
    </footer>
  </aside>
</div>
<script src="/static/map.js"></script>
</body>
</html>