Files
maptool/public/css/style.css
T
randy 24b6180c03
CI / check (push) Successful in 45s
CI / deploy (push) Successful in 14s
Fix overlapping Distance/Area text; label each segment's own length
The measure panel's mode buttons sit inside a maplibregl-ctrl-group
container (for the ruler button's own 29x29 sizing) — its global
"button" rule reaches them too, clipping "Distance"/"Area" into a
fixed 29px square with zero padding instead of sizing to fit the
text. Fixed by upping specificity (".measure-panel .measure-panel__mode")
rather than fighting the cascade with !important.

In distance mode, each segment between two clicked points now shows
its own length at its midpoint, not just the running total in the
panel — a symbol layer keyed off the existing source, filtered apart
from the vertex circles by a new "role" property.

Verified both in a real browser (panel no longer overlaps; two
638m segments rendered and labeled correctly summing to the panel's
1.28km total).
2026-09-07 23:05:54 +02:00

956 lines
15 KiB
CSS

html, body {
height: 100%;
margin: 0;
font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
#app {
height: 100%;
display: flex;
flex-direction: column;
}
.topbar {
flex: 0 0 auto;
display: flex;
align-items: center;
height: 48px;
padding: 0 16px;
background: #fff;
border-bottom: 1px solid #ddd;
}
.topbar__brand {
font-size: 15px;
font-weight: 700;
color: #333;
}
.topbar__auth {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #555;
}
.app-body {
flex: 1;
display: flex;
min-height: 0;
}
.sidebar {
flex: 0 0 52px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 0;
background: #fff;
border-right: 1px solid #ddd;
/* No overflow-y:auto here: setting only one axis to a clipping value
forces the browser to compute the other axis as "auto" too (CSS
Overflow spec), which would clip the flyouts — they intentionally
poke out past the sidebar's own width via left:44px. */
}
.sidebar-item {
position: relative;
}
.sidebar-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
background: none;
color: #444;
cursor: pointer;
}
.sidebar-icon svg {
width: 20px;
height: 20px;
}
.sidebar-icon:hover {
background: #f0f0f0;
}
.sidebar-icon.active {
background: #dbe9ff;
color: #1a5fb4;
}
.sidebar-flyout {
position: absolute;
top: 0;
left: 44px;
z-index: 20;
min-width: max-content;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 6px;
font-size: 13px;
}
.sidebar-flyout[hidden] {
display: none;
}
.sidebar-flyout__option {
display: block;
width: 100%;
padding: 6px 10px;
border: none;
border-radius: 4px;
background: none;
font: inherit;
font-size: 13px;
text-align: left;
white-space: nowrap;
cursor: pointer;
}
.sidebar-flyout__option:hover {
background: #f0f0f0;
}
.sidebar-flyout__option.active {
background: #dbe9ff;
font-weight: 600;
}
#map {
flex: 1;
min-width: 0;
position: relative;
}
.radar-control__playback {
display: flex;
align-items: center;
gap: 6px;
}
.radar-control__play {
border: none;
background: none;
padding: 2px 4px;
font-size: 13px;
cursor: pointer;
}
.radar-control__scrub {
width: 110px;
}
.radar-control__time {
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.basemap-panel {
position: fixed;
top: 0;
left: 52px;
width: 260px;
max-width: 90vw;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
font-size: 13px;
z-index: 10;
}
.basemap-panel[hidden] {
display: none;
}
.basemap-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid #eee;
}
.basemap-panel__close {
border: none;
background: none;
font-size: 18px;
line-height: 1;
padding: 0 4px;
cursor: pointer;
}
.basemap-panel__list {
padding: 6px;
}
.basemap-panel__option {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 10px;
border: none;
border-radius: 4px;
background: none;
font: inherit;
font-size: 14px;
text-align: left;
cursor: pointer;
}
.basemap-panel__option:hover {
background: #f0f0f0;
}
.basemap-panel__option.active {
background: #dbe9ff;
font-weight: 600;
}
.basemap-panel__check {
color: #1a5fb4;
visibility: hidden;
}
.basemap-panel__option.active .basemap-panel__check {
visibility: visible;
}
.gpx-panel {
position: fixed;
top: 0;
left: 52px;
width: 300px;
max-width: 90vw;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
font-size: 13px;
z-index: 10;
}
.gpx-panel[hidden] {
display: none;
}
.gpx-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid #eee;
}
.gpx-panel__actions {
display: flex;
align-items: center;
gap: 2px;
}
.gpx-panel__action {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
background: none;
color: #444;
cursor: pointer;
}
.gpx-panel__action svg {
width: 18px;
height: 18px;
}
.gpx-panel__action:hover {
background: #f0f0f0;
}
.gpx-panel__close {
border: none;
background: none;
font-size: 18px;
line-height: 1;
padding: 0 4px;
cursor: pointer;
}
.gpx-upload__status {
margin: 0 12px 10px;
color: #666;
}
.gpx-tree {
/* flex:1 (not just overflow-y) so the tree fills the rest of the panel
even when short/empty — otherwise the blank space below it wouldn't
be part of the drop target and dropping a file there would silently
do nothing. */
flex: 1;
overflow-y: auto;
padding: 8px 12px;
}
.gpx-tree ul {
list-style: none;
margin: 0;
padding-left: 16px;
}
.gpx-tree > ul {
padding-left: 0;
}
.gpx-tree summary {
cursor: pointer;
font-weight: 600;
border-radius: 3px;
}
.gpx-tree__folder-row {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 4px;
width: calc(100% - 18px);
}
.gpx-tree summary.gpx-drop-hover {
background: #eef5fd;
outline: 1px dashed #4a90d9;
}
.gpx-tree__file {
display: flex;
align-items: center;
gap: 4px;
padding: 2px 0;
cursor: grab;
}
.gpx-tree__file label {
display: flex;
align-items: center;
gap: 4px;
flex: 1;
min-width: 0;
cursor: pointer;
}
.gpx-tree__file-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: inherit;
text-decoration: none;
}
.gpx-tree__file-name:hover {
text-decoration: underline;
}
.gpx-tree__kind-icon {
flex-shrink: 0;
display: flex;
color: #999;
}
.gpx-tree__kind-icon svg {
width: 14px;
height: 14px;
}
.gpx-tree__download {
flex-shrink: 0;
display: flex;
color: #999;
padding: 0 4px;
}
.gpx-tree__download svg {
width: 14px;
height: 14px;
}
.gpx-tree__download:hover {
color: #333;
}
.gpx-tree__delete {
flex-shrink: 0;
border: none;
background: none;
color: #999;
font-size: 14px;
line-height: 1;
padding: 0 4px;
cursor: pointer;
}
.gpx-tree__delete:hover {
color: #e63946;
}
.gpx-tree__empty {
color: #888;
font-style: italic;
}
.gpx-tree.gpx-drop-hover {
outline: 1px dashed #4a90d9;
outline-offset: -2px;
}
.gpx-legend {
flex-shrink: 0;
max-height: 140px;
overflow-y: auto;
padding: 8px 12px;
border-top: 1px solid #eee;
}
.gpx-legend[hidden] {
display: none;
}
.gpx-legend__item {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 0;
}
.gpx-legend__swatch {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
#map.gpx-drop-hover::after {
content: "";
position: absolute;
inset: 0;
outline: 3px dashed #4a90d9;
outline-offset: -3px;
background: rgba(74, 144, 217, 0.08);
pointer-events: none;
}
.maplibregl-popup-content p {
margin: 4px 0 0;
}
.debug-popup .maplibregl-popup-content {
max-height: 320px;
overflow-y: auto;
}
.debug-popup__feature {
font-size: 11px;
font-family: monospace;
}
.debug-popup__feature + .debug-popup__feature {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #eee;
}
.debug-popup__feature table {
border-collapse: collapse;
width: 100%;
}
.debug-popup__feature th {
text-align: left;
color: #888;
font-weight: normal;
padding: 1px 6px 1px 0;
vertical-align: top;
white-space: nowrap;
}
.debug-popup__feature td {
word-break: break-word;
}
.maplibregl-ctrl-overview-map {
border-radius: 4px;
overflow: hidden;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
.measure-ctrl {
position: relative;
}
.measure-ctrl__button svg {
display: block;
margin: 0 auto;
width: 18px;
height: 18px;
}
.measure-ctrl__button.active {
background: #dbe9ff;
}
.measure-panel {
position: absolute;
top: 0;
left: 100%;
margin-left: 8px;
z-index: 20;
display: flex;
align-items: center;
gap: 6px;
min-width: max-content;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 6px 8px;
font-size: 13px;
}
.measure-panel[hidden] {
display: none;
}
/* ".measure-panel " prefix (not just ".measure-panel__mode") to outrank
".maplibregl-ctrl-group button" (higher specificity, since these buttons
sit inside a maplibregl-ctrl-group container for the ruler icon's own
sake) — its fixed 29x29 square + zero padding otherwise wins, clipping
"Distance"/"Area" into overlapping text instead of sizing to fit it. */
.measure-panel .measure-panel__mode {
width: auto;
height: auto;
display: inline-block;
white-space: nowrap;
padding: 4px 8px;
border: none;
border-radius: 4px;
background: none;
font: inherit;
font-size: 12px;
cursor: pointer;
}
.measure-panel__mode:hover {
background: #f0f0f0;
}
.measure-panel__mode.active {
background: #dbe9ff;
font-weight: 600;
}
.measure-panel__result {
min-width: 4em;
color: #333;
font-variant-numeric: tabular-nums;
}
.measure-panel .measure-panel__clear {
width: auto;
height: auto;
display: inline-block;
border: none;
background: none;
font-size: 16px;
line-height: 1;
color: #888;
cursor: pointer;
padding: 0 2px;
}
.measure-panel__clear:hover {
color: #333;
}
.zoom-level-ctrl__value {
width: 29px;
height: 29px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
color: #333;
user-select: none;
}
.gpx-popup__time {
color: #888;
font-size: 12px;
}
.gpx-popup__category {
color: #888;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.gpx-popup__description {
line-height: 1.4;
}
.gpx-popup__photos {
display: flex;
gap: 4px;
margin: 4px 0;
overflow-x: auto;
}
.gpx-popup__photo {
width: 64px;
height: 64px;
flex-shrink: 0;
object-fit: cover;
border-radius: 4px;
}
.gpx-popup__meta {
margin-top: 8px;
padding-top: 6px;
border-top: 1px solid #eee;
font-size: 12px;
line-height: 1.5;
}
.gpx-popup__meta-row + .gpx-popup__meta-row {
margin-top: 2px;
}
.gpx-popup__meta-label {
color: #888;
margin-right: 4px;
}
.gpx-popup__tags {
display: flex;
flex-wrap: wrap;
gap: 4px 10px;
margin-top: 2px;
}
.gpx-popup__tag {
display: inline-flex;
align-items: center;
gap: 3px;
}
.gpx-popup__tag-icon {
display: flex;
width: 13px;
height: 13px;
color: #555;
flex-shrink: 0;
}
.gpx-popup__tag-icon svg {
width: 100%;
height: 100%;
}
.gpx-popup__link {
margin-top: 6px;
font-size: 12px;
}
.gnss-control__status {
color: #888;
white-space: nowrap;
}
.gnss-marker {
width: 14px;
height: 14px;
border: 2px solid #fff;
border-radius: 50%;
background: #2a9d8f;
box-shadow: 0 0 0 2px rgba(42, 157, 143, 0.4);
}
.livetrack-panel__list {
flex: 1;
overflow-y: auto;
list-style: none;
margin: 0;
padding: 8px 12px;
}
.livetrack-panel__item {
border-bottom: 1px solid #f0f0f0;
}
.livetrack-panel__row {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 6px 4px;
border: none;
border-radius: 4px;
background: none;
font: inherit;
text-align: left;
cursor: pointer;
}
.livetrack-panel__row:hover {
background: #f0f0f0;
}
.livetrack-panel__row.active {
background: #f1e6fb;
}
.livetrack-panel__row.loading {
opacity: 0.6;
cursor: wait;
}
.livetrack-panel__label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.livetrack-panel__label--id {
font-family: monospace;
}
.livetrack-badge {
flex-shrink: 0;
padding: 2px 6px;
border-radius: 3px;
background: #e5e5e5;
color: #666;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.livetrack-badge--live {
background: #d7f3e3;
color: #1b7a43;
}
.livetrack-panel__time {
flex-shrink: 0;
color: #888;
font-size: 12px;
}
.profile-panel {
position: fixed;
left: 52px;
right: 0;
bottom: 0;
max-height: 45vh;
display: flex;
flex-direction: column;
background: #fcfcfb;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
z-index: 10;
}
.profile-panel[hidden] {
display: none;
}
.profile-panel__header {
display: flex;
align-items: baseline;
gap: 10px;
padding: 10px 14px;
border-bottom: 1px solid #e1e0d9;
}
.profile-panel__title {
font-size: 14px;
}
.profile-panel__stats {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #52514e;
font-size: 13px;
}
.profile-panel__close {
border: none;
background: none;
font-size: 18px;
line-height: 1;
padding: 0 4px;
cursor: pointer;
color: #898781;
}
.profile-panel__charts {
position: relative;
overflow-y: auto;
padding: 8px 14px 14px;
}
.profile-chart {
padding-top: 8px;
}
.profile-chart + .profile-chart {
border-top: 1px solid #e1e0d9;
}
.profile-chart__label {
font-size: 11px;
color: #898781;
margin-bottom: 2px;
}
.profile-chart__unit {
font-weight: normal;
}
.profile-chart__plot {
position: relative;
}
.profile-chart__svg {
display: block;
width: 100%;
height: 100%;
cursor: crosshair;
}
.profile-chart__crosshair {
stroke: #898781;
stroke-width: 1;
vector-effect: non-scaling-stroke;
}
/* Overlaid as plain HTML, not SVG text: the chart's viewBox scales width
and height independently (preserveAspectRatio="none", so the line fills
a responsive width at a fixed pixel height) — text drawn inside that
coordinate space would get stretched by the same mismatched factor. */
.profile-chart__axis-value {
position: absolute;
left: 4px;
color: #898781;
font-size: 10px;
line-height: 1;
background: rgba(252, 252, 251, 0.75);
padding: 1px 3px;
border-radius: 2px;
pointer-events: none;
}
.profile-chart__axis-value--max {
top: 2px;
}
.profile-chart__axis-value--min {
bottom: 2px;
}
.profile-panel__xaxis-toggle {
display: flex;
justify-content: flex-end;
gap: 4px;
margin-bottom: 4px;
}
.profile-panel__xaxis-toggle-btn {
border: 1px solid #e1e0d9;
background: #fcfcfb;
color: #52514e;
font-size: 11px;
padding: 2px 8px;
border-radius: 3px;
cursor: pointer;
}
.profile-panel__xaxis-toggle-btn.active {
background: #0b0b0b;
border-color: #0b0b0b;
color: #fff;
}
.profile-panel__xaxis {
position: relative;
height: 14px;
margin-top: 6px;
}
.profile-panel__xaxis-tick {
position: absolute;
top: 0;
transform: translateX(-50%);
font-size: 10px;
color: #898781;
white-space: nowrap;
}
.profile-panel__xaxis-tick:first-child {
transform: translateX(0);
}
.profile-panel__xaxis-tick:last-child {
transform: translateX(-100%);
}
.profile-panel__tooltip {
position: absolute;
top: 0;
z-index: 1;
pointer-events: none;
background: #0b0b0b;
color: #fff;
border-radius: 4px;
padding: 6px 8px;
font-size: 12px;
white-space: nowrap;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}
.profile-panel__tooltip[hidden] {
display: none;
}
.profile-panel__tooltip-distance {
font-weight: 600;
margin-bottom: 2px;
}
.profile-panel__tooltip-label {
color: #c3c2b7;
}