A field can be a place, and a question can be about one

`type: location` puts a pin on a map and stores {lat, lon, zoom}; a
record that holds one renders as a still, on a desk and in any
resource card. `place:` on a question is the counterpart to `event:`:
event makes a page an occasion with a time, place makes it somewhere,
shows the map under the question, and opens any location field on the
page there instead of on the globe.

Every tile and every still is fetched by portal and served from this
origin (src/maps.rs, MapLibre vendored in public/), so a page with a
map on it still talks to nobody but us: no third-party request, no
consent banner, and the token never leaves the server. MAPBOX_TOKEN
unset turns the whole thing off, the way GARAGE_* does for uploads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-24 21:17:28 +02:00
co-authored by Claude Opus 5
parent 9ed8194484
commit 4fd60293fa
10 changed files with 834 additions and 4 deletions
+67 -1
View File
@@ -1120,4 +1120,70 @@ textarea:focus {
flex-direction: column;
align-items: flex-start;
}
}
}
/* A `type: location` field: the map fills the field, and the pin is the
centre of it - panning works the same with a finger, a mouse or the
arrow keys, and there is nothing to miss by a few pixels. MapLibre
draws into .map-canvas after hydration, so the box is reserved up
front the way .gesture-wrap reserves its own. */
.map-wrap {
position: relative;
width: 100%;
}
.map-wrap:empty {
aspect-ratio: 3 / 2;
}
.map-canvas {
display: block;
width: 100%;
aspect-ratio: 3 / 2;
border: 0.06rem solid var(--line);
border-radius: 0.2rem;
overflow: hidden;
}
/* The pin sits over the exact centre of the canvas and takes no
pointer events, so every drag goes to the map under it. Its point is
the bottom tip, hence the upward shift of its own height. */
.map-pin {
position: absolute;
top: 50%;
left: 50%;
width: 0.9rem;
height: 0.9rem;
margin: -1.35rem 0 0 -0.45rem;
border: 0.18rem solid var(--ink);
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
pointer-events: none;
background: var(--paper);
}
.map-readout {
margin: 0.3rem 0 0;
font-size: 0.85em;
color: var(--ink-dim);
}
/* A stored location in a list: a still image from this origin, never a
live map - twenty rows of WebGL would cost more than the page. */
.map-still {
display: block;
width: 100%;
max-width: 24rem;
height: auto;
border: 0.06rem solid var(--line);
border-radius: 0.2rem;
}
.map-coords {
display: block;
margin-top: 0.2rem;
font-size: 0.85em;
}
.answer-field-map .answer-field-value {
display: block;
}