Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function BoundsRestrictionIndicator() {

return (
<div
className="pointer-events-auto absolute bottom-12 left-2 z-10 flex items-center gap-1 rounded-md border bg-background/90 px-2 py-1 text-xs font-medium text-foreground shadow-sm backdrop-blur-sm"
className="pointer-events-auto absolute bottom-12 left-2 z-10 flex items-center gap-1 rounded-md border map-glass px-2 py-1 text-xs font-medium text-foreground shadow-sm"
role="status"
title={tooltip}
data-testid="bounds-restriction-indicator"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
// Default to [] defensively: a relay that predates the chat protocol can send
// a `welcome` without `chat`, and an undefined slice would crash this badge
// (and, since it renders inside the map's error boundary, the whole map).
const chat = useAppStore((s) => s.collaboration.chat) ?? [];

Check warning on line 64 in apps/geolibre-desktop/src/components/layout/CollaborationStatusBadge.tsx

View workflow job for this annotation

GitHub Actions / Build and test

The 'chat' logical expression could make the dependencies of useEffect Hook (at line 208) change on every render. To fix this, wrap the initialization of 'chat' in its own useMemo() Hook
const isHost = role === "host";
const setCollaborateDialogOpen = useAppStore((s) => s.setCollaborateDialogOpen);
// Shares the bottom-left corner with the MapLibre scale control and the
Expand Down Expand Up @@ -276,7 +276,7 @@
{announcements.map((a) => (
<div
key={a.id}
className="pointer-events-auto flex items-center gap-1.5 rounded-md border bg-background/95 px-2 py-1 text-xs text-foreground shadow-sm backdrop-blur-sm"
className="pointer-events-auto flex items-center gap-1.5 rounded-md border map-glass px-2 py-1 text-xs text-foreground shadow-sm"
>
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-green-500" />
<span className="truncate">{a.text}</span>
Expand All @@ -288,7 +288,7 @@
{expanded && (
<div
id="collab-roster-panel"
className="pointer-events-auto rounded-md border bg-background/95 shadow-md backdrop-blur-sm"
className="pointer-events-auto rounded-md border map-glass shadow-md"
>
<div className="flex items-center justify-between border-b px-2.5 py-1.5">
<span className="text-xs font-medium">
Expand Down Expand Up @@ -449,7 +449,7 @@
: t("collaborate.sessionStatusTooltip")
}
title={expanded ? t("collaborate.collapseRoster") : t("collaborate.sessionStatusTooltip")}
className="pointer-events-auto flex items-center gap-1.5 self-start rounded-full border bg-background/95 px-2.5 py-1 text-xs font-medium text-foreground shadow-sm backdrop-blur-sm transition hover:bg-accent"
className="pointer-events-auto flex items-center gap-1.5 self-start rounded-full border map-glass px-2.5 py-1 text-xs font-medium text-foreground shadow-sm transition hover:bg-accent"
Comment thread
giswqs marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
>
<span className="relative flex h-2 w-2" aria-hidden="true">
{!connecting && (
Expand Down
17 changes: 12 additions & 5 deletions apps/geolibre-desktop/src/components/layout/MapGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function PaneLabel({
onChange={(event) => onChange(event.target.value)}
aria-label={ariaLabel}
placeholder={t("mapGrid.labelPlaceholder")}
className="pointer-events-auto h-7 w-32 max-w-[40%] rounded-md border border-input bg-background/90 px-2 text-center text-sm text-foreground shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-none"
className="pointer-events-auto h-7 w-32 max-w-[40%] rounded-md border border-input map-glass px-2 text-center text-sm text-foreground shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-none"
/>
</div>
);
Expand Down Expand Up @@ -165,7 +165,7 @@ function SecondaryMapPane({ viewId, index, cesiumToken }: SecondaryMapPaneProps)
{cesiumAvailable ? (
<button
type="button"
className="flex h-7 w-7 items-center justify-center rounded-md border border-input bg-background/90 text-muted-foreground shadow-sm transition-colors hover:bg-accent hover:text-foreground"
className="flex h-7 w-7 items-center justify-center rounded-md border border-input map-glass text-muted-foreground shadow-sm transition-colors hover:bg-accent hover:text-foreground"
Comment thread
giswqs marked this conversation as resolved.
aria-label={
is3d
? t("mapGrid.show2d", { number: index + 2 })
Expand All @@ -179,7 +179,7 @@ function SecondaryMapPane({ viewId, index, cesiumToken }: SecondaryMapPaneProps)
) : null}
<button
type="button"
className="flex h-7 w-7 items-center justify-center rounded-md border border-input bg-background/90 text-muted-foreground shadow-sm transition-colors hover:bg-accent hover:text-foreground"
className="flex h-7 w-7 items-center justify-center rounded-md border border-input map-glass text-muted-foreground shadow-sm transition-colors hover:bg-accent hover:text-foreground"
aria-label={t("mapGrid.removePane", { number: index + 2 })}
onClick={() => removeSecondaryMapView(viewId)}
>
Expand Down Expand Up @@ -215,9 +215,16 @@ function PaneLayerToggle({ viewId, index, is3d }: PaneLayerToggleProps) {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
// ghost rather than outline: outline carries its own bg-background,
// which sits in the same cascade layer as map-glass at equal
// specificity, so this button rendered opaque beside its glass
// siblings. ghost sets no base background, leaving map-glass to paint
// it, and still supplies hover:bg-accent. The border the outline
// variant would have given is restored explicitly to match the
// sibling controls.
variant="ghost"
size="sm"
className="h-7 gap-1.5 bg-background/90 px-2 shadow-sm"
className="h-7 gap-1.5 border border-input map-glass px-2 shadow-sm"
aria-label={t("mapGrid.layersLabel", { number: index + 2 })}
>
<Layers className="h-3.5 w-3.5" />
Expand Down
4 changes: 2 additions & 2 deletions apps/geolibre-desktop/src/components/layout/MapModeBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function MapModeBanner({ mapControllerRef }: MapModeBannerProps) {
<div className="pointer-events-none absolute left-1/2 top-3 z-20 flex w-[min(92vw,30rem)] -translate-x-1/2 flex-col gap-2">
{directionsActive ? (
<div
className="pointer-events-auto flex flex-col gap-2 rounded-md border bg-background/95 px-3 py-2 text-sm shadow-lg backdrop-blur-sm"
className="pointer-events-auto flex flex-col gap-2 rounded-md border map-glass px-3 py-2 text-sm shadow-lg"
role="region"
aria-label={t("map.directionsMode.title")}
data-testid="directions-mode-banner"
Expand Down Expand Up @@ -231,7 +231,7 @@ export function MapModeBanner({ mapControllerRef }: MapModeBannerProps) {

{reverseGeocodeActive ? (
<div
className="pointer-events-auto flex items-center gap-2 rounded-md border bg-background/95 px-3 py-2 text-sm shadow-lg backdrop-blur-sm"
className="pointer-events-auto flex items-center gap-2 rounded-md border map-glass px-3 py-2 text-sm shadow-lg"
role="region"
aria-label={t("map.reverseGeocodeMode.title")}
data-testid="reverse-geocode-mode-banner"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function QuickAnalysisBanner() {
<div className="pointer-events-none absolute bottom-10 left-1/2 z-20 flex w-[min(92vw,28rem)] -translate-x-1/2 justify-center">
<div
role="status"
className="pointer-events-auto flex w-full items-center gap-2 rounded-md border bg-background/95 px-3 py-2 text-xs shadow-md backdrop-blur"
className="pointer-events-auto flex w-full items-center gap-2 rounded-md border map-glass px-3 py-2 text-xs shadow-md"
>
{running ? (
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ export function MapLegendPanel({
// a flex column keeps header/footer fixed while the entry list scrolls.
<div
ref={panelRef}
className="relative flex w-64 flex-col overflow-hidden rounded-lg border border-border/50 bg-background/95 text-foreground shadow-lg backdrop-blur-md"
className="relative flex w-64 flex-col overflow-hidden rounded-lg border border-border/50 map-glass text-foreground shadow-lg"
style={{
maxHeight: maxHeight ?? undefined,
...(width !== undefined ? { width: clamp(width, MIN_PANEL_WIDTH, MAX_PANEL_WIDTH) } : {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function FlightSimulatorCard() {

return (
<div
className="absolute z-30 rounded-lg border border-border bg-background/95 shadow-lg backdrop-blur"
className="absolute z-30 rounded-lg border border-border map-glass shadow-lg"
style={{ left: position.x, top: position.y, width: PANEL_WIDTH }}
role="dialog"
aria-label={t("toolbar.flightSim.title")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ function RouteAnimationCard({ mapControllerRef }: RouteAnimationPanelProps) {

return (
<div
className="absolute z-30 rounded-lg border border-border bg-background/95 shadow-lg backdrop-blur"
className="absolute z-30 rounded-lg border border-border map-glass shadow-lg"
style={{ left: position.x, top: position.y, width: PANEL_WIDTH }}
role="dialog"
aria-label={t("toolbar.routeAnimation.title")}
Expand Down
2 changes: 1 addition & 1 deletion apps/geolibre-desktop/src/components/panels/SunPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ function SunPanelCard() {

return (
<div
className="absolute z-30 rounded-lg border border-border bg-background/95 shadow-lg backdrop-blur"
className="absolute z-30 rounded-lg border border-border map-glass shadow-lg"
style={{ left: position.x, top: position.y, width: PANEL_WIDTH }}
role="dialog"
aria-label={t("toolbar.sun.title")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function StoryMapComposeBar({ mapControllerRef }: StoryMapComposeBarProps
return (
<div className="pointer-events-none absolute bottom-6 left-1/2 z-20 flex w-[min(92vw,34rem)] -translate-x-1/2 flex-col">
<div
className="pointer-events-auto flex flex-col gap-2 rounded-md border bg-background/95 px-3 py-2 text-sm shadow-lg backdrop-blur-sm"
className="pointer-events-auto flex flex-col gap-2 rounded-md border map-glass px-3 py-2 text-sm shadow-lg"
role="region"
aria-label={t("storymap.composeMode.title")}
data-testid="storymap-compose-bar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ export function StoryMapPresenter({ mapControllerRef }: StoryMapPresenterProps)
{navOpen ? (
<nav
aria-label={t("storymap.chapterNav")}
className="absolute left-3 top-14 z-[72] max-h-[calc(100%-4.5rem)] w-52 overflow-y-auto rounded-md border bg-background/85 p-1.5 shadow-lg backdrop-blur"
className="absolute left-3 top-14 z-[72] max-h-[calc(100%-4.5rem)] w-52 overflow-y-auto rounded-md border map-glass p-1.5 shadow-lg"
>
{chapters.map((chapter, index) => (
<button
Expand Down
90 changes: 90 additions & 0 deletions apps/geolibre-desktop/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,96 @@
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

/* Map-floating chrome, a.k.a. "instrument glass".

The distinction this encodes: chrome docked BESIDE the map (top toolbar,
status bar, side panels) is fully opaque, while panels that hover OVER the
map are translucent and blurred so the data reads through them. Translucency
is the signal for "this floats above your map" — which is why it must not
spread to docked chrome or to dialogs, which sit on a dimmed overlay rather
than on the map.

Codified here because the recipe was already in use across 11 components
with drifted values — bg-background/90 vs /95, and backdrop-blur-sm vs -md vs
bare backdrop-blur — so tuning the look meant finding every copy. Components
keep their own radius, border, shadow and padding; this owns only the surface
itself.

Must be an `@utility`, i.e. inside Tailwind's utilities layer — NOT an
unlayered plain class. Unlayered CSS beats every layered declaration for the
same property, and cascade-layer precedence is resolved *before* specificity,
so an unlayered `.map-glass { background-color }` also silently beats
`hover:bg-accent`. That killed the hover fill on MapGrid's pane buttons and
the CollaborationStatusBadge toggle — verified dead, not theoretical. Inside
the layer, `.hover\:bg-accent:hover` (0,2,0) outranks `.map-glass` (0,1,0)
and hover works again.

The @supports fallback is nested so it stays in the layer too; hoisting it
out would reintroduce exactly the same bug for browsers without
backdrop-filter. Without the blur, 85% over live imagery is unreadable.

The saturate() boost keeps basemap imagery from going muddy under the blur. */
@utility map-glass {
background-color: hsl(var(--background) / 0.85);
-webkit-backdrop-filter: blur(12px) saturate(140%);
backdrop-filter: blur(12px) saturate(140%);

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
background-color: hsl(var(--background) / 0.97);
}
}

/* Elevation-tied shadow scale, replacing Tailwind's stock one. Each step maps to
a surface in the ladder rather than being picked by eye:

shadow-xs form field — input, textarea, select, colour picker
shadow-sm resting control / inline card
shadow-md popover, dropdown, tooltip
shadow-lg panel floating over the map
shadow-xl modal dialog
Comment thread
giswqs marked this conversation as resolved.

shadow-xs matters more than its name suggests: it is what @geolibre/ui's form
primitives use, so leaving it on the stock colour would have left every input,
textarea, select and colour field in the app on the invisible-in-dark shadow
this block exists to fix. Bare `shadow` is defined too (one use, slider.tsx).
shadow-2xl and shadow-inner are unused; add them here if that changes,
otherwise they silently fall back to Tailwind's 10% black.

Redefining the tokens fixes all ~65 existing shadow sites without touching a
single component. The colours come from --elev-penumbra/--elev-umbra
(packages/ui/src/globals.css), which are theme-aware — Tailwind's built-in
scale hardcodes 10% black, so dark mode previously had no visible shadows at
all.

The colours are written bare here: Tailwind wraps each one in
var(--tw-shadow-color, …) when it generates the utility, so `shadow-<color>`
overrides keep working without spelling the wrapper out (writing it manually
just nests the fallback twice). */
@theme {
--shadow-xs: 0 1px 1px 0 var(--elev-penumbra);
--shadow-sm: 0 1px 2px 0 var(--elev-penumbra), 0 1px 3px 0 var(--elev-umbra);
--shadow: 0 1px 2px 0 var(--elev-penumbra), 0 1px 3px 0 var(--elev-umbra);
--shadow-md: 0 2px 4px -1px var(--elev-penumbra), 0 4px 8px -2px var(--elev-umbra);
--shadow-lg: 0 4px 8px -2px var(--elev-penumbra), 0 12px 20px -4px var(--elev-umbra);
--shadow-xl: 0 8px 16px -4px var(--elev-penumbra), 0 24px 40px -8px var(--elev-umbra);
}
Comment thread
giswqs marked this conversation as resolved.

/* One global reduced-motion guard. Previously only two components respected the
preference while ~53 transition sites ignored it. Motion is reduced to a
near-instant cross-fade rather than removed outright, so state changes stay
perceptible (an abrupt swap is its own accessibility problem) — and scroll
behaviour is pinned to auto so programmatic smooth scrolls stop animating. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Comment thread
giswqs marked this conversation as resolved.

html,
body,
#root {
Expand Down
27 changes: 26 additions & 1 deletion packages/ui/src/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,24 @@
--border: 220 15% 88%;
--input: 220 13% 76%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
/* 0.375rem rather than shadcn's stock 0.5rem: a dense tool UI reads sharper,
and the derived md/sm steps (calc -2px/-4px) stay positive. */
--radius: 0.375rem;

/* Elevation shadow colours, consumed by the --shadow-* scale the app defines
in index.css. Two layers: penumbra is the wide soft spread, umbra the
tight contact shadow.

These are plain custom properties precisely so they inherit. Tailwind v4
registers its own --tw-shadow-color with `inherits: false`, so retinting
shadows by setting that on :root silently does nothing — the value has to
reach each element, which ordinary inheritance does and @property-
registered custom properties do not.

Light shadows are a cool near-navy rather than pure black; against the
neutral chrome it reads less muddy than #000 at the same alpha. */
--elev-penumbra: hsl(220 40% 12% / 0.06);
--elev-umbra: hsl(220 40% 12% / 0.1);
}

/* Dark mode is built as an explicit elevation ladder. Previously --background,
Expand Down Expand Up @@ -69,6 +86,14 @@
--destructive-foreground: 210 40% 98%;
--border: 220 14% 26%;
--input: 220 12% 38%;
/* Tailwind's built-in shadow scale bakes in a 10%-black colour, which is
all but invisible against these surfaces — so before this, every one of
the app's ~65 shadow utilities did nothing in dark mode and elevation was
carried entirely by the surface ladder above. Pure black at a much higher
alpha is what actually reads on a dark UI. */
--elev-penumbra: hsl(0 0% 0% / 0.4);
--elev-umbra: hsl(0 0% 0% / 0.55);

/* Matches --primary, so the focus ring stays visible against the lifted
surfaces (4.98:1 on --background, 4.64:1 on --card). The violet, rose and
amber schemes below already pair ring with primary this way; emerald was
Expand Down
Loading