mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
62bc7ff8fd
* Open a running trip on today Asked for in discussion #1567: during a trip, opening the planner starts at day 1 and you scroll to find out what is planned for today. The mobile shell has done this since the plan tab was built - it seeds the active day with today when the trip is running. The desktop day plan never did, so the same trip behaved differently depending on the screen you opened it on. It now selects today's day, expands it and scrolls it into view, once per mount and only while nothing is selected yet, so a deep link or a day picked before switching tabs still wins. The date comparison moved into one helper both sides use. It is deliberately local rather than toISOString(): 08:00 in Tokyo is still yesterday in UTC, and a trip day is a calendar day where the traveller is standing. Also fixes a note losing its colour when dragged to another day. A move is a delete plus a create, and the create was still sending the three fields notes had before colours existed. * Open a collection place in a map app Asked for in discussion #1455: a saved place is somewhere you intend to go, but the only way to get directions to one was to add it to a trip first, where the button has been for a while. The collection detail gets the same picker off the same helper: Google Maps, Waze, Apple Maps and OpenStreetMap, filtered by what the place can actually be located with. A place with coordinates offers all of them; a place typed in by hand with just a name and an address still offers the two that take a search string, and a place with neither shows no button at all. It sits on the meta line beside the address, quieter than the link chips below it, because it is an action on the place rather than a piece of it. * Drag a place off the map onto a day Asked for in discussion #891: assigning a place means finding it again in the sidebar, even though it is right there under the cursor on the map. The drop half already existed and did not need touching. Every day, every row and every gap between rows in the day plan reads a placeId and calls onAssignToDay with the position it was dropped at, complete with the toast and the undo entry. What was missing was a second place to pick a place UP from - the only one was the row in the places sidebar. Markers become that second source. Both renderers build their markers as real DOM nodes, so one helper covers Leaflet and the GL variants alike, and it sets the same two channels the sidebar row does: dataTransfer for the drop, and window.__dragData because browsers hide dataTransfer while the pointer is merely hovering. The day plan cannot tell the two apart. Not offered on touch: HTML5 drag is a pointer feature, and on a phone the day plan is not on screen next to the map anyway. * Show the landing time in the PDF Asked for in discussion #1310: the export prints a flight's departure and nothing else, so a reader cannot tell how much of the day is left for anything after it. The day plan has drawn both times as "14:30 - 16:45" for a while; the export never got the rule. Now it does, for any transport that begins and ends on the same day - a train and a ferry read the same way a flight does. Across a span it stays as it was: the arrival belongs to the arrival day, which already prints it as its own time, and repeating it on the departure day would put tomorrow's clock next to today's departure.
1278 lines
49 KiB
CSS
1278 lines
49 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; }
|
||
|
||
|
||
/* 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; }
|
||
|
||
/* 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;
|
||
}
|
||
.atlas-tooltip::before { border-top-color: rgba(10, 10, 20, 0.6) !important; }
|
||
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, and drag reorder is
|
||
off wherever the pointer is coarse (#1432), so these buttons are the only affordance left. */
|
||
.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;
|
||
}
|
||
.note-edit-buttons {
|
||
opacity: 1 !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. */
|
||
button:not(:disabled):not([data-no-press]),
|
||
[role="button"]:not([aria-disabled="true"]):not([data-no-press]) {
|
||
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]):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;
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* 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 > * {
|
||
animation: trek-backdrop-enter 120ms ease-out;
|
||
}
|
||
.trek-skeleton {
|
||
animation: none;
|
||
background: var(--bg-tertiary);
|
||
}
|
||
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;
|
||
--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;
|
||
}
|
||
|
||
/* 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; }
|
||
|
||
/* Note card actions: stacked beside the card, and big enough to aim at. */
|
||
.note-edit-btn {
|
||
transition: background-color 0.12s ease, color 0.12s ease;
|
||
}
|
||
.note-edit-btn:hover {
|
||
background: var(--bg-hover);
|
||
color: var(--text-primary);
|
||
}
|
||
.note-edit-btn-danger:hover {
|
||
background: rgba(220, 38, 38, 0.13);
|
||
color: #dc2626;
|
||
}
|
||
|
||
/* 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; }
|