mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
85b5756e89
Ride minutes and late marks follow an edited timetable instead of the routing that fetched them; a landing day's join band reads as the ride it is rather than a nine hour drive of no length; the refuel offer is there on the road out of a terminal or a pick-up desk; the phone's stage map draws the ride; the share page numbers its stops the way the planner does; the PDF lists a day the way the day plan lists it, from both shells; a poster-less clip draws its play badge everywhere, not only in the editor; the hero title has room for its descenders and the list card keeps its badge clear; the release notice keeps its close control in reach when it stacks; a link chip's actions are thumb-sized and its delete asks first; the Google retry line and the Google-only switch appear only where Google answers.
1595 lines
62 KiB
CSS
1595 lines
62 KiB
CSS
@tailwind base;
|
||
@tailwind components;
|
||
@tailwind utilities;
|
||
|
||
html { height: 100%; overflow: hidden; background-color: var(--bg-primary); }
|
||
body { height: 100%; overflow: auto; overscroll-behavior: none; -webkit-overflow-scrolling: touch; }
|
||
|
||
/* Phone (<768px): the document is the scroller, not body (#1809). iOS Safari
|
||
only minimises its address bar in reaction to the root scroller moving, so a
|
||
locked html keeps the bar at full height forever. `visible` on both, not
|
||
`auto`: only then does body's overflow propagate to the viewport, which is
|
||
what lets the shared body scroll lock stop the page behind an overlay.
|
||
overscroll-behavior has to sit on html, because the value on body is never
|
||
propagated to the viewport, and without it a pull at the top of the page
|
||
reloads the installed PWA. Desktop/tablet keep the rules above. */
|
||
@media (max-width: 767px) {
|
||
html { overflow: visible; overscroll-behavior: none; }
|
||
/* overflow-x clip (not hidden: clip creates no scroll container) so a screen
|
||
that accidentally overflows sideways cannot widen the document. Android's
|
||
forced-zoom setting reacts to a wider document by growing the layout
|
||
viewport that position:fixed resolves against, which pushed the top bar
|
||
and the dock off the right edge on narrow phones. On body, never on html:
|
||
body's overflow only propagates to the viewport while html stays visible,
|
||
and the shared body scroll lock relies on that propagation. */
|
||
body { height: auto; overflow: visible; overflow-x: clip; }
|
||
/* Transparent so the shell's own background layer, which sits behind the page
|
||
on a negative z-index, is not painted over: a negative layer goes above the
|
||
canvas but below the background of ordinary blocks, and body is one. The
|
||
alternative, lifting the layer into the positioned range, would have turned
|
||
the content wrapper into a stacking context and trapped every fixed overlay
|
||
inside it (the vacay FAB ended up under the dock that way).
|
||
|
||
`html body` and not `body`: a media query adds no specificity, and the
|
||
themed `body` rule further down this file would otherwise win on source
|
||
order alone and paint over the layer again. */
|
||
html body { background-color: transparent; }
|
||
}
|
||
|
||
|
||
/* Journey desktop feed: hide scrollbar (right column is a sticky map, a
|
||
visible scrollbar on the left breaks the polarsteps-style reading feel). */
|
||
.journey-feed-scroll { scrollbar-width: none; -ms-overflow-style: none; }
|
||
.journey-feed-scroll::-webkit-scrollbar { display: none; }
|
||
|
||
/* Road trip rail: hide the scrollbar of the day list. The totals card is pinned
|
||
above the list and therefore outside its scroll box, so a visible scrollbar
|
||
would take width from the day cards alone and leave the two stacks a few
|
||
pixels out of line — the one thing a column of nested cards cannot hide. */
|
||
.roadtrip-rail-scroll { scrollbar-width: none; -ms-overflow-style: none; }
|
||
.roadtrip-rail-scroll::-webkit-scrollbar { display: none; }
|
||
|
||
/* Journey video: let the player fill the box the lightbox gives it.
|
||
*
|
||
* Plyr sizes its wrapper from the video's intrinsic aspect ratio via a
|
||
* padding-bottom hack, which fights a fixed-height container — a portrait clip
|
||
* then overflowed and pushed its own controls out of reach (#2041). Pinning the
|
||
* wrapper to the box and letting the video letterbox inside it keeps any aspect
|
||
* ratio whole and the control bar on screen. */
|
||
.trek-video-player .plyr,
|
||
.trek-video-player .plyr__video-wrapper {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #000;
|
||
}
|
||
.trek-video-player .plyr__video-wrapper {
|
||
padding-bottom: 0 !important;
|
||
}
|
||
.trek-video-player video {
|
||
width: 100% !important;
|
||
height: 100% !important;
|
||
object-fit: contain !important;
|
||
}
|
||
|
||
/* Leaflet Popups — Enter-Animation vom Anchor-Tip */
|
||
.leaflet-popup {
|
||
animation: trek-popover-enter 220ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
transform-origin: bottom center;
|
||
will-change: transform, opacity;
|
||
}
|
||
.leaflet-popup-content-wrapper {
|
||
border-radius: 14px !important;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18) !important;
|
||
background: var(--bg-card) !important;
|
||
color: var(--text-primary) !important;
|
||
border: 1px solid var(--border-faint);
|
||
}
|
||
.leaflet-popup-tip {
|
||
background: var(--bg-card) !important;
|
||
}
|
||
.leaflet-popup-close-button {
|
||
transition: color 150ms cubic-bezier(0.23, 1, 0.32, 1), transform 150ms cubic-bezier(0.23, 1, 0.32, 1) !important;
|
||
}
|
||
.leaflet-popup-close-button:hover {
|
||
transform: scale(1.15);
|
||
color: var(--text-primary) !important;
|
||
}
|
||
|
||
/* GL hover popup — the name/category/address card on marker hover.
|
||
Matches the Leaflet map's white hover tooltip. pointer-events:none so moving
|
||
onto the popup never steals the marker's mouseleave and causes flicker. */
|
||
.trek-map-popup { pointer-events: none; }
|
||
.trek-map-popup .mapboxgl-popup-content,
|
||
.trek-map-popup .maplibregl-popup-content {
|
||
padding: 7px 10px;
|
||
border-radius: 10px;
|
||
background: #fff;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.16);
|
||
}
|
||
/* No speech-bubble tail on the hover card. The card stands off its marker by
|
||
26px and reads as a label, not as a callout pinned to a point — the tail
|
||
hung off the bottom edge as a stray notch. The popup's offset carries the
|
||
gap on its own. */
|
||
.trek-map-popup .mapboxgl-popup-tip,
|
||
.trek-map-popup .maplibregl-popup-tip {
|
||
display: none;
|
||
}
|
||
/* Both engines square off one corner per anchor so the tail meets the card
|
||
flush (.maplibregl-popup-anchor-bottom-left .maplibregl-popup-content and
|
||
its three siblings). Same specificity as the rule above, so which one wins
|
||
comes down to stylesheet order — and with no tail there, a squared corner is
|
||
just a squared corner. `[class]` buys the extra weight to settle it. */
|
||
.trek-map-popup[class] .mapboxgl-popup-content,
|
||
.trek-map-popup[class] .maplibregl-popup-content {
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.atlas-tooltip {
|
||
background: rgba(10, 10, 20, 0.6) !important;
|
||
backdrop-filter: blur(20px) saturate(180%) !important;
|
||
-webkit-backdrop-filter: blur(20px) saturate(180%) !important;
|
||
color: #f1f5f9 !important;
|
||
border: 1px solid rgba(255,255,255,0.1) !important;
|
||
border-radius: 14px !important;
|
||
padding: 10px 14px !important;
|
||
font-size: 12px !important;
|
||
font-family: inherit !important;
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.25) !important;
|
||
transition: none !important;
|
||
max-width: min(280px, calc(100vw - 32px)) !important;
|
||
white-space: normal !important;
|
||
word-wrap: break-word !important;
|
||
}
|
||
.atlas-tooltip::before { border-top-color: rgba(10, 10, 20, 0.6) !important; }
|
||
/* Bucket-list marker tooltip only — arbitrary-length user notes need a scroll
|
||
cap. The scroll happens on .atlas-tooltip-scroll-inner, not this element,
|
||
so the outer border-radius clips the scrollbar instead of it poking
|
||
through the rounded corner. overflow-y on the inner div is toggled
|
||
per-instance in useAtlas.ts, not forced here. */
|
||
.atlas-tooltip-scrollable {
|
||
width: min(480px, calc(100vw - 32px)) !important;
|
||
max-width: min(480px, calc(100vw - 32px)) !important;
|
||
overflow: hidden !important;
|
||
pointer-events: auto !important;
|
||
}
|
||
.atlas-tooltip-scroll-inner {
|
||
max-height: min(200px, 40vh);
|
||
overflow-y: hidden;
|
||
/* Leaflet puts touch-action: none on the map container; without widening it
|
||
back here a touch swipe over the note can never scroll it (#2153). */
|
||
touch-action: pan-y;
|
||
}
|
||
.atlas-tooltip-scroll-inner::-webkit-scrollbar { width: 6px; }
|
||
.atlas-tooltip-scroll-inner::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.25); border-radius: 3px; }
|
||
html:not(.dark) .atlas-tooltip-scroll-inner::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); }
|
||
html:not(.dark) .atlas-tooltip {
|
||
background: rgba(255, 255, 255, 0.75) !important;
|
||
color: #0f172a !important;
|
||
border: 1px solid rgba(0,0,0,0.08) !important;
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.1) !important;
|
||
}
|
||
html:not(.dark) .atlas-tooltip::before { border-top-color: rgba(255, 255, 255, 0.75) !important; }
|
||
.leaflet-tooltip.atlas-tooltip { opacity: 1 !important; }
|
||
.leaflet-tooltip-pane { transition: none !important; }
|
||
.leaflet-fade-anim .leaflet-tooltip { transition: none !important; opacity: 1 !important; }
|
||
|
||
.dark .leaflet-control-zoom a {
|
||
background: rgba(10, 10, 20, 0.7) !important;
|
||
color: #e2e8f0 !important;
|
||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||
backdrop-filter: blur(12px);
|
||
}
|
||
.dark .leaflet-control-zoom a:hover {
|
||
background: rgba(30, 30, 40, 0.8) !important;
|
||
}
|
||
@media (max-width: 767px) {
|
||
.leaflet-control-zoom { display: none !important; }
|
||
}
|
||
|
||
/* Dark mode overrides for pages using hardcoded slate-* Tailwind classes */
|
||
html.dark .bg-slate-50 { background-color: var(--bg-secondary) !important; }
|
||
html.dark .bg-white { background-color: var(--bg-card) !important; }
|
||
html.dark .bg-slate-100 { background-color: var(--bg-secondary) !important; }
|
||
html.dark .bg-slate-900.text-white { background-color: #e2e8f0 !important; color: #0f172a !important; }
|
||
html.dark .border-slate-200, html.dark .border-slate-300 { border-color: var(--border-primary) !important; }
|
||
html.dark .border-slate-100, html.dark .border-b-slate-100 { border-color: var(--border-secondary) !important; }
|
||
html.dark .text-slate-900 { color: var(--text-primary) !important; }
|
||
html.dark .text-slate-700 { color: var(--text-secondary) !important; }
|
||
html.dark .text-slate-600 { color: var(--text-muted) !important; }
|
||
html.dark .text-slate-500 { color: var(--text-muted) !important; }
|
||
html.dark .text-slate-400 { color: var(--text-faint) !important; }
|
||
html.dark .hover\:bg-slate-50:hover, html.dark .hover\:bg-slate-100:hover { background-color: var(--bg-hover) !important; }
|
||
html.dark .hover\:text-slate-900:hover { color: var(--text-primary) !important; }
|
||
html.dark .hover\:bg-slate-700:hover { background-color: var(--bg-hover) !important; }
|
||
html.dark .divide-slate-100 > :not([hidden]) ~ :not([hidden]) { border-color: var(--border-secondary) !important; }
|
||
html.dark .focus\:ring-slate-400:focus { --tw-ring-color: var(--text-faint) !important; }
|
||
html.dark input[class*="border-slate"], html.dark input[class*="text-slate"] { background: var(--bg-secondary) !important; color: var(--text-primary) !important; border-color: var(--border-primary) !important; }
|
||
html.dark .text-amber-900 { color: #fbbf24 !important; }
|
||
html.dark .text-amber-700 { color: #f59e0b !important; }
|
||
html.dark .bg-amber-50 { background-color: rgba(245,158,11,0.1) !important; }
|
||
html.dark .border-amber-200 { border-color: rgba(245,158,11,0.2) !important; }
|
||
html.dark .disabled\:bg-slate-400:disabled { background-color: var(--text-faint) !important; }
|
||
html.dark button.bg-slate-900 { background-color: #e2e8f0 !important; color: #0f172a !important; opacity: 1 !important; }
|
||
html.dark button.bg-slate-900:hover { background-color: #cbd5e1 !important; }
|
||
html.dark button.bg-slate-900:disabled { background-color: #ffffff !important; color: #000000 !important; opacity: 0.4 !important; }
|
||
html.dark span.bg-slate-900 { background-color: #e2e8f0 !important; color: #0f172a !important; }
|
||
html.dark span.bg-slate-100 { background-color: var(--bg-secondary) !important; color: var(--text-muted) !important; }
|
||
html.dark .border-b { border-bottom-color: var(--border-secondary) !important; }
|
||
/* Gray variants (CategoryManager, BackupPanel) */
|
||
html.dark .bg-gray-50 { background-color: var(--bg-secondary) !important; }
|
||
html.dark .bg-gray-100 { background-color: var(--bg-secondary) !important; }
|
||
html.dark .border-gray-200, html.dark .border-gray-300 { border-color: var(--border-primary) !important; }
|
||
html.dark .border-gray-100 { border-color: var(--border-secondary) !important; }
|
||
html.dark .text-gray-900 { color: var(--text-primary) !important; }
|
||
html.dark .text-gray-700 { color: var(--text-secondary) !important; }
|
||
html.dark .text-gray-600 { color: var(--text-muted) !important; }
|
||
html.dark .text-gray-500 { color: var(--text-muted) !important; }
|
||
html.dark .text-gray-400 { color: var(--text-faint) !important; }
|
||
html.dark .text-gray-300 { color: var(--text-faint) !important; }
|
||
html.dark .hover\:bg-gray-50:hover, html.dark .hover\:bg-gray-200:hover { background-color: var(--bg-hover) !important; }
|
||
html.dark .hover\:border-gray-200:hover, html.dark .hover\:border-gray-400:hover { border-color: var(--border-primary) !important; }
|
||
html.dark input.bg-white, html.dark input[class*="bg-white"] { background: var(--bg-secondary) !important; color: var(--text-primary) !important; }
|
||
html.dark .bg-gray-200 { background-color: var(--border-primary) !important; }
|
||
html.dark .border-gray-300.border-t-slate-600 { border-color: var(--border-primary) !important; border-top-color: var(--text-primary) !important; }
|
||
/* Modal buttons */
|
||
html.dark button[class*="text-slate-600"][class*="border-slate-200"] { color: var(--text-muted) !important; border-color: var(--border-primary) !important; }
|
||
html.dark button[class*="text-slate-600"][class*="border-slate-200"]:hover { background: var(--bg-hover) !important; }
|
||
/* Dashed borders */
|
||
html.dark .border-dashed.border-gray-300 { border-color: var(--border-primary) !important; }
|
||
html.dark .bg-slate-50\/60, html.dark [class*="bg-slate-50/"] { background-color: transparent !important; }
|
||
|
||
/* Reorder buttons: desktop = original style; touch = always visible, larger touch targets.
|
||
Keyed on pointer as well as width — a tablet is a wide touch device with no hover, so
|
||
without this the buttons would never appear there. (Drag reorder itself is no longer off
|
||
on a coarse pointer: #1616 gave tablets the long-press bridge.) */
|
||
.reorder-buttons {
|
||
flex-direction: column;
|
||
opacity: 0;
|
||
/* Hidden means non-interactive. Opacity alone still hit-tests, which left an invisible
|
||
but fully clickable target on every row that silently reordered the itinerary. */
|
||
pointer-events: none;
|
||
}
|
||
.reorder-buttons button {
|
||
background: none !important;
|
||
padding: 1px 2px;
|
||
}
|
||
/* Reveal on row hover (and on keyboard focus, so the buttons are reachable by Tab). */
|
||
.dp-row:hover .reorder-buttons,
|
||
.dp-row:focus-within .reorder-buttons {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
@media (max-width: 767px), (pointer: coarse) {
|
||
.reorder-buttons {
|
||
flex-direction: row !important;
|
||
opacity: 1 !important;
|
||
/* There is no hover on touch, so these are always live — undo the base rule. */
|
||
pointer-events: auto !important;
|
||
align-items: center;
|
||
margin-left: auto;
|
||
}
|
||
.reorder-buttons button {
|
||
background: var(--bg-tertiary) !important;
|
||
border-radius: 6px;
|
||
width: 32px;
|
||
height: 32px;
|
||
padding: 0 !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
}
|
||
|
||
/* Packing list rows: on phones, keep the item name readable and move the dense
|
||
per-item controls behind the overflow menu. */
|
||
.packing-row-overflow {
|
||
display: none !important;
|
||
}
|
||
@media (max-width: 639px) {
|
||
.packing-item-row {
|
||
gap: 6px !important;
|
||
padding: 7px 8px !important;
|
||
}
|
||
.packing-item-row > span {
|
||
flex-basis: auto !important;
|
||
}
|
||
.packing-row-inline-actions,
|
||
.packing-row-badge {
|
||
display: none !important;
|
||
}
|
||
.packing-row-overflow {
|
||
display: flex !important;
|
||
}
|
||
}
|
||
|
||
/* Ladeanimation */
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* ── Press-Feedback + bessere Easings (Emil Kowalski) ─────────── */
|
||
/* Buttons sollen antworten wenn sie gedrückt werden.
|
||
Map-Marker sind ausgenommen: MapLibre und Mapbox geben ihrem Marker-Element
|
||
role="button" und schreiben dessen `transform` bei jedem Frame einer Bewegung
|
||
neu. Mit einem Übergang darauf hinkt die Nadel dem Kartenzug hinterher und
|
||
springt am Ende an ihren Ort zurück, und ein Klick würde den scale(0.97) über
|
||
die Positionierung der Bibliothek legen. */
|
||
button:not(:disabled):not([data-no-press]),
|
||
[role="button"]:not([aria-disabled="true"]):not([data-no-press]):not(.maplibregl-marker):not(.mapboxgl-marker) {
|
||
transition-property: transform, color, background-color, border-color, box-shadow, opacity, filter !important;
|
||
transition-duration: 180ms;
|
||
transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
|
||
}
|
||
button:not(:disabled):not([data-no-press]):active,
|
||
[role="button"]:not([aria-disabled="true"]):not([data-no-press]):not(.maplibregl-marker):not(.mapboxgl-marker):active {
|
||
transform: scale(0.97);
|
||
transition-duration: 80ms;
|
||
}
|
||
|
||
/* Tailwind-Default-Easing durch ease-out-quint ersetzen.
|
||
Eingebaute CSS-Easings sind kraftlos; ease-out-quint hat Punch. */
|
||
.transition,
|
||
.transition-all,
|
||
.transition-colors,
|
||
.transition-opacity,
|
||
.transition-transform,
|
||
.transition-shadow {
|
||
transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
|
||
}
|
||
|
||
/* Input-Focus transitions — border + ring faden weich ein */
|
||
input, textarea, select {
|
||
transition: border-color 150ms cubic-bezier(0.23, 1, 0.32, 1),
|
||
box-shadow 150ms cubic-bezier(0.23, 1, 0.32, 1),
|
||
background-color 150ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
}
|
||
|
||
/* Back-Button Icon-Slide on hover */
|
||
.trek-back-btn .trek-back-icon {
|
||
transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
}
|
||
.trek-back-btn:hover .trek-back-icon {
|
||
transform: translateX(-2px);
|
||
}
|
||
|
||
/* Global focus-visible ring — konsistent überall */
|
||
:focus-visible {
|
||
outline: 2px solid var(--accent);
|
||
outline-offset: 2px;
|
||
border-radius: 4px;
|
||
}
|
||
button:focus-visible, [role="button"]:focus-visible, a:focus-visible {
|
||
outline-offset: 3px;
|
||
}
|
||
input:focus-visible, textarea:focus-visible, select:focus-visible {
|
||
outline: none;
|
||
}
|
||
|
||
/* Theme crossfade — beim Dark/Light switch, Hauptflächen + Text faden ihre Farben.
|
||
Sparingly: nur background-color und color bekommen eine Transition. */
|
||
html.trek-theme-transitioning,
|
||
html.trek-theme-transitioning body,
|
||
html.trek-theme-transitioning *:not(img):not(video):not(canvas):not([class*="trek-skeleton"]):not(.leaflet-layer) {
|
||
transition:
|
||
background-color 320ms cubic-bezier(0.23, 1, 0.32, 1),
|
||
color 320ms cubic-bezier(0.23, 1, 0.32, 1),
|
||
border-color 320ms cubic-bezier(0.23, 1, 0.32, 1),
|
||
fill 320ms cubic-bezier(0.23, 1, 0.32, 1) !important;
|
||
}
|
||
|
||
/* Touch-Geräte: iOS-Tap-Highlight weg (wir haben eigenes Press-Feedback) */
|
||
@media (hover: none) {
|
||
button, [role="button"], a {
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
}
|
||
html, body {
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
/* Tabular-nums global für Time/Date/Currency/Counter */
|
||
time, .tabular-nums, [data-tabular],
|
||
input[type="number"], input[type="time"], input[type="date"], input[type="datetime-local"] {
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
/* Wenn Element explizit ease-in-out nutzt (z.B. Accordions), nicht überschreiben.
|
||
Tailwind setzt ease-in-out via eigener Klasse — die gewinnt durch letzte Deklaration. */
|
||
|
||
/* Press-Scale für clickbare Divs (Cards, Tiles) — sanfter als Buttons */
|
||
[data-press]:active {
|
||
transform: scale(0.985);
|
||
transition-duration: 80ms;
|
||
}
|
||
|
||
/* ── Popover/Dropdown Enter-Animationen ─────────────────────────
|
||
Emil: Popovers sollen von ihrem Trigger aus scalen, nicht vom Center.
|
||
Start bei scale(0.95) — nichts in der echten Welt poppt aus dem Nichts. */
|
||
@keyframes trek-menu-enter {
|
||
from { opacity: 0; transform: scale(0.95) translateY(-4px); }
|
||
to { opacity: 1; transform: scale(1) translateY(0); }
|
||
}
|
||
@keyframes trek-popover-enter {
|
||
from { opacity: 0; transform: scale(0.96); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
@keyframes trek-modal-enter {
|
||
from { opacity: 0; transform: scale(0.97); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
@keyframes trek-backdrop-enter {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
@keyframes trek-toast-enter {
|
||
from { opacity: 0; transform: translateY(8px) scale(0.96); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
@keyframes trek-progress-fill {
|
||
from { width: 0%; }
|
||
to { width: var(--trek-progress-to, 0%); }
|
||
}
|
||
|
||
/* Pie-Chart Reveal — rotate + fade-in, gibt dem Kreisdiagramm ein "Draw"-Gefühl */
|
||
@keyframes trek-pie-reveal {
|
||
from { opacity: 0; transform: rotate(-90deg) scale(0.85); }
|
||
to { opacity: 1; transform: rotate(0deg) scale(1); }
|
||
}
|
||
.trek-pie-reveal {
|
||
animation: trek-pie-reveal 900ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
||
transform-origin: center;
|
||
will-change: transform, opacity;
|
||
}
|
||
|
||
/* Bar-Chart Reveal — horizontaler Fill von links */
|
||
@keyframes trek-bar-fill {
|
||
from { transform: scaleX(0); }
|
||
to { transform: scaleX(1); }
|
||
}
|
||
.trek-bar-fill {
|
||
animation: trek-bar-fill 700ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
||
transform-origin: left center;
|
||
will-change: transform;
|
||
}
|
||
|
||
/* Page-Transition — subtiler Fade-Up beim Mount */
|
||
@keyframes trek-page-enter {
|
||
from { opacity: 0; transform: translateY(6px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
.trek-page-enter {
|
||
animation: trek-page-enter 220ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
||
}
|
||
|
||
/* Skeleton shimmer — ein fließender Gradient-Strip überquert den Platzhalter */
|
||
@keyframes trek-shimmer {
|
||
from { background-position: -200% 0; }
|
||
to { background-position: 200% 0; }
|
||
}
|
||
.trek-skeleton {
|
||
background: linear-gradient(
|
||
90deg,
|
||
var(--bg-tertiary) 0%,
|
||
var(--bg-hover) 50%,
|
||
var(--bg-tertiary) 100%
|
||
);
|
||
background-size: 200% 100%;
|
||
animation: trek-shimmer 1.6s linear infinite;
|
||
border-radius: 8px;
|
||
color: transparent;
|
||
user-select: none;
|
||
}
|
||
.dark .trek-skeleton {
|
||
background: linear-gradient(
|
||
90deg,
|
||
rgba(255,255,255,0.04) 0%,
|
||
rgba(255,255,255,0.08) 50%,
|
||
rgba(255,255,255,0.04) 100%
|
||
);
|
||
background-size: 200% 100%;
|
||
}
|
||
.trek-menu-enter {
|
||
animation: trek-menu-enter 200ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
transform-origin: top right;
|
||
will-change: transform, opacity;
|
||
}
|
||
.trek-menu-enter-left {
|
||
animation: trek-menu-enter 200ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
transform-origin: top left;
|
||
will-change: transform, opacity;
|
||
}
|
||
.trek-popover-enter {
|
||
animation: trek-popover-enter 180ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
will-change: transform, opacity;
|
||
}
|
||
.trek-modal-enter {
|
||
animation: trek-modal-enter 220ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
will-change: transform, opacity;
|
||
}
|
||
|
||
/* Dokumentenabgleich: die aktive Bahn laeuft, WAEHREND ein Lauf laeuft.
|
||
Bewegung als Antwort auf eine Handlung, nicht als Dekoration: sie hoert auf,
|
||
sobald der Lauf durch ist, und sagt in der Zwischenzeit, in welche Richtung
|
||
gerade etwas unterwegs ist. */
|
||
@keyframes trek-docsync-lane {
|
||
from { background-position: 0 0; }
|
||
to { background-position: 16px 0; }
|
||
}
|
||
.trek-docsync-lane-run {
|
||
background-image: repeating-linear-gradient(
|
||
90deg,
|
||
currentColor 0 5px,
|
||
transparent 5px 16px
|
||
);
|
||
animation: trek-docsync-lane 700ms linear infinite;
|
||
}
|
||
.trek-docsync-lane-run-rev {
|
||
animation-direction: reverse;
|
||
}
|
||
|
||
/* Die abgeschaltete Richtung bekommt eine Schraffur, dasselbe Zeichen, das eine
|
||
gesperrte Flaeche ueberall traegt. Es liest sich als "hier geht nichts durch",
|
||
ohne dass die Bahn verschwindet und die Zeile zusammenklappt. */
|
||
.trek-docsync-lane-off {
|
||
background-image: repeating-linear-gradient(
|
||
-45deg,
|
||
var(--border-primary) 0 5px,
|
||
transparent 5px 20px
|
||
);
|
||
}
|
||
|
||
/* Dieselbe Schraffur im Telefon-Schema: die mobilen Flaechen kennen die
|
||
Desktop-Randfarbe nicht, und eine Fremdfarbe faellt dort sofort auf. */
|
||
.trek-docsync-lane-off-m {
|
||
background-image: repeating-linear-gradient(
|
||
-45deg,
|
||
var(--m-rowbr) 0 5px,
|
||
transparent 5px 20px
|
||
);
|
||
}
|
||
|
||
/* Mobile-Drawer-Feel — Modal slidet von unten rein, wird unten am Screen angedockt */
|
||
@keyframes trek-drawer-enter {
|
||
from { opacity: 0; transform: translateY(100%); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@media (max-width: 639px) {
|
||
.trek-modal-enter {
|
||
animation: trek-drawer-enter 320ms cubic-bezier(0.32, 0.72, 0, 1);
|
||
border-bottom-left-radius: 0 !important;
|
||
border-bottom-right-radius: 0 !important;
|
||
margin-top: auto !important;
|
||
align-self: flex-end;
|
||
}
|
||
}
|
||
.trek-backdrop-enter {
|
||
animation: trek-backdrop-enter 180ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
}
|
||
.trek-toast-enter {
|
||
animation: trek-toast-enter 260ms cubic-bezier(0.23, 1, 0.32, 1);
|
||
will-change: transform, opacity;
|
||
}
|
||
|
||
/* Roadtrip: die Zzz am Tageswechsel im Streckenstrang. Drei Buchstaben auf einer
|
||
Grundlinie, jeder ein Drittel des Laufs nach dem vorigen, damit immer einer
|
||
unterwegs ist. Rein dekorativ — das Wort daneben sagt schon, was das Band ist. */
|
||
@keyframes trek-doze {
|
||
0% { opacity: 0; transform: translate(0, 3px) scale(0.7); }
|
||
22% { opacity: 0.9; }
|
||
100% { opacity: 0; transform: translate(6px, -10px) scale(1.15); }
|
||
}
|
||
.trek-doze > * {
|
||
animation: trek-doze 3s ease-out infinite;
|
||
}
|
||
.trek-doze > *:nth-child(2) { animation-delay: 1s; }
|
||
.trek-doze > *:nth-child(3) { animation-delay: 2s; }
|
||
|
||
/* Roadtrip: der Block, den eine Nachtfahrt auf dem Folgetag hinterlaesst. Er faellt
|
||
von oben in die Karte, weil er von der Karte darueber kommt — kurz genug, dass ein
|
||
Neuberechnen der Ankunftszeiten nicht als Zappeln liest. */
|
||
@keyframes trek-spill-in {
|
||
from { opacity: 0; transform: translateY(-6px) scale(0.985); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
.trek-spill-in {
|
||
animation: trek-spill-in 320ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
||
}
|
||
|
||
/* Roadtrip: die Reserveleuchte am Band, an dem der Tank leerlaeuft. Das Gegenstueck
|
||
zu den Zzz am Tageswechsel — dort schlaeft der Tag ein, hier geht die Lampe im
|
||
Armaturenbrett an. Langsam an, kurz aus: eine Warnleuchte blinkt nicht hektisch,
|
||
sie glimmt. */
|
||
@keyframes trek-lowfuel {
|
||
0%, 62%, 100% { opacity: 1; }
|
||
74% { opacity: 0.28; }
|
||
86% { opacity: 1; }
|
||
}
|
||
.trek-lowfuel {
|
||
animation: trek-lowfuel 2.6s ease-in-out infinite;
|
||
}
|
||
|
||
/* Stagger-Helpers für Listen — Enter-Animation mit Offset */
|
||
@keyframes trek-fade-up {
|
||
from { opacity: 0; transform: translateY(6px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
.trek-stagger > * {
|
||
animation: trek-fade-up 280ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
||
}
|
||
.trek-stagger > *:nth-child(1) { animation-delay: 0ms; }
|
||
.trek-stagger > *:nth-child(2) { animation-delay: 40ms; }
|
||
.trek-stagger > *:nth-child(3) { animation-delay: 80ms; }
|
||
.trek-stagger > *:nth-child(4) { animation-delay: 120ms; }
|
||
.trek-stagger > *:nth-child(5) { animation-delay: 160ms; }
|
||
.trek-stagger > *:nth-child(6) { animation-delay: 200ms; }
|
||
.trek-stagger > *:nth-child(7) { animation-delay: 240ms; }
|
||
.trek-stagger > *:nth-child(8) { animation-delay: 280ms; }
|
||
.trek-stagger > *:nth-child(n+9) { animation-delay: 320ms; }
|
||
|
||
/* Reduced motion — Emil's Accessibility-Regel: fewer and gentler, not zero */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.trek-menu-enter, .trek-menu-enter-left, .trek-popover-enter,
|
||
.trek-modal-enter, .trek-toast-enter, .trek-stagger > *, .trek-spill-in {
|
||
animation: trek-backdrop-enter 120ms ease-out;
|
||
}
|
||
/* Die Reserveleuchte steht still statt zu glimmen — sie bleibt an, was die Aussage
|
||
ohnehin ist. */
|
||
.trek-lowfuel {
|
||
animation: none;
|
||
}
|
||
/* Die Bahn bleibt sichtbar, sie laeuft nur nicht mehr. Die Aussage
|
||
"hier ist gerade etwas unterwegs" traegt auch im Stillstand. */
|
||
.trek-docsync-lane-run {
|
||
animation: none;
|
||
}
|
||
.trek-skeleton {
|
||
animation: none;
|
||
background: var(--bg-tertiary);
|
||
}
|
||
/* Die Zzz stehen still statt zu treiben — der Tageswechsel bleibt lesbar. */
|
||
.trek-doze > * {
|
||
animation: none;
|
||
opacity: 0.6;
|
||
transform: none;
|
||
}
|
||
button:not(:disabled):not([data-no-press]):active,
|
||
[role="button"]:not([aria-disabled="true"]):not([data-no-press]):active,
|
||
[data-press]:active {
|
||
transform: none;
|
||
}
|
||
/* Parallax & lift disablen */
|
||
.group:hover img,
|
||
.group:hover .cover-img { transform: none !important; }
|
||
*:hover { translate: none !important; }
|
||
}
|
||
|
||
/* ── Design tokens ─────────────────────────────── */
|
||
:root {
|
||
/* Easing curves — stärker als die CSS-Defaults, siehe easing.dev */
|
||
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
|
||
--ease-in-out-quint: cubic-bezier(0.77, 0, 0.175, 1);
|
||
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
|
||
|
||
--safe-top: env(safe-area-inset-top, 0px);
|
||
--nav-h: 0px;
|
||
--bottom-nav-h: 0px;
|
||
|
||
/* Layer scale. Anything fixed or sticky picks a step from here instead of
|
||
inventing a number, so two overlays can no longer land in the wrong order
|
||
by accident. The steps are spaced so today's hardcoded z-indexes keep the
|
||
position they already have; each of those moves onto its token whenever
|
||
its file gets touched for other reasons. */
|
||
--z-bar: 60; /* BottomNav, page-bound sticky bars */
|
||
--z-nav: 200; /* the desktop navbar */
|
||
--z-modal: 10000; /* shared/Modal, ConfirmDialog */
|
||
--z-overlay: 99990; /* page-owned full-screen overlays */
|
||
--z-notice: 99998; /* app-wide system and release notices */
|
||
--z-toast: 100000; /* toasts, tooltips, context menus */
|
||
|
||
--font-system: 'Poppins', -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
|
||
/* Secondary "subtext"/caption tier renders in Geist; primary text + headings stay Poppins. */
|
||
--font-subtext: 'Geist Sans', 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||
--sp-1: 4px;
|
||
--sp-2: 8px;
|
||
--sp-3: 12px;
|
||
--sp-4: 16px;
|
||
--sp-6: 24px;
|
||
--radius-sm: 8px;
|
||
--radius-md: 12px;
|
||
--radius-lg: 16px;
|
||
--radius-xl: 20px;
|
||
--shadow-card: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
|
||
--shadow-elevated: 0 4px 16px rgba(0,0,0,0.1);
|
||
|
||
/* Theme colors */
|
||
--bg-primary: #ffffff;
|
||
--bg-secondary: #f8fafc;
|
||
--bg-tertiary: #f1f5f9;
|
||
--bg-elevated: rgba(250,250,250,0.82);
|
||
--bg-card: #ffffff;
|
||
--bg-input: #ffffff;
|
||
--bg-hover: rgba(0,0,0,0.03);
|
||
--bg-selected: #e2e8f0;
|
||
--text-primary: #111827;
|
||
--text-secondary: #374151;
|
||
--text-muted: #6b7280;
|
||
--text-faint: #9ca3af;
|
||
--border-primary: #e5e7eb;
|
||
--border-secondary: #f3f4f6;
|
||
--border-faint: rgba(0,0,0,0.06);
|
||
--accent: #111827;
|
||
--accent-text: #ffffff;
|
||
--sidebar-bg: rgba(250,250,250,0.82);
|
||
--sidebar-shadow: 0 4px 32px rgba(0,0,0,0.10), 0 0 0 1px rgba(0,0,0,0.06);
|
||
--tooltip-bg: rgba(255,255,255,0.96);
|
||
--scrollbar-track: #f1f5f9;
|
||
--scrollbar-thumb: #d1d5db;
|
||
--scrollbar-hover: #9ca3af;
|
||
|
||
/* ── Extended semantic tokens (appearance platform) ──────────────
|
||
Accent variants + status/shadow/overlay/inverse + per-tier type scales.
|
||
Additive: nothing consumes these until components migrate to them, so
|
||
defining them is visually inert. Colored schemes override --accent* via
|
||
[data-scheme]; status/shadow/inverse stay scheme-independent. */
|
||
--accent-on: var(--accent); /* accent as text/icon/border on a surface */
|
||
--accent-hover: #1f2937; /* hover state of an accent fill */
|
||
--accent-subtle: #f1f5f9; /* faint accent-tinted surface (chips, selected) */
|
||
|
||
/* Strength of a dayTintProvider plugin's colour in each region of a day card.
|
||
Per-theme because the tones are fixed hexes: the default tone #4F46E5 is itself a
|
||
dark colour, so a light theme's alpha makes it vanish against the dark sidebar,
|
||
while warn #f59e0b at the dark alpha would outshine the selected day. Per-region
|
||
because area and text density differ — the badge is a small bold pill, the
|
||
activity list is a large surface behind dense rows. Tuned so every region reads
|
||
above the hover step and below the selected one. */
|
||
--day-tint-badge: 16%;
|
||
--day-tint-header: 8%;
|
||
--day-tint-header-hover: 14%;
|
||
--day-tint-activity: 6%;
|
||
|
||
/* The OKLCH lightness band a plugin's OWN colour is clamped into before it is mixed,
|
||
so "any hex" cannot mean "a day nobody can read". The band brackets the four tones
|
||
above (L 0.51–0.77) rather than tightening around them: an ordinary choice passes
|
||
through untouched, and a hex equal to a tone renders exactly like that tone. Only
|
||
the extremes move — here, a near-white that would otherwise wash out against the
|
||
sidebar's own near-white surfaces.
|
||
|
||
This bounds a colour, it does not boost one: hue and chroma are never touched, so a
|
||
near-white GREY still lands as a faint tint. That is the intended trade — the
|
||
failure this must prevent is an unreadable card, not an understated one. */
|
||
--day-tint-l-min: 0.40;
|
||
--day-tint-l-max: 0.78;
|
||
|
||
--fs-scale-title: 1;
|
||
--fs-scale-subtitle: 1;
|
||
--fs-scale-body: 1;
|
||
--fs-scale-caption: 1;
|
||
|
||
--success: #16a34a; --success-soft: #dcfce7;
|
||
--danger: #dc2626; --danger-soft: #fef2f2;
|
||
--warning: #d97706; --warning-soft: #fffbeb;
|
||
--info: #2563eb; --info-soft: #eff6ff;
|
||
|
||
--bg-inverse: #111827; --text-inverse: #ffffff;
|
||
--overlay: rgba(0, 0, 0, 0.5);
|
||
|
||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.12);
|
||
--shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.20);
|
||
--shadow-dropdown: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||
--shadow-popover: 0 8px 32px rgba(0, 0, 0, 0.18);
|
||
|
||
/* Journey design tokens */
|
||
--journal-bg: #FAFAFA;
|
||
--journal-card: #FFFFFF;
|
||
--journal-border: #E4E4E7;
|
||
--journal-accent: #6366F1;
|
||
--journal-text: #09090B;
|
||
--journal-muted: #71717A;
|
||
--journal-faint: #A1A1AA;
|
||
--mood-amazing: #E8654A;
|
||
--mood-good: #EF9F27;
|
||
--mood-neutral: #94928C;
|
||
--mood-tired: #6B9BD2;
|
||
--mood-rough: #9B8EC4;
|
||
|
||
/* ── Vacay "liquid glass" facelift tokens (desktop) ──────────────
|
||
Prefixed --vg-*; only the Vacay surface consumes them. Values are
|
||
the light palette; .dark overrides below. Kept global (not scoped)
|
||
so the tablet sidebar drawer — which portals to <body> — still
|
||
resolves them in dark mode. */
|
||
--vg-ink: oklch(0.22 0.012 65);
|
||
--vg-ink2: oklch(0.42 0.012 65);
|
||
--vg-ink3: oklch(0.62 0.01 65);
|
||
--vg-bg: #f8fafc;
|
||
--vg-surf: #ffffff;
|
||
--vg-surf2: oklch(0.968 0.003 247);
|
||
--vg-line: oklch(0.92 0.008 70);
|
||
--vg-line2: oklch(0.88 0.01 70);
|
||
--vg-glass-bg: linear-gradient(135deg, oklch(1 0 0 / .72) 0%, oklch(0.99 0.006 75 / .5) 100%);
|
||
--vg-glass-br: oklch(0.88 0.008 70 / .7);
|
||
--vg-glass-sh: 0 1px 2px oklch(0.4 0.02 60 / .05), 0 12px 32px -14px oklch(0.3 0.02 60 / .2);
|
||
--vg-hi: inset 0 1px 0 oklch(1 0 0 / .8);
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
:root { --bottom-nav-h: calc(84px + env(safe-area-inset-bottom, 0px)); }
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
:root { --nav-h: calc(64px + env(safe-area-inset-top, 0px)); }
|
||
}
|
||
|
||
.dark {
|
||
--bg-primary: #121215;
|
||
--bg-secondary: #1a1a1e;
|
||
--bg-tertiary: #1c1c21;
|
||
--bg-elevated: rgba(19,19,22,0.82);
|
||
--bg-card: #131316;
|
||
--bg-input: #1c1c21;
|
||
--bg-hover: rgba(255,255,255,0.06);
|
||
--bg-selected: rgba(255,255,255,0.1);
|
||
--text-primary: #f4f4f5;
|
||
--text-secondary: #d4d4d8;
|
||
--text-muted: #a1a1aa;
|
||
--text-faint: #71717a;
|
||
--border-primary: #27272a;
|
||
--border-secondary: #1c1c21;
|
||
--border-faint: rgba(255,255,255,0.07);
|
||
--accent: #e4e4e7;
|
||
--accent-text: #09090b;
|
||
--sidebar-bg: rgba(19,19,22,0.82);
|
||
--sidebar-shadow: 0 4px 32px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.05);
|
||
--tooltip-bg: rgba(19,19,22,0.96);
|
||
--scrollbar-track: #131316;
|
||
--scrollbar-thumb: #3f3f46;
|
||
--scrollbar-hover: #52525b;
|
||
|
||
/* Extended semantic tokens (dark) */
|
||
--accent-on: var(--accent);
|
||
--accent-hover: #d4d4d8;
|
||
--accent-subtle: rgba(255, 255, 255, 0.08);
|
||
|
||
--day-tint-badge: 24%;
|
||
--day-tint-header: 13%;
|
||
--day-tint-header-hover: 20%;
|
||
--day-tint-activity: 10%;
|
||
|
||
/* Shifted up, and the reason the band is per theme at all: here the failure is a
|
||
near-BLACK tint disappearing into the dark sidebar, while a pale one reads fine.
|
||
Inherited by the mobile chips too — mobile.css sets only their alpha. */
|
||
--day-tint-l-min: 0.48;
|
||
--day-tint-l-max: 0.88;
|
||
|
||
--success: #22c55e; --success-soft: rgba(34, 197, 94, 0.15);
|
||
--danger: #ef4444; --danger-soft: rgba(239, 68, 68, 0.15);
|
||
--warning: #f59e0b; --warning-soft: rgba(245, 158, 11, 0.15);
|
||
--info: #3b82f6; --info-soft: rgba(59, 130, 246, 0.15);
|
||
|
||
--bg-inverse: #e4e4e7; --text-inverse: #09090b;
|
||
--overlay: rgba(0, 0, 0, 0.6);
|
||
|
||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
|
||
--shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.6);
|
||
--shadow-dropdown: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||
--shadow-popover: 0 8px 32px rgba(0, 0, 0, 0.55);
|
||
|
||
/* Journey design tokens (dark) */
|
||
--journal-bg: #09090B;
|
||
--journal-card: #18181B;
|
||
--journal-border: #27272A;
|
||
--journal-accent: #818CF8;
|
||
--journal-text: #FAFAFA;
|
||
--journal-muted: #A1A1AA;
|
||
--journal-faint: #52525B;
|
||
--mood-amazing: #f28a6e;
|
||
--mood-good: #f5b84d;
|
||
--mood-neutral: #9a9a94;
|
||
--mood-tired: #6db3f0;
|
||
--mood-rough: #a9a3f0;
|
||
|
||
/* Vacay "liquid glass" facelift tokens (dark) */
|
||
--vg-ink: oklch(0.96 0 0);
|
||
--vg-ink2: oklch(0.78 0 0);
|
||
--vg-ink3: oklch(0.6 0 0);
|
||
--vg-bg: oklch(0.17 0 0);
|
||
--vg-surf: oklch(0.225 0 0);
|
||
--vg-surf2: oklch(0.255 0 0);
|
||
--vg-line: oklch(0.32 0 0);
|
||
--vg-line2: oklch(0.38 0 0);
|
||
--vg-glass-bg: linear-gradient(135deg, oklch(0.31 0 0 / .58) 0%, oklch(0.25 0 0 / .42) 100%);
|
||
--vg-glass-br: oklch(1 0 0 / .1);
|
||
--vg-glass-sh: 0 1px 2px oklch(0 0 0 / .3), 0 12px 32px -14px oklch(0 0 0 / .55);
|
||
--vg-hi: inset 0 1px 0 oklch(1 0 0 / .09);
|
||
}
|
||
|
||
/* Vacay glass card + button helpers (consume --vg-* above). */
|
||
.vg-card {
|
||
background: var(--vg-glass-bg);
|
||
border: 1px solid var(--vg-glass-br);
|
||
box-shadow: var(--vg-glass-sh), var(--vg-hi);
|
||
-webkit-backdrop-filter: blur(22px) saturate(1.7);
|
||
backdrop-filter: blur(22px) saturate(1.7);
|
||
}
|
||
|
||
/* ── Color schemes (data-scheme) ──────────────────────────────────
|
||
Each non-default scheme overrides the accent family (High-Contrast also
|
||
raises neutral text/border contrast and solidifies surfaces). Light and dark
|
||
are tuned independently. The 'default' scheme sets NO attribute, so the
|
||
monochrome accent above is untouched — existing users see no change. */
|
||
|
||
:root[data-scheme="indigo"] {
|
||
--accent: #4f46e5; --accent-text: #ffffff; --accent-on: #4338ca;
|
||
--accent-hover: #4338ca; --accent-subtle: #eef2ff;
|
||
}
|
||
.dark[data-scheme="indigo"] {
|
||
--accent: #6366f1; --accent-text: #ffffff; --accent-on: #a5b4fc;
|
||
--accent-hover: #4f46e5; --accent-subtle: rgba(99, 102, 241, 0.18);
|
||
}
|
||
|
||
:root[data-scheme="teal"] {
|
||
--accent: #0d9488; --accent-text: #ffffff; --accent-on: #0f766e;
|
||
--accent-hover: #0f766e; --accent-subtle: #f0fdfa;
|
||
}
|
||
.dark[data-scheme="teal"] {
|
||
--accent: #14b8a6; --accent-text: #04211f; --accent-on: #5eead4;
|
||
--accent-hover: #0d9488; --accent-subtle: rgba(20, 184, 166, 0.18);
|
||
}
|
||
|
||
:root[data-scheme="rose"] {
|
||
--accent: #e11d48; --accent-text: #ffffff; --accent-on: #be123c;
|
||
--accent-hover: #be123c; --accent-subtle: #fff1f2;
|
||
}
|
||
.dark[data-scheme="rose"] {
|
||
--accent: #f43f5e; --accent-text: #ffffff; --accent-on: #fda4af;
|
||
--accent-hover: #e11d48; --accent-subtle: rgba(244, 63, 94, 0.18);
|
||
}
|
||
|
||
:root[data-scheme="amber"] {
|
||
--accent: #d97706; --accent-text: #ffffff; --accent-on: #b45309;
|
||
--accent-hover: #b45309; --accent-subtle: #fffbeb;
|
||
}
|
||
.dark[data-scheme="amber"] {
|
||
--accent: #f59e0b; --accent-text: #1c1917; --accent-on: #fcd34d;
|
||
--accent-hover: #d97706; --accent-subtle: rgba(245, 158, 11, 0.18);
|
||
}
|
||
|
||
:root[data-scheme="violet"] {
|
||
--accent: #7c3aed; --accent-text: #ffffff; --accent-on: #6d28d9;
|
||
--accent-hover: #6d28d9; --accent-subtle: #f5f3ff;
|
||
}
|
||
.dark[data-scheme="violet"] {
|
||
--accent: #8b5cf6; --accent-text: #ffffff; --accent-on: #c4b5fd;
|
||
--accent-hover: #7c3aed; --accent-subtle: rgba(139, 92, 246, 0.18);
|
||
}
|
||
|
||
/* High-Contrast — raises neutral text/border contrast and solidifies surfaces,
|
||
directly answering #951 / #1025 (grey-on-grey, transparency hurting legibility). */
|
||
:root[data-scheme="highContrast"] {
|
||
--accent: #1d4ed8; --accent-text: #ffffff; --accent-on: #1d4ed8;
|
||
--accent-hover: #1e40af; --accent-subtle: #dbeafe;
|
||
--text-primary: #000000; --text-secondary: #1f2937;
|
||
--text-muted: #374151; --text-faint: #4b5563;
|
||
--border-primary: #475569; --border-secondary: #94a3b8;
|
||
--border-faint: rgba(0, 0, 0, 0.35);
|
||
--bg-elevated: #ffffff; --bg-hover: #e2e8f0; --bg-selected: #cbd5e1;
|
||
--sidebar-bg: #ffffff; --tooltip-bg: #ffffff;
|
||
}
|
||
.dark[data-scheme="highContrast"] {
|
||
--accent: #60a5fa; --accent-text: #06182f; --accent-on: #93c5fd;
|
||
--accent-hover: #3b82f6; --accent-subtle: rgba(96, 165, 250, 0.2);
|
||
--text-primary: #ffffff; --text-secondary: #f1f5f9;
|
||
--text-muted: #e2e8f0; --text-faint: #cbd5e1;
|
||
--border-primary: #64748b; --border-secondary: #475569;
|
||
--border-faint: rgba(255, 255, 255, 0.35);
|
||
--bg-elevated: #131316; --bg-hover: #2a2a30; --bg-selected: #3a3a42;
|
||
--sidebar-bg: #131316; --tooltip-bg: #131316;
|
||
}
|
||
|
||
/* Custom — the user's picked accent (applyAppearance writes the inline vars;
|
||
color-mix derives hover/subtle so a single picked color yields a full set). */
|
||
:root[data-scheme="custom"] {
|
||
--accent: var(--accent-custom-light, var(--accent));
|
||
--accent-text: var(--accent-custom-text-light, #ffffff);
|
||
--accent-on: var(--accent-custom-light, var(--accent));
|
||
--accent-hover: color-mix(in srgb, var(--accent-custom-light, #4f46e5) 86%, #000);
|
||
--accent-subtle: color-mix(in srgb, var(--accent-custom-light, #4f46e5) 12%, transparent);
|
||
}
|
||
.dark[data-scheme="custom"] {
|
||
--accent: var(--accent-custom-dark, var(--accent));
|
||
--accent-text: var(--accent-custom-text-dark, #ffffff);
|
||
--accent-on: var(--accent-custom-dark, var(--accent));
|
||
--accent-hover: color-mix(in srgb, var(--accent-custom-dark, #6366f1) 84%, #fff);
|
||
--accent-subtle: color-mix(in srgb, var(--accent-custom-dark, #6366f1) 20%, transparent);
|
||
}
|
||
|
||
/* ── Legacy accent bridge (gated) ─────────────────────────────────
|
||
Under a NON-default scheme ONLY, redirect the hardcoded "primary/black"
|
||
action surfaces (and raw indigo accents) to the chosen accent so un-migrated
|
||
components follow the scheme without editing ~50 files. These selectors carry
|
||
higher specificity than the legacy dark shim, so they win under a scheme; on
|
||
the default scheme nothing here matches (zero change for existing users).
|
||
TEMPORARY scaffold — the Phase-8 codemod migrates these to bg-accent and this
|
||
block shrinks file-by-file until it is deleted. */
|
||
html[data-scheme]:not([data-scheme="default"]) :is(.bg-slate-900, .bg-gray-900, .bg-zinc-900, .bg-neutral-900, .bg-black) {
|
||
background-color: var(--accent) !important;
|
||
color: var(--accent-text) !important;
|
||
}
|
||
html[data-scheme]:not([data-scheme="default"]) :is(.bg-slate-900, .bg-gray-900, .bg-zinc-900, .bg-neutral-900, .bg-black):hover {
|
||
background-color: var(--accent-hover) !important;
|
||
}
|
||
html[data-scheme]:not([data-scheme="default"])
|
||
:is(.hover\:bg-slate-900, .hover\:bg-slate-800, .hover\:bg-slate-700, .hover\:bg-gray-900, .hover\:bg-gray-800, .hover\:bg-black):hover {
|
||
background-color: var(--accent-hover) !important;
|
||
color: var(--accent-text) !important;
|
||
}
|
||
html[data-scheme]:not([data-scheme="default"]) :is(.bg-indigo-500, .bg-indigo-600, .bg-indigo-700) {
|
||
background-color: var(--accent) !important;
|
||
color: var(--accent-text) !important;
|
||
}
|
||
html[data-scheme]:not([data-scheme="default"]) :is(.text-indigo-400, .text-indigo-500, .text-indigo-600, .text-indigo-700) {
|
||
color: var(--accent-on) !important;
|
||
}
|
||
html[data-scheme]:not([data-scheme="default"])
|
||
:is(.border-indigo-500, .border-indigo-600, .ring-indigo-500, .ring-indigo-600) {
|
||
border-color: var(--accent) !important;
|
||
}
|
||
|
||
/* ── Transparency off (data-no-transparency) ──────────────────────
|
||
Solidify the alpha-bearing surface tokens, flatten the dashboard liquid-glass,
|
||
and disable backdrop blur app-wide so content over the map is fully opaque
|
||
(#1025). Inert unless the attribute is set. Intentional dimming scrims (modal
|
||
backdrops) stay translucent on purpose — forcing them opaque blacks out the
|
||
screen. */
|
||
html[data-no-transparency] {
|
||
--bg-elevated: #fafafa;
|
||
--bg-hover: #f1f3f5;
|
||
--bg-selected: #e2e8f0;
|
||
--border-faint: #e5e7eb;
|
||
--sidebar-bg: #ffffff;
|
||
--tooltip-bg: #ffffff;
|
||
}
|
||
html.dark[data-no-transparency] {
|
||
--bg-elevated: #131316;
|
||
--bg-hover: #26262c;
|
||
--bg-selected: #2f2f37;
|
||
--border-faint: #27272a;
|
||
--sidebar-bg: #131316;
|
||
--tooltip-bg: #131316;
|
||
}
|
||
html[data-no-transparency] .trek-dash {
|
||
--glass-bg: var(--surface, var(--bg-card));
|
||
--glass-border: var(--line, var(--border-primary));
|
||
--glass-blur: none;
|
||
--glass-highlight: none;
|
||
}
|
||
/* The boarding-pass hero ticket uses its own translucent gradient (not the glass
|
||
tokens) — force it solid. */
|
||
html[data-no-transparency] .trek-dash .hero-pass {
|
||
background: var(--surface, var(--bg-card)) !important;
|
||
}
|
||
html[data-no-transparency] .trek-dash .hero-pass::before { display: none !important; }
|
||
html[data-no-transparency] *,
|
||
html[data-no-transparency] *::before,
|
||
html[data-no-transparency] *::after {
|
||
backdrop-filter: none !important;
|
||
-webkit-backdrop-filter: none !important;
|
||
}
|
||
/* Class-based translucent surfaces (Atlas tooltip, Leaflet controls, GL popups)
|
||
forced solid. Inline rgba() is handled per-component (it can't be reached by CSS). */
|
||
html[data-no-transparency] .atlas-tooltip {
|
||
background: var(--bg-card) !important;
|
||
color: var(--text-primary) !important;
|
||
border-color: var(--border-primary) !important;
|
||
}
|
||
html[data-no-transparency] .atlas-tooltip::before { border-top-color: var(--bg-card) !important; }
|
||
html[data-no-transparency] .leaflet-control-zoom a { background: var(--bg-card) !important; }
|
||
html[data-no-transparency] .map-tooltip,
|
||
html[data-no-transparency] .leaflet-tooltip.map-tooltip { background: var(--bg-card) !important; }
|
||
html[data-no-transparency] .trek-map-popup .mapboxgl-popup-content,
|
||
html[data-no-transparency] .trek-map-popup .maplibregl-popup-content { background: var(--bg-card) !important; }
|
||
|
||
/* ── Compact density ──────────────────────────────────────────────
|
||
Tightens the spacing scale; reach grows as components adopt the tokens. */
|
||
html[data-density="compact"] {
|
||
--sp-1: 3px;
|
||
--sp-2: 6px;
|
||
--sp-3: 9px;
|
||
--sp-4: 12px;
|
||
--sp-6: 18px;
|
||
}
|
||
html[data-density="compact"] .form-input { padding: 5px 11px; }
|
||
|
||
body {
|
||
font-family: var(--font-system);
|
||
background-color: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
transition: background-color 0.2s, color 0.2s;
|
||
}
|
||
|
||
/* Subtext tier in Geist. The faint text token is TREK's caption/secondary tier;
|
||
a direct rule on the element beats the Poppins inherited from wrapper styles,
|
||
giving the design's "Geist text · Poppins numbers" hierarchy. */
|
||
.text-content-faint { font-family: var(--font-subtext); }
|
||
|
||
/* ── Marker cluster custom styling ────────────── */
|
||
.marker-cluster-wrapper {
|
||
background: transparent !important;
|
||
border: none !important;
|
||
}
|
||
|
||
/* A marker being dragged onto a day (#891): it stays under the cursor as the
|
||
browser's drag image, so what is left behind is dimmed to show it is in
|
||
flight rather than still sitting there. */
|
||
.marker-dragging {
|
||
opacity: 0.4;
|
||
}
|
||
|
||
.marker-cluster-custom {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
background: #111827;
|
||
border: 2.5px solid rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25), 0 0 0 2px rgba(17, 24, 39, 0.15);
|
||
cursor: pointer;
|
||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
|
||
.marker-cluster-custom:hover {
|
||
transform: scale(1.1);
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35), 0 0 0 3px rgba(17, 24, 39, 0.2);
|
||
}
|
||
|
||
.marker-cluster-custom span {
|
||
font-family:var(--font-system);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: #ffffff;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* Hide default markercluster styles we don't need */
|
||
.marker-cluster-small,
|
||
.marker-cluster-medium,
|
||
.marker-cluster-large {
|
||
background: transparent !important;
|
||
}
|
||
.marker-cluster-small div,
|
||
.marker-cluster-medium div,
|
||
.marker-cluster-large div {
|
||
background: transparent !important;
|
||
}
|
||
|
||
/* ── Leaflet z-index ───────────────────────────── */
|
||
.leaflet-container {
|
||
z-index: 0;
|
||
}
|
||
.leaflet-pane {
|
||
z-index: 0 !important;
|
||
}
|
||
.leaflet-top, .leaflet-bottom {
|
||
z-index: 1 !important;
|
||
}
|
||
|
||
/* ── iOS-style map tooltip ─────────────────────── */
|
||
.leaflet-tooltip.map-tooltip {
|
||
background: var(--tooltip-bg);
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border: none;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||
padding: 5px 10px;
|
||
font-family: var(--font-system);
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
pointer-events: none;
|
||
color: var(--text-primary);
|
||
}
|
||
.leaflet-tooltip.map-tooltip::before,
|
||
.leaflet-tooltip-left.map-tooltip::before,
|
||
.leaflet-tooltip-top.map-tooltip::before {
|
||
display: none;
|
||
}
|
||
|
||
/* Scrollbars — styled on desktop, hidden on mobile */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
::-webkit-scrollbar-button {
|
||
display: none;
|
||
height: 0;
|
||
width: 0;
|
||
}
|
||
::-webkit-scrollbar-track {
|
||
background: var(--scrollbar-track);
|
||
border-radius: 3px;
|
||
}
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--scrollbar-thumb);
|
||
border-radius: 3px;
|
||
}
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--scrollbar-hover);
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
* { scrollbar-width: none; }
|
||
::-webkit-scrollbar { width: 0; height: 0; }
|
||
}
|
||
|
||
.route-info-pill { background: none !important; border: none !important; box-shadow: none !important; width: auto !important; height: auto !important; margin: 0 !important; }
|
||
.chat-scroll { overflow-y: auto !important; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
|
||
.chat-scroll::-webkit-scrollbar { width: 0; background: transparent; }
|
||
|
||
|
||
/* Einheitliche Formular-Inputs */
|
||
.form-input {
|
||
width: 100%;
|
||
border: 1px solid var(--border-primary);
|
||
border-radius: 10px;
|
||
padding: 8px 14px;
|
||
font-size: 13px;
|
||
font-family: inherit;
|
||
outline: none;
|
||
box-sizing: border-box;
|
||
color: var(--text-primary);
|
||
background: var(--bg-input);
|
||
transition: border-color 0.15s;
|
||
}
|
||
.form-input:focus {
|
||
border-color: var(--text-faint);
|
||
}
|
||
.form-input::placeholder {
|
||
color: var(--text-faint);
|
||
}
|
||
|
||
/* Brand images: no save/copy/drag */
|
||
img[alt="TREK"] {
|
||
pointer-events: none;
|
||
user-select: none;
|
||
-webkit-user-select: none;
|
||
-webkit-user-drag: none;
|
||
-webkit-touch-callout: none;
|
||
}
|
||
|
||
/* Weiche Übergänge */
|
||
.transition-smooth {
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
|
||
|
||
/* Tagesliste */
|
||
.day-list-item {
|
||
transition: background-color 0.15s ease;
|
||
}
|
||
|
||
.day-list-item:hover {
|
||
background-color: var(--bg-secondary);
|
||
}
|
||
|
||
.day-list-item.active {
|
||
background-color: #4f46e5;
|
||
color: white;
|
||
}
|
||
|
||
.scroll-container {
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||
}
|
||
|
||
/* ── Dark-Mode: Tailwind-Klassen ─────────────── */
|
||
.dark .bg-slate-50 { background-color: #131316 !important; }
|
||
.dark .bg-slate-100 { background-color: #1c1c21 !important; }
|
||
.dark .bg-white { background-color: #09090b !important; }
|
||
.dark .bg-gray-50 { background-color: #131316 !important; }
|
||
.dark .bg-gray-100 { background-color: #1c1c21 !important; }
|
||
.dark .text-slate-900, .dark .text-gray-900, .dark .text-slate-800 { color: #f4f4f5 !important; }
|
||
.dark .text-slate-700, .dark .text-gray-700 { color: #d4d4d8 !important; }
|
||
.dark .text-slate-600, .dark .text-gray-600 { color: #a1a1aa !important; }
|
||
.dark .text-slate-500, .dark .text-gray-500 { color: #a1a1aa !important; }
|
||
.dark .text-slate-400, .dark .text-gray-400 { color: #71717a !important; }
|
||
.dark .border-slate-200, .dark .border-gray-200, .dark .border-slate-300 { border-color: #27272a !important; }
|
||
.dark .border-gray-100, .dark .border-slate-100 { border-color: #1c1c21 !important; }
|
||
.dark .hover\:bg-slate-50:hover, .dark .hover\:bg-gray-50:hover { background-color: #1c1c21 !important; }
|
||
.dark .bg-slate-50\/60 { background-color: transparent !important; }
|
||
.dark .divide-slate-100 > :not([hidden]) ~ :not([hidden]) { border-color: #27272a !important; }
|
||
.dark .bg-slate-100 { background-color: #27272a !important; }
|
||
.dark .bg-slate-900 { background-color: #e4e4e7 !important; color: #09090b !important; }
|
||
.dark .hover\:bg-slate-700:hover { background-color: #d4d4d8 !important; color: #09090b !important; }
|
||
.dark input, .dark textarea, .dark select {
|
||
background-color: #1c1c21;
|
||
color: #f4f4f5;
|
||
border-color: #27272a;
|
||
}
|
||
.dark input::placeholder, .dark textarea::placeholder { color: #71717a; }
|
||
.dark .bg-emerald-50 { background-color: rgba(16,185,129,0.15) !important; }
|
||
.dark .bg-red-50 { background-color: rgba(239,68,68,0.15) !important; }
|
||
|
||
/* Ladebildschirm */
|
||
.dark .min-h-screen { background-color: var(--bg-primary) !important; }
|
||
|
||
/* Modal-Hintergrund */
|
||
.dark .trek-modal-backdrop { background: rgba(0,0,0,0.6); }
|
||
|
||
/* ── Dark: Fallback für Komponenten die noch nicht auf CSS-Variablen umgestellt sind ── */
|
||
.dark {
|
||
color-scheme: dark;
|
||
}
|
||
|
||
|
||
/* Toast-Animationen */
|
||
@keyframes slideUp {
|
||
from { transform: translateY(100%); }
|
||
to { transform: translateY(0); }
|
||
}
|
||
|
||
@keyframes slide-in-right {
|
||
from { transform: translateX(100%); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
|
||
@keyframes slide-out-right {
|
||
from { transform: translateX(0); opacity: 1; }
|
||
to { transform: translateX(100%); opacity: 0; }
|
||
}
|
||
|
||
.toast-enter {
|
||
animation: slide-in-right 0.3s ease-out forwards;
|
||
}
|
||
|
||
.toast-exit {
|
||
animation: slide-out-right 0.3s ease-in forwards;
|
||
}
|
||
|
||
/* Modal-Hintergrund (eigener Namespace, sonst blenden Content-Blocker .modal-backdrop aus) */
|
||
.trek-modal-backdrop {
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
|
||
/* Fortschrittsbalken */
|
||
@keyframes progress-fill {
|
||
from { width: 0; }
|
||
to { width: var(--progress); }
|
||
}
|
||
|
||
/* Anmeldeseiten-Gradient */
|
||
.auth-gradient {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
}
|
||
|
||
/* Bildanzeige-Overlay */
|
||
.lightbox-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.95);
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* Shared links in the Collab tab: one chip per link, two to a row across the full
|
||
width, one to a row where the panel is narrow. The chip is the link; edit, pin and
|
||
delete sit quietly in its tail and come forward on hover or keyboard focus. They
|
||
keep their width throughout: unfolding them reflowed the row under the pointer,
|
||
and a chip that grows as you reach for it is a chip you cannot catch. */
|
||
.collab-link-grid {
|
||
container-type: inline-size;
|
||
}
|
||
.collab-link-grid__cells {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 8px;
|
||
}
|
||
@container (max-width: 420px) {
|
||
.collab-link-grid__cells { grid-template-columns: minmax(0, 1fr); }
|
||
}
|
||
.collab-link-chip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
padding: 5px 8px 5px 5px;
|
||
border-radius: 999px;
|
||
background: var(--bg-secondary);
|
||
border: 1px solid var(--border-faint);
|
||
box-shadow: var(--shadow-sm);
|
||
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
|
||
}
|
||
.collab-link-chip:hover, .collab-link-chip:focus-within {
|
||
transform: translateY(-1px);
|
||
box-shadow: var(--shadow-md);
|
||
border-color: var(--border-primary);
|
||
}
|
||
.collab-link-chip--pinned {
|
||
background: var(--accent-subtle);
|
||
border-color: color-mix(in srgb, var(--accent) 35%, transparent);
|
||
}
|
||
.collab-link-chip__main {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
color: var(--text-primary);
|
||
text-decoration: none;
|
||
outline: none;
|
||
}
|
||
.collab-link-chip__icon {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
width: 24px;
|
||
height: 24px;
|
||
flex: 0 0 24px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
background: var(--bg-card);
|
||
color: var(--text-faint);
|
||
}
|
||
.collab-link-chip__icon img { width: 16px; height: 16px; object-fit: contain; }
|
||
.collab-link-chip__icon > * { transition: opacity 0.15s ease; }
|
||
/* Reaching for the chip turns its favicon into the open-in-new-tab glyph: the
|
||
affordance without a glyph of its own taking room from the title. */
|
||
.collab-link-chip__go {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: grid;
|
||
place-items: center;
|
||
color: var(--text-primary);
|
||
opacity: 0;
|
||
}
|
||
.collab-link-chip__main:hover .collab-link-chip__go, .collab-link-chip__main:focus-visible .collab-link-chip__go { opacity: 1; }
|
||
.collab-link-chip__main:hover .collab-link-chip__icon > :not(.collab-link-chip__go),
|
||
.collab-link-chip__main:focus-visible .collab-link-chip__icon > :not(.collab-link-chip__go) { opacity: 0; }
|
||
/* Title first, host with what is left: a max-content track takes its full width
|
||
before the fr track sees any, and shrinks only once the title alone no longer
|
||
fits. Two flex items shrank together and both ended in an ellipsis. */
|
||
.collab-link-chip__text {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
|
||
align-items: baseline;
|
||
column-gap: 6px;
|
||
min-width: 0;
|
||
flex: 1 1 auto;
|
||
}
|
||
.collab-link-chip__title {
|
||
font-size: calc(12.5px * var(--fs-scale-body, 1));
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
min-width: 0;
|
||
}
|
||
.collab-link-chip__host {
|
||
font-size: calc(10.5px * var(--fs-scale-caption, 1));
|
||
color: var(--text-faint);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
min-width: 0;
|
||
}
|
||
.collab-link-chip__actions {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
gap: 1px;
|
||
margin-left: auto;
|
||
opacity: 0.45;
|
||
transition: opacity 0.15s ease;
|
||
}
|
||
.collab-link-chip:hover .collab-link-chip__actions, .collab-link-chip:focus-within .collab-link-chip__actions { opacity: 1; }
|
||
.collab-link-chip__action {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 22px;
|
||
height: 22px;
|
||
border: 0;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
color: var(--text-faint);
|
||
cursor: pointer;
|
||
transition: background-color 0.15s ease, color 0.15s ease;
|
||
}
|
||
.collab-link-chip__action:hover, .collab-link-chip__action:focus-visible { background: var(--bg-hover); color: var(--text-primary); }
|
||
.collab-link-chip__action--on { color: var(--accent); }
|
||
/* Fingers, not a pointer: the phone Collab tab mounts this same chip. 22px targets
|
||
a pixel apart put delete one slip away from pin, so the hit box grows to the
|
||
30px at a 34px pitch the phone Notes tab uses. The vertical margin gives the
|
||
height back, the chip stays 36px. */
|
||
@media (hover: none) {
|
||
.collab-link-chip__actions { opacity: 1; gap: 4px; }
|
||
.collab-link-chip__action { width: 30px; height: 30px; margin: -4px 0; }
|
||
}
|
||
|
||
/* Markdown in Collab Notes */
|
||
.collab-note-md strong, .collab-note-md-full strong { font-weight: 700 !important; }
|
||
.collab-note-md em, .collab-note-md-full em { font-style: italic !important; }
|
||
.collab-note-md h1, .collab-note-md h2, .collab-note-md h3 { font-weight: 700 !important; margin: 0; }
|
||
.collab-note-md-full h1 { font-size: 1.3em !important; font-weight: 700 !important; margin: 0.8em 0 0.3em; }
|
||
.collab-note-md-full h2 { font-size: 1.15em !important; font-weight: 700 !important; margin: 0.8em 0 0.3em; }
|
||
.collab-note-md-full h3 { font-size: 1em !important; font-weight: 700 !important; margin: 0.8em 0 0.3em; }
|
||
.collab-note-md p, .collab-note-md-full p { margin: 0 0 0.3em; }
|
||
.collab-note-md ul, .collab-note-md-full ul { list-style-type: disc !important; padding-left: 1.4em !important; margin: 0.2em 0; }
|
||
.collab-note-md ol, .collab-note-md-full ol { list-style-type: decimal !important; padding-left: 1.4em !important; margin: 0.2em 0; }
|
||
.collab-note-md li, .collab-note-md-full li { display: list-item !important; margin: 0.1em 0; }
|
||
.collab-note-md code, .collab-note-md-full code { font-size: 0.9em; padding: 1px 5px; border-radius: 4px; background: var(--bg-secondary); }
|
||
.collab-note-md-full pre { padding: 10px 12px; border-radius: 8px; background: var(--bg-secondary); overflow-x: auto; margin: 0.5em 0; }
|
||
.collab-note-md-full pre code { padding: 0; background: none; }
|
||
.collab-note-md a, .collab-note-md-full a { color: #3b82f6; text-decoration: underline; word-break: break-all; }
|
||
.collab-note-md blockquote, .collab-note-md-full blockquote { border-left: 3px solid var(--border-primary); padding-left: 12px; margin: 0.5em 0; color: var(--text-muted); }
|
||
.collab-note-md-full table { border-collapse: collapse; width: 100%; margin: 0.5em 0; }
|
||
.collab-note-md-full th, .collab-note-md-full td { border: 1px solid var(--border-primary); padding: 4px 8px; font-size: 0.9em; }
|
||
.collab-note-md-full hr { border: none; border-top: 1px solid var(--border-primary); margin: 0.8em 0; }
|
||
|
||
/* Day-plan header action grid (edit / +transport / note / collapse) */
|
||
.dp-day-actions button {
|
||
color: var(--text-faint);
|
||
background: transparent;
|
||
transition: background-color 0.12s ease, color 0.12s ease;
|
||
}
|
||
.dp-day-actions button:hover {
|
||
background: var(--bg-hover);
|
||
color: var(--text-primary);
|
||
}
|
||
/* Reveal the action grid only when hovering the day row (pointer devices).
|
||
Touch devices (hover: none) keep it visible; the selected day stays visible too. */
|
||
@media (hover: hover) {
|
||
.dp-day-actions { opacity: 0; transition: opacity 0.12s ease; }
|
||
.dp-day-header:hover .dp-day-actions,
|
||
.dp-day-header[data-selected="true"] .dp-day-actions { opacity: 1; }
|
||
}
|
||
|
||
/* Planner rows a finger can pick up (#1616). A long press is what arms the drag,
|
||
and that is the same gesture the browser answers with a text selection and a
|
||
callout — "the action is interpreted as a text selection" in the report. Only
|
||
on coarse pointers, and only inside the two panes that opt into the bridge. */
|
||
@media (pointer: coarse) {
|
||
[data-touch-drag] [draggable='true'] {
|
||
-webkit-touch-callout: none;
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
}
|
||
}
|
||
|
||
/* ── TREK range slider — thin filled track + soft round thumb ─────────────── */
|
||
.trek-range {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
height: 5px;
|
||
border-radius: 999px;
|
||
background: linear-gradient(to right, var(--accent) var(--fill, 50%), var(--border-faint) var(--fill, 50%));
|
||
outline: none;
|
||
cursor: pointer;
|
||
}
|
||
.trek-range::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: var(--bg-card, #fff);
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
|
||
transition: transform 0.12s ease;
|
||
}
|
||
.trek-range::-webkit-slider-thumb:hover { transform: scale(1.1); }
|
||
.trek-range::-webkit-slider-thumb:active { transform: scale(1.18); }
|
||
.trek-range::-moz-range-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: var(--bg-card, #fff);
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
|
||
}
|
||
.trek-range::-moz-range-track { height: 5px; border-radius: 999px; background: transparent; }
|