rs_maps/frontend/style.css
2026-08-03 13:41:35 +01:00

571 lines
17 KiB
CSS

/* frontend/style.css */
/* ─────────────────────────────────────────────────────────────
rs_maps — chrome for a map, not a dashboard.
Palette taken from Ordnance Survey sheet conventions:
rights-of-way green, road-fill magenta, contour bistre.
───────────────────────────────────────────────────────────── */
:root {
--ink: #0F1A17; /* deep bottle-green black */
--panel: #16231F;
--panel-2: #1E2E29;
--hair: #2C433C; /* hairline rules */
--paper: #E7EDE7; /* cool pale sage */
--paper-dim: #9DB0A8;
--moss: #4E9C6B; /* rights of way — primary action */
--moss-lift: #5FB47D;
--accent: #D2467F; /* road fill — shared / accent */
--sun: #E2A93C; /* live location, warnings */
--alarm: #D3574B;
--r: 3px; /* map furniture is squared off, not pill-shaped */
--shadow: 0 8px 28px rgba(0,0,0,.38);
--sans: ui-sans-serif, "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
--mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", "Consolas", monospace;
}
* { box-sizing: border-box; }
html, body {
margin: 0; height: 100%;
font-family: var(--sans);
background: var(--ink);
color: var(--paper);
-webkit-text-size-adjust: 100%;
}
[hidden] { display: none !important; }
:focus-visible {
outline: 2px solid var(--sun);
outline-offset: 2px;
}
/* ── trig point: a small pyramid glyph, the app's only ornament ── */
.trig {
display: block;
width: 34px; height: 30px;
background: var(--moss);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
margin: 0 auto 14px;
}
.trig.small { width: 20px; height: 17px; margin: 0 4px 0 0; flex: none; }
/* ───────────────────────── auth ───────────────────────── */
.auth {
min-height: 100%;
display: grid;
place-items: center;
padding: 24px;
/* faint contour rings, drawn not imported */
background:
radial-gradient(circle at 22% 30%, transparent 0 78px, rgba(78,156,107,.10) 78px 79px, transparent 79px),
radial-gradient(circle at 22% 30%, transparent 0 128px, rgba(78,156,107,.08) 128px 129px, transparent 129px),
radial-gradient(circle at 22% 30%, transparent 0 186px, rgba(78,156,107,.06) 186px 187px, transparent 187px),
radial-gradient(circle at 80% 74%, transparent 0 96px, rgba(210,70,127,.09) 96px 97px, transparent 97px),
radial-gradient(circle at 80% 74%, transparent 0 154px, rgba(210,70,127,.06) 154px 155px, transparent 155px),
var(--ink);
}
.auth-card {
width: min(420px, 100%);
background: var(--panel);
border: 1px solid var(--hair);
border-radius: var(--r);
padding: 34px 30px 30px;
box-shadow: var(--shadow);
}
.auth-head { text-align: center; margin-bottom: 26px; }
.auth-head h1 {
margin: 0;
font-size: 30px;
font-weight: 600;
letter-spacing: .16em;
text-transform: uppercase;
}
.auth-sub {
margin: 8px 0 0;
color: var(--paper-dim);
font-size: 13px;
}
/* ───────────────────────── forms ───────────────────────── */
label {
display: block;
margin-bottom: 15px;
font-size: 12px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--paper-dim);
}
label .opt {
text-transform: none;
letter-spacing: 0;
font-size: 11px;
color: var(--paper-dim);
opacity: .8;
}
input[type=text], input[type=password], input[type=email],
input[type=search], input:not([type]), select, textarea {
display: block;
width: 100%;
margin-top: 6px;
padding: 10px 11px;
background: var(--ink);
border: 1px solid var(--hair);
border-radius: var(--r);
color: var(--paper);
font: 15px/1.3 var(--sans);
text-transform: none;
letter-spacing: 0;
}
textarea { resize: vertical; }
input:focus, select:focus, textarea:focus {
border-color: var(--moss);
outline: none;
}
label.check {
display: flex;
align-items: center;
gap: 9px;
text-transform: none;
letter-spacing: 0;
font-size: 14px;
color: var(--paper);
}
label.check input { width: auto; margin: 0; }
button {
font: 500 14px var(--sans);
border-radius: var(--r);
border: 1px solid transparent;
padding: 10px 15px;
cursor: pointer;
color: var(--paper);
background: var(--panel-2);
transition: background .12s ease, border-color .12s ease;
}
button:hover { background: var(--hair); }
button[disabled] { opacity: .55; cursor: progress; }
button.primary { background: var(--moss); color: #07140D; font-weight: 600; }
button.primary:hover { background: var(--moss-lift); }
button.danger { background: transparent; border-color: var(--alarm); color: var(--alarm); }
button.danger:hover { background: rgba(211,87,75,.14); }
button.ghost { background: transparent; border-color: var(--hair); }
button.ghost:hover { background: var(--panel-2); }
button.ghost[aria-pressed=true] { border-color: var(--sun); color: var(--sun); }
button.small { padding: 7px 11px; font-size: 13px; }
button.link {
background: none; border: none; padding: 4px;
color: var(--paper-dim); font-size: 13px;
}
button.link:hover { color: var(--paper); background: none; }
.pane .primary { width: 100%; margin-top: 6px; }
.switch {
display: flex;
justify-content: space-between;
gap: 12px;
margin: 16px 0 0;
font-size: 13px;
}
.switch a { color: var(--paper-dim); text-decoration: none; }
.switch a:hover { color: var(--moss-lift); text-decoration: underline; }
.note { margin: 10px 0 0; font-size: 13px; min-height: 1em; color: var(--paper-dim); }
.note.bad { color: var(--alarm); }
.note.good { color: var(--moss-lift); }
/* ───────────────────────── app shell ───────────────────────── */
.app { position: fixed; inset: 0; }
#map { position: absolute; inset: 0; background: var(--ink); }
.bar {
position: absolute;
top: 0; left: 0; right: 0;
/* Above .rail (500) so the account menu isn't painted behind the
Places/Routes tabs. .bar sets a stacking context, so raising .menu alone
would have no effect — the parent is what has to come up. */
z-index: 700;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: linear-gradient(var(--ink) 55%, rgba(15,26,23,0));
padding-bottom: 22px;
}
/* The bar's gradient extends 22px past its contents. Now that it sits above
the rail, that transparent strip would intercept clicks meant for the tabs
beneath it, so the bar ignores pointer events and its children take them back. */
.bar { pointer-events: none; }
.bar > * { pointer-events: auto; }
.search { display: flex; gap: 6px; flex: 1 1 auto; max-width: 420px; position: relative; }
/* Search results. Nominatim frequently returns many equally-valid matches for
a chain name, so the list is shown rather than silently taking the first. */
.results {
position: absolute;
top: calc(100% + 6px); left: 0; right: 0;
max-height: min(46vh, 380px);
overflow-y: auto;
margin: 0; padding: 5px;
list-style: none;
background: var(--panel);
border: 1px solid var(--hair);
border-radius: var(--r);
box-shadow: var(--shadow);
}
.results li { margin: 0; }
.results button {
display: block; width: 100%;
background: none; border: none; text-align: left;
padding: 8px 10px; border-radius: calc(var(--r) - 2px);
font-size: 13px; line-height: 1.35;
}
.results button:hover, .results button:focus-visible { background: var(--panel-2); }
.results .r-name { display: block; color: var(--paper); }
.results .r-where { display: block; color: var(--paper-dim); font-size: 11.5px; }
.results .r-kind {
display: inline-block; margin-top: 3px;
font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
text-transform: uppercase; color: var(--moss-lift);
}
/* Marker list rows are clickable as a whole; the Edit button sits on top. */
#marker-list li { cursor: pointer; }
.search input { margin-top: 0; background: var(--panel); }
.search button { flex: none; background: var(--panel); border-color: var(--hair); }
.account { position: relative; margin-left: auto; }
.menu {
position: absolute;
right: 0; top: calc(100% + 6px);
min-width: 180px;
background: var(--panel);
border: 1px solid var(--hair);
border-radius: var(--r);
box-shadow: var(--shadow);
padding: 5px;
display: flex;
flex-direction: column;
}
.menu button {
background: none; border: none; text-align: left;
padding: 9px 10px; font-size: 14px;
}
.menu button:hover { background: var(--panel-2); }
/* signature: map-margin grid readout */
.readout {
position: absolute;
left: 12px; bottom: 12px;
z-index: 500;
display: flex;
align-items: baseline;
gap: 8px;
padding: 6px 11px;
background: rgba(15,26,23,.86);
border: 1px solid var(--hair);
border-left: 3px solid var(--moss);
border-radius: var(--r);
font-family: var(--mono);
font-size: 12px;
backdrop-filter: blur(3px);
}
.readout-label { color: var(--paper-dim); font-size: 10px; letter-spacing: .14em; }
.readout-val { color: var(--paper); }
.rail {
position: absolute;
top: 62px; right: 12px;
z-index: 500;
display: flex;
gap: 6px;
}
.tab {
background: var(--panel);
border: 1px solid var(--hair);
font-size: 13px;
padding: 8px 13px;
}
.tab[aria-pressed=true] { border-color: var(--moss); color: var(--moss-lift); }
/* ───────────────────────── drawers & sheets ───────────────────────── */
.drawer {
position: absolute;
top: 104px; right: 12px; bottom: 12px;
width: min(330px, calc(100vw - 24px));
z-index: 500;
display: flex;
flex-direction: column;
gap: 10px;
padding: 14px;
overflow-y: auto;
background: rgba(22,35,31,.96);
border: 1px solid var(--hair);
border-radius: var(--r);
box-shadow: var(--shadow);
backdrop-filter: blur(6px);
}
.drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.drawer-head h2 {
margin: 0; font-size: 13px; font-weight: 600;
letter-spacing: .14em; text-transform: uppercase;
}
.sub {
margin: 6px 0 0; font-size: 11px; font-weight: 600;
letter-spacing: .14em; text-transform: uppercase; color: var(--paper-dim);
}
.hint, .empty { margin: 0; font-size: 13px; color: var(--paper-dim); }
.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.list li {
display: flex;
align-items: center;
gap: 9px;
padding: 9px 4px;
border-bottom: 1px solid var(--hair);
font-size: 14px;
}
.list li:last-child { border-bottom: none; }
.list .dot {
width: 9px; height: 9px; border-radius: 50%;
background: var(--moss); flex: none;
}
.list .grow { flex: 1 1 auto; min-width: 0; }
.list .name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list .meta { display: block; font-family: var(--mono); font-size: 11px; color: var(--paper-dim); }
.badge {
font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
color: var(--accent); border: 1px solid var(--accent);
border-radius: var(--r); padding: 1px 5px; flex: none;
}
.row { display: flex; gap: 8px; flex-wrap: wrap; }
.filebtn {
margin: 0; padding: 10px 15px;
background: var(--panel-2); border: 1px solid var(--hair);
border-radius: var(--r); cursor: pointer;
font-size: 13px; letter-spacing: 0; text-transform: none;
color: var(--paper);
}
.filebtn:hover { background: var(--hair); }
.local-actions {
display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
padding: 9px; border: 1px dashed var(--hair); border-radius: var(--r);
}
.mono { font-family: var(--mono); font-size: 12px; }
.scrim {
position: absolute; inset: 0; z-index: 900;
background: rgba(8,14,12,.55);
}
.sheet {
position: absolute;
z-index: 1000;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: min(430px, calc(100vw - 24px));
max-height: calc(100vh - 48px);
overflow-y: auto;
padding: 18px;
background: var(--panel);
border: 1px solid var(--hair);
border-radius: var(--r);
box-shadow: var(--shadow);
}
.sheet form { margin-top: 14px; }
.block + .block { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--hair); }
.current { margin: 8px 0 14px; font-size: 13px; color: var(--paper-dim); }
.coords {
margin: 0 0 14px; padding: 7px 9px;
background: var(--ink); border-left: 3px solid var(--moss);
border-radius: var(--r); color: var(--paper-dim);
}
.actions { display: flex; gap: 9px; margin-top: 6px; }
.actions .primary { flex: 1 1 auto; }
.swatches { display: flex; gap: 7px; margin-top: 7px; }
.swatches button {
width: 26px; height: 26px; padding: 0;
border-radius: 50%; border: 2px solid transparent;
}
.swatches button[aria-pressed=true] { border-color: var(--paper); }
.toast {
position: absolute;
left: 50%; bottom: 22px;
transform: translateX(-50%);
z-index: 1100;
padding: 10px 16px;
background: var(--panel);
border: 1px solid var(--hair);
border-left: 3px solid var(--moss);
border-radius: var(--r);
font-size: 14px;
box-shadow: var(--shadow);
}
.toast.bad { border-left-color: var(--alarm); }
/* ───────────────────────── leaflet overrides ───────────────────────── */
.leaflet-container { background: var(--ink); font-family: var(--sans); }
.leaflet-control-attribution {
background: rgba(15,26,23,.82) !important;
color: var(--paper-dim) !important;
font-size: 10px !important;
}
.leaflet-control-attribution a { color: var(--paper-dim) !important; }
.leaflet-bar a {
background: var(--panel) !important;
color: var(--paper) !important;
border-bottom-color: var(--hair) !important;
}
.leaflet-bar a:hover { background: var(--panel-2) !important; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
background: var(--panel);
color: var(--paper);
border-radius: var(--r);
}
.leaflet-popup-content { margin: 11px 13px; font-size: 14px; }
.leaflet-popup-content b { display: block; margin-bottom: 3px; }
.leaflet-popup-content .mono { color: var(--paper-dim); }
.pin {
width: 15px; height: 15px;
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
border: 2px solid rgba(0,0,0,.42);
}
.here {
width: 15px; height: 15px; border-radius: 50%;
background: var(--sun);
box-shadow: 0 0 0 5px rgba(226,169,60,.26);
}
/* ───────────────────────── responsive ───────────────────────── */
@media (max-width: 640px) {
.bar { flex-wrap: wrap; }
.search { order: 3; flex-basis: 100%; max-width: none; }
.drawer { top: auto; left: 12px; right: 12px; bottom: 12px; width: auto; max-height: 52vh; }
.readout { display: none; }
.rail { top: 108px; }
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; animation: none !important; }
}
/* ───────────────────── place details in popups ───────────────────── */
.popup-actions {
display: flex; flex-wrap: wrap; gap: 4px 12px;
margin-top: 8px; padding-top: 7px;
border-top: 1px solid var(--hair);
}
.popup-actions .link { padding: 0; font-size: 12px; }
/* Two-column label/value grid. Values are shown as OSM records them — no
interpretation, so what you see is what's actually in the data. */
.details {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 10px;
margin-top: 8px; padding-top: 7px;
border-top: 1px solid var(--hair);
font-size: 12px;
line-height: 1.4;
}
.d-label {
font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
text-transform: uppercase; color: var(--paper-dim);
padding-top: 2px; white-space: nowrap;
}
.d-value { color: var(--paper); overflow-wrap: anywhere; }
.d-value a { color: var(--moss-lift); }
/* opening_hours clauses, one per line. */
.d-hours { display: flex; flex-direction: column; gap: 1px; font-family: var(--mono); font-size: 11px; }
.d-empty {
margin: 8px 0 0; padding-top: 7px;
border-top: 1px solid var(--hair);
font-size: 12px; color: var(--paper-dim);
}
/* ───────────────────────── route drawing ───────────────────────── */
#draw-panel { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
#draw-panel label { display: block; }
.draw-count {
margin: 0;
font-family: var(--mono); font-size: 11px;
letter-spacing: .06em; color: var(--paper-dim);
}
.hint { font-size: 11.5px; color: var(--paper-dim); }
/* Numbered waypoint handles. Distinct from place pins and from the amber search
pin so the three never read as the same kind of thing. */
.wp-icon { background: none; border: none; }
.wp {
width: 22px; height: 22px;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
background: #4FA3D1;
color: #08131b;
font-family: var(--mono); font-size: 11px; font-weight: 600;
border: 2px solid rgba(255,255,255,.85);
box-shadow: 0 1px 4px rgba(0,0,0,.45);
cursor: grab;
}
.wp:active { cursor: grabbing; }