Files
Tanken/public/style.css
T

6211 lines
142 KiB
CSS

/* === Reset & Base === */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--safe-bottom: env(safe-area-inset-bottom, 0px);
--tab-height: 56px;
/* Light mode (default) */
--color-text: #000;
--color-text-2: #3c3c43;
--color-bg: #fff;
--color-bg-secondary: #f2f2f7;
--color-bg-elevated: #ffffff;
--color-hint: #8e8e93;
--color-separator: rgba(60,60,67,0.12);
--color-accent: #007aff;
--color-accent-text: #fff;
--color-favorite: #ffb800;
--color-good: #34c759;
--color-okay: #ff9500;
--color-bad: #ff3b30;
/* Spacing scale — every padding/margin/gap should snap to this. */
--sp-1: 4px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 20px;
--sp-6: 24px;
--sp-7: 32px;
--sp-8: 40px;
/* Border-radius scale. r-md is the workhorse for cards/inputs;
r-pill is for chips and the round status pills. */
--r-xs: 6px;
--r-sm: 8px;
--r-md: 12px;
--r-lg: 16px;
--r-xl: 20px;
--r-pill: 999px;
/* Elevation — light mode keeps shadows soft so the secondary surface
stays the primary visual anchor on cards. */
--shadow-1: 0 1px 2px rgba(0,0,0,0.04), 0 1px 3px rgba(0,0,0,0.05);
--shadow-2: 0 2px 8px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04);
--shadow-3: 0 6px 18px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06);
--shadow-4: 0 16px 48px rgba(0,0,0,0.16), 0 4px 12px rgba(0,0,0,0.08);
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-text: #ffffff;
--color-text-2: #ebebf5;
--color-bg: #000000;
--color-bg-secondary: #1c1c1e;
--color-bg-elevated: #2c2c2e;
--color-hint: #98989e;
--color-separator: rgba(255,255,255,0.1);
--color-accent: #0a84ff;
--color-accent-text: #fff;
--color-favorite: #ffb800;
--shadow-1: 0 1px 2px rgba(0,0,0,0.4);
--shadow-2: 0 2px 8px rgba(0,0,0,0.35);
--shadow-3: 0 6px 18px rgba(0,0,0,0.45);
--shadow-4: 0 16px 48px rgba(0,0,0,0.55);
}
}
html[data-theme="dark"] {
--color-text: #ffffff;
--color-text-2: #ebebf5;
--color-bg: #000000;
--color-bg-secondary: #1c1c1e;
--color-bg-elevated: #2c2c2e;
--color-hint: #98989e;
--color-separator: rgba(255,255,255,0.1);
--color-accent: #0a84ff;
--color-accent-text: #fff;
--shadow-1: 0 1px 2px rgba(0,0,0,0.4);
--shadow-2: 0 2px 8px rgba(0,0,0,0.35);
--shadow-3: 0 6px 18px rgba(0,0,0,0.45);
--shadow-4: 0 16px 48px rgba(0,0,0,0.55);
color-scheme: dark;
}
html[data-theme="light"] {
color-scheme: light;
}
html, body {
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
font-size: 15px;
line-height: 1.4;
color: var(--color-text);
background: var(--color-bg);
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent;
-webkit-overflow-scrolling: touch;
}
/* === Accessibility: Focus-visible === */
:where(button, a, [role="button"], input, select, textarea):focus { outline: none; }
:where(button, a, [role="button"]):focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
border-radius: 6px;
}
:where(input, select, textarea):focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 1px;
}
/* === Accessibility: Reduced motion === */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* === App Container === */
#app {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: calc(var(--tab-height) + var(--safe-bottom));
overflow: hidden;
}
/* === Tab Views === */
.tab-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
opacity: 0;
visibility: hidden;
transform: translateY(6px) scale(0.985);
pointer-events: none;
/* Outgoing: fade out quickly, then hide */
transition: opacity 0.18s ease-in,
transform 0.18s ease-in,
visibility 0s linear 0.18s;
will-change: opacity, transform;
}
.tab-view.active {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
pointer-events: auto;
/* Incoming: wait for outgoing to clear, then ease in with a soft decel */
transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1) 0.08s,
transform 0.38s cubic-bezier(0.22, 1, 0.36, 1) 0.08s,
visibility 0s linear 0s;
}
/* === Sections (iOS Settings Style) === */
.section {
padding: 0 var(--sp-4);
margin-bottom: var(--sp-3);
}
.section-header {
font-size: 13px;
font-weight: 600;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.6px;
padding: var(--sp-5) var(--sp-1) var(--sp-2);
display: flex;
align-items: center;
gap: var(--sp-2);
}
.section-sub-header {
font-size: 11px;
font-weight: 600;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.6px;
padding: var(--sp-5) var(--sp-1) var(--sp-2);
}
.section-hint {
margin: 0 var(--sp-1) var(--sp-3);
font-size: 12px;
line-height: 1.5;
color: var(--color-hint);
}
.sync-badge {
display: none;
align-items: center;
justify-content: center;
color: #fff;
opacity: 0.45;
cursor: help;
width: 14px;
height: 14px;
position: relative;
transition: opacity 0.25s ease, color 0.3s ease;
}
body.logged-in .sync-badge {
display: inline-flex;
}
.sync-badge svg {
position: absolute;
left: 0;
top: 0;
transition: opacity 0.3s ease;
}
.sync-badge .sync-icon-idle { opacity: 1; }
.sync-badge .sync-icon-ok { opacity: 0; }
/* The rotating arrow caused visible wobble because the icon is asymmetric;
sync state is now signalled by an opacity pulse on the cloud itself, so
the spin icon is permanently hidden (markup kept for back-compat). */
.sync-badge .sync-icon-spin { display: none; }
/* Syncing: cloud stays put and pulses gently. No rotation, no swap. */
.sync-badge.syncing { opacity: 1; }
.sync-badge.syncing .sync-icon-idle {
animation: sync-pulse 0.7s ease-in-out infinite;
}
/* Synced: cloud crossfades into the cloud+check variant with a green tint,
plus a tiny pop so success feels noticed. */
.sync-badge.synced .sync-icon-idle { opacity: 0; }
.sync-badge.synced .sync-icon-ok {
opacity: 1;
animation: sync-check-pop 0.28s ease-out;
}
.sync-badge.synced {
opacity: 0.95;
color: #34c759;
}
@keyframes sync-pulse {
0%, 100% { opacity: 0.55; }
50% { opacity: 1; }
}
@keyframes sync-check-pop {
0% { transform: scale(0.7); }
60% { transform: scale(1.08); }
100% { transform: scale(1); }
}
.cloud-sync-hint {
display: flex;
align-items: center;
gap: var(--sp-2);
margin: var(--sp-3) var(--sp-4) var(--sp-1);
padding: var(--sp-2) var(--sp-3);
border-radius: var(--r-sm);
background: var(--color-bg-secondary);
font-size: 12px;
color: var(--color-hint);
line-height: 1.4;
}
.cloud-sync-hint svg {
flex-shrink: 0;
opacity: 0.5;
}
body.logged-in .cloud-sync-hint {
display: none;
}
/* === Cards (shadcn-style: 1 px border + subtle background) === */
.card {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-3) var(--sp-4);
margin-bottom: var(--sp-2);
}
.card-list {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
overflow: hidden;
margin-bottom: var(--sp-2);
}
.card-list .card-row {
padding: var(--sp-3) var(--sp-4);
border-bottom: 0.5px solid var(--color-separator);
}
/* Inline card header (used inside a .card to act as a section title for the
card's contents). Title is bigger and more emphatic than .section-header,
the description sits muted right beneath. */
.card-head {
display: flex;
flex-direction: column;
gap: var(--sp-1);
padding-bottom: var(--sp-3);
margin-bottom: var(--sp-3);
border-bottom: 1px solid var(--color-separator);
}
.card-head-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
}
.card-title-lg {
font-size: 17px;
font-weight: 600;
color: var(--color-text);
letter-spacing: -0.01em;
line-height: 1.25;
}
.card-description {
font-size: 13px;
color: var(--color-hint);
line-height: 1.45;
}
/* === Page header (sits at the top of each non-map tab) === */
.page-header {
padding: var(--sp-5) var(--sp-4) var(--sp-4);
border-bottom: 1px solid var(--color-separator);
margin-bottom: var(--sp-4);
}
.page-title {
font-size: 28px;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--color-text);
line-height: 1.15;
}
.page-description {
font-size: 14px;
color: var(--color-hint);
margin-top: var(--sp-1);
line-height: 1.45;
}
/* === Metric (KPI) card — used for the 3-up dashboard tiles in history
and stats. Tighter than .card, more visual weight on the value. */
.metric-card {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-3) var(--sp-4);
display: flex;
flex-direction: column;
justify-content: center;
gap: var(--sp-1);
min-height: 84px;
min-width: 0;
}
.metric-label {
font-size: 12px;
color: var(--color-hint);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-value {
font-size: 24px;
font-weight: 700;
color: var(--color-text);
letter-spacing: -0.01em;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.metric-trend {
font-size: 12px;
color: var(--color-hint);
margin-top: var(--sp-1);
display: inline-flex;
align-items: center;
gap: 4px;
}
.metric-trend.up { color: var(--color-bad); }
.metric-trend.down { color: var(--color-good); }
.metric-trend.flat { color: var(--color-hint); }
.metric-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--sp-3);
margin-bottom: var(--sp-3);
}
.stats-overview-row {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 600px) {
.metric-row {
grid-template-columns: 1fr;
}
}
/* === Extreme cards (lowest / highest price in history summary) === */
.extreme-card {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-4) var(--sp-4);
display: flex;
flex-direction: column;
gap: var(--sp-1);
min-width: 0;
}
.extreme-label {
font-size: 12px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.extreme-value {
font-size: 28px;
font-weight: 700;
color: var(--color-text);
letter-spacing: -0.01em;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.extreme-value.good { color: var(--color-good); }
.extreme-value.bad { color: var(--color-bad); }
.extreme-card.is-empty .extreme-value { color: var(--color-hint); opacity: 0.6; }
.extreme-station {
font-size: 13px;
color: var(--color-hint);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.empty-state-inline {
text-align: center;
padding: var(--sp-5) 0 var(--sp-2);
opacity: 0.5;
font-size: 13px;
color: var(--color-hint);
}
/* === Stats hero (overall avg headline) === */
.stats-hero {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-5) var(--sp-5);
margin-bottom: var(--sp-3);
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: var(--sp-1);
min-height: 120px;
}
.stats-hero-label {
font-size: 12px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.stats-hero-value {
font-size: 40px;
font-weight: 800;
color: var(--color-text);
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
line-height: 1.05;
}
/* === Best times (cheapest day / hour) === */
.best-time-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--sp-3);
}
.best-time-card {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-4) var(--sp-4);
display: flex;
flex-direction: column;
justify-content: center;
gap: var(--sp-1);
min-height: 112px;
min-width: 0;
}
.best-time-label {
font-size: 12px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.best-time-value {
font-size: 20px;
font-weight: 700;
color: var(--color-text);
letter-spacing: -0.01em;
}
.best-time-price {
font-size: 14px;
font-weight: 600;
margin-top: var(--sp-1);
font-variant-numeric: tabular-nums;
}
.best-time-price.good { color: var(--color-good); }
.best-time-price.bad { color: var(--color-bad); }
@media (max-width: 480px) {
.best-time-grid {
grid-template-columns: 1fr;
}
}
#stats-content {
width: 100%;
}
#stats-content > .metric-row {
margin-left: var(--sp-4);
margin-right: var(--sp-4);
}
/* === Stats hero card — overall avg with price spread visualisation === */
.stats-hero-section {
padding-bottom: 0;
}
.stats-hero-card {
position: relative;
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, color-mix(in srgb, var(--color-accent) 7%, var(--color-bg-secondary)) 100%);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-5);
margin-bottom: var(--sp-3);
overflow: hidden;
}
.stats-hero-card::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at 92% -10%, color-mix(in srgb, var(--color-accent) 18%, transparent) 0%, transparent 55%);
pointer-events: none;
}
.stats-hero-top {
position: relative;
z-index: 1;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--sp-3);
}
.stats-hero-headline {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.stats-hero-label {
font-size: 11px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.7px;
font-weight: 600;
}
.stats-hero-value {
font-size: 36px;
font-weight: 800;
color: var(--color-text);
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
line-height: 1.05;
}
.stats-hero-glyph {
flex-shrink: 0;
width: 44px;
height: 44px;
border-radius: 12px;
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
color: var(--color-accent);
display: grid;
place-items: center;
}
.stats-hero-period {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 10px;
padding: 4px 10px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
color: var(--color-accent);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1px;
width: fit-content;
cursor: default;
}
.stats-spread {
position: relative;
z-index: 1;
margin-top: var(--sp-4);
}
.stats-spread-track {
position: relative;
height: 6px;
border-radius: 3px;
background: var(--color-separator);
overflow: visible;
}
.stats-spread-fill {
position: absolute;
inset: 0;
border-radius: 3px;
background: linear-gradient(90deg, var(--color-good) 0%, #ff9500 50%, var(--color-bad) 100%);
opacity: 0.85;
}
.stats-spread-marker {
position: absolute;
top: 50%;
width: 14px;
height: 14px;
margin-left: -7px;
border-radius: 50%;
background: var(--color-text);
border: 2px solid var(--color-bg-secondary);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
transform: translateY(-50%);
transition: left 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.stats-spread-labels {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-2);
margin-top: 8px;
font-size: 11px;
color: var(--color-hint);
font-variant-numeric: tabular-nums;
}
.stats-spread-low { color: var(--color-good); font-weight: 600; }
.stats-spread-high { color: var(--color-bad); font-weight: 600; }
.stats-spread-caption {
text-transform: uppercase;
letter-spacing: 0.6px;
font-weight: 600;
font-size: 10px;
color: var(--color-hint);
}
/* === Stats facts row — compact 3-up below the hero === */
.stats-facts-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.stats-fact-card {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: 10px;
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.stats-fact-icon {
flex-shrink: 0;
width: 30px;
height: 30px;
border-radius: 9px;
display: grid;
place-items: center;
}
.stats-fact-icon-good { background: color-mix(in srgb, var(--color-good) 16%, transparent); color: var(--color-good); }
.stats-fact-icon-bad { background: color-mix(in srgb, var(--color-bad) 16%, transparent); color: var(--color-bad); }
.stats-fact-icon-accent { background: color-mix(in srgb, var(--color-accent) 16%, transparent); color: var(--color-accent); }
.stats-fact-body {
display: flex;
flex-direction: column;
gap: 0;
min-width: 0;
}
.stats-fact-label {
font-size: 10px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stats-fact-value {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
font-variant-numeric: tabular-nums;
line-height: 1.15;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Best-time header + savings badge */
.best-time-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 2px;
}
.best-time-icon-wrap {
flex-shrink: 0;
width: 26px;
height: 26px;
border-radius: 8px;
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
color: var(--color-accent);
display: grid;
place-items: center;
}
.best-time-savings {
display: inline-flex;
align-items: center;
gap: 4px;
margin-top: 6px;
padding: 3px 8px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-good) 16%, transparent);
color: var(--color-good);
font-size: 11px;
font-weight: 600;
font-variant-numeric: tabular-nums;
align-self: flex-start;
width: fit-content;
}
.ranking-medal .ranking-pos {
font-size: 17px;
line-height: 1;
}
/* === Weekday tile row — 7 chronological cards with a coloured
cheapness bar at the bottom. Background stays neutral so the
price reads cleanly; the bar carries the rank signal. The
cheapest day gets a subtle tinted surface + coloured border. === */
.stats-tile-grid {
display: grid;
gap: 6px;
}
.stats-tile-grid-7 {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.stats-tile {
position: relative;
border-radius: 12px;
padding: 11px 4px 9px;
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 2px;
min-height: 64px;
min-width: 0;
transition: transform 0.18s cubic-bezier(0.2, 0, 0, 1), border-color 0.2s ease, background 0.2s ease;
}
.stats-tile.is-empty .stats-tile-name,
.stats-tile.is-empty .stats-tile-value { opacity: 0.4; }
.stats-tile.is-best {
border-color: color-mix(in srgb, var(--tile-color) 65%, transparent);
background: color-mix(in srgb, var(--tile-color) 9%, var(--color-bg-secondary));
}
.stats-tile-name {
font-size: 10px;
font-weight: 700;
color: var(--color-hint);
letter-spacing: 0.5px;
text-transform: uppercase;
line-height: 1;
}
.stats-tile-value {
font-size: 13px;
font-weight: 700;
color: var(--tile-color, var(--color-text));
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
line-height: 1.1;
margin-top: 2px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.stats-tile.is-empty .stats-tile-value {
color: var(--color-hint);
}
.stats-tile-bar {
margin-top: auto;
width: calc(100% - 12px);
height: 3px;
background: var(--color-separator);
border-radius: 999px;
overflow: hidden;
align-self: center;
}
.stats-tile-bar-fill {
height: 100%;
width: 0;
background: var(--tile-color, var(--color-separator));
border-radius: 999px;
transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.stats-tile.is-empty .stats-tile-bar-fill { background: var(--color-separator); }
.stats-tile-crown {
position: absolute;
top: 4px;
right: 5px;
font-size: 10px;
color: var(--tile-color);
z-index: 2;
line-height: 1;
}
/* === Settings: account hero card === */
.account-hero {
position: relative;
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-secondary)) 100%);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-5);
overflow: hidden;
}
.account-hero::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at 92% -10%, color-mix(in srgb, var(--color-accent) 20%, transparent) 0%, transparent 55%);
pointer-events: none;
}
.account-hero-top {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: var(--sp-4);
min-width: 0;
}
.account-hero-avatar {
flex-shrink: 0;
width: 60px;
height: 60px;
border-radius: 50%;
background: color-mix(in srgb, var(--color-accent) 18%, var(--color-bg-secondary));
color: color-mix(in srgb, var(--color-accent) 80%, var(--color-text));
display: grid;
place-items: center;
overflow: hidden;
border: 1.5px solid color-mix(in srgb, var(--color-accent) 30%, transparent);
transition: border-color 0.25s ease, background 0.25s ease;
}
.account-hero.is-signed-in .account-hero-avatar {
background: var(--color-bg);
border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
color: var(--color-accent);
}
.account-hero-avatar .account-avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.account-hero-avatar .account-avatar-fallback {
width: 100%;
height: 100%;
display: grid;
place-items: center;
font-size: 24px;
font-weight: 700;
color: var(--color-accent);
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}
.account-hero-text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.account-hero-label {
font-size: 10px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.7px;
font-weight: 700;
}
.account-hero-name {
font-size: 22px;
font-weight: 800;
color: var(--color-text);
letter-spacing: -0.02em;
line-height: 1.15;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.account-hero-subline {
font-size: 13px;
color: var(--color-hint);
line-height: 1.35;
overflow: hidden;
text-overflow: ellipsis;
}
.account-hero-actions {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: var(--sp-3);
margin-top: var(--sp-4);
flex-wrap: wrap;
}
.account-hero-sync-hint {
flex: 1;
min-width: 0;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--color-hint);
line-height: 1.35;
}
.account-hero-sync-hint svg {
flex-shrink: 0;
color: var(--color-accent);
opacity: 0.85;
}
.account-hero-btn {
flex-shrink: 0;
padding: 9px 18px;
border-radius: 999px;
background: var(--color-accent);
color: var(--color-accent-text);
border: none;
font-size: 14px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.account-hero-btn:hover {
box-shadow: 0 4px 12px color-mix(in srgb, var(--color-accent) 35%, transparent);
}
.account-hero-btn:active {
transform: scale(0.96);
}
.account-hero.is-signed-in .account-hero-btn {
background: color-mix(in srgb, var(--color-text) 8%, transparent);
color: var(--color-text);
}
.account-hero.is-signed-in .account-hero-btn:hover {
background: color-mix(in srgb, var(--color-text) 14%, transparent);
box-shadow: none;
}
/* === Settings rows: icon chip in front of the label === */
.settings-icon-chip {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 9px;
display: grid;
place-items: center;
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
color: var(--color-accent);
}
.settings-icon-chip.settings-icon-good {
background: color-mix(in srgb, var(--color-good) 14%, transparent);
color: var(--color-good);
}
.settings-icon-chip.settings-icon-warn {
background: color-mix(in srgb, var(--color-bad) 14%, transparent);
color: var(--color-bad);
}
.settings-card .settings-row-left {
gap: var(--sp-3);
}
.settings-card .settings-row-label {
font-size: 15px;
font-weight: 500;
}
/* === Segmented control (theme, history-default range) === */
.seg-control {
position: relative;
display: flex;
gap: 4px;
margin: 0 var(--sp-4) var(--sp-3);
padding: 4px;
border-radius: 12px;
background: color-mix(in srgb, var(--color-text) 6%, transparent);
border: 1px solid var(--color-separator);
}
.seg-thumb {
position: absolute;
top: 4px;
bottom: 4px;
left: 0;
width: 0;
background: var(--color-bg);
border-radius: 9px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 1px rgba(0, 0, 0, 0.04);
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
width 0.28s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
z-index: 1;
}
html[data-theme="dark"] .seg-thumb,
html:not([data-theme]) .seg-thumb { /* dark via prefers-color-scheme */
background: var(--color-bg-secondary);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]) .seg-thumb {
background: var(--color-bg-secondary);
}
}
.seg-option {
position: relative;
z-index: 2;
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 9px 10px;
border: none;
background: none;
color: var(--color-hint);
font-size: 13px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
border-radius: 9px;
-webkit-tap-highlight-color: transparent;
transition: color 0.2s ease;
white-space: nowrap;
min-width: 0;
}
.seg-option svg {
flex-shrink: 0;
opacity: 0.65;
transition: opacity 0.2s ease, color 0.2s ease;
}
.seg-option:hover { color: var(--color-text); }
.seg-option:hover svg { opacity: 0.85; }
.seg-option.active {
color: var(--color-text);
font-weight: 700;
}
.seg-option.active svg {
color: var(--color-accent);
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.seg-thumb { transition: none; }
}
/* === About hero — bigger app-icon centerpiece === */
.about-hero {
display: flex;
align-items: center;
gap: var(--sp-4);
padding: var(--sp-5);
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
margin-bottom: var(--sp-3);
}
.about-hero .about-app-icon {
flex-shrink: 0;
width: 68px;
height: 68px;
border-radius: 16px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
cursor: pointer;
}
.about-hero-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.about-hero-info .about-app-name {
font-size: 22px;
font-weight: 800;
color: var(--color-text);
letter-spacing: -0.02em;
}
.about-hero-info .about-app-version {
font-size: 12px;
color: var(--color-hint);
font-variant-numeric: tabular-nums;
margin-bottom: 4px;
}
.about-hero-tag {
font-size: 13px;
color: var(--color-hint);
line-height: 1.35;
}
/* === History tab — hero card with current avg + trend pills === */
.history-hero-card {
position: relative;
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, color-mix(in srgb, var(--color-accent) 7%, var(--color-bg-secondary)) 100%);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-5);
overflow: hidden;
}
.history-hero-card::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at 92% -10%, color-mix(in srgb, var(--color-accent) 18%, transparent) 0%, transparent 55%);
pointer-events: none;
}
.history-hero-top {
position: relative;
z-index: 1;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--sp-3);
}
.history-hero-headline {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.history-hero-label {
font-size: 11px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.7px;
font-weight: 600;
}
.history-hero-value {
font-size: 36px;
font-weight: 800;
color: var(--color-text);
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
line-height: 1.05;
}
.history-hero-period {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 6px;
padding: 4px 10px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
color: var(--color-accent);
font-size: 11px;
font-weight: 600;
width: fit-content;
}
.history-hero-glyph {
flex-shrink: 0;
width: 44px;
height: 44px;
border-radius: 12px;
background: color-mix(in srgb, var(--glyph-color, var(--color-accent)) 14%, transparent);
color: var(--glyph-color, var(--color-accent));
display: grid;
place-items: center;
}
.history-hero-pills {
position: relative;
z-index: 1;
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.history-hero-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 11px;
border-radius: 999px;
background: color-mix(in srgb, var(--pill-color) 16%, transparent);
color: var(--pill-color);
font-size: 12px;
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: 0.1px;
}
.history-hero-pill-arrow { font-weight: 700; }
.history-hero-pill-label {
color: color-mix(in srgb, var(--pill-color) 75%, var(--color-text));
opacity: 0.7;
font-weight: 500;
margin-left: 2px;
letter-spacing: 0.2px;
}
/* === History extreme cards — clickable, opens centred station popup === */
.history-extreme-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.history-extreme-card {
position: relative;
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: 14px;
padding: 12px 14px 12px 18px;
display: flex;
align-items: center;
gap: 12px;
text-align: left;
cursor: pointer;
font-family: inherit;
color: var(--color-text);
overflow: hidden;
-webkit-tap-highlight-color: transparent;
transition: transform 0.18s cubic-bezier(0.2, 0, 0, 1),
box-shadow 0.2s ease,
border-color 0.2s ease;
}
.history-extreme-card::before {
content: '';
position: absolute;
left: 0;
top: 14%;
bottom: 14%;
width: 3px;
border-radius: 0 2px 2px 0;
background: var(--accent);
}
.history-extreme-card:hover {
border-color: color-mix(in srgb, var(--accent) 35%, var(--color-separator));
box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 12%, transparent);
}
.history-extreme-card:active {
transform: scale(0.98);
}
.history-extreme-card.is-empty {
cursor: default;
opacity: 0.55;
}
.history-extreme-card.is-empty:hover {
border-color: var(--color-separator);
box-shadow: none;
}
.history-extreme-card.is-empty:active {
transform: none;
}
.history-extreme-icon {
flex-shrink: 0;
width: 34px;
height: 34px;
border-radius: 10px;
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--accent);
display: grid;
place-items: center;
}
.history-extreme-body {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 1px;
overflow: hidden;
}
.history-extreme-label {
font-size: 10px;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.history-extreme-value {
font-size: 18px;
font-weight: 800;
color: var(--accent);
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
line-height: 1.15;
}
.history-extreme-station {
font-size: 12px;
color: var(--color-hint);
line-height: 1.25;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
/* === History chart custom tooltip === */
.history-tooltip {
position: absolute;
pointer-events: none;
z-index: 99997;
background: rgba(20, 20, 20, 0.94);
border-radius: 12px;
padding: 10px 14px;
transform: translate(-50%, calc(-100% - 14px)) scale(0.94);
transform-origin: bottom center;
opacity: 0;
transition: opacity 0.16s ease, transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 8px 26px rgba(0, 0, 0, 0.28);
white-space: nowrap;
font-family: -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
text-align: center;
min-width: 130px;
}
.history-tooltip.show {
opacity: 1;
transform: translate(-50%, calc(-100% - 14px)) scale(1);
}
.history-tooltip-time {
font-size: 11px;
font-weight: 600;
color: rgba(255, 255, 255, 0.62);
letter-spacing: 0.3px;
margin-bottom: 2px;
}
.history-tooltip-price {
font-size: 18px;
font-weight: 800;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
}
.history-tooltip-meta {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 5px;
font-size: 11px;
font-weight: 600;
color: rgba(255, 255, 255, 0.78);
font-variant-numeric: tabular-nums;
letter-spacing: 0.1px;
}
.history-tooltip-hint {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid rgba(255, 255, 255, 0.14);
font-size: 10px;
color: rgba(255, 255, 255, 0.55);
letter-spacing: 0.2px;
}
/* Hour chart section header — drilled-down day + close button */
#hour-chart-label {
display: flex !important;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.hour-chart-day {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hour-chart-close {
flex-shrink: 0;
border: none;
background: color-mix(in srgb, var(--color-text) 8%, transparent);
color: var(--color-hint);
width: 24px;
height: 24px;
border-radius: 50%;
display: grid;
place-items: center;
cursor: pointer;
padding: 0;
-webkit-tap-highlight-color: transparent;
transition: background 0.18s ease, color 0.18s ease;
}
.hour-chart-close:hover {
background: color-mix(in srgb, var(--color-text) 14%, transparent);
color: var(--color-text);
}
.hour-chart-close:active { transform: scale(0.92); }
/* Soft warm drop-shadow on both history canvases so they sit on the
page like the stats hour chart does. */
#price-chart, #hour-chart {
filter: drop-shadow(0 4px 14px rgba(255, 149, 0, 0.07));
}
/* === Hour line chart — custom HTML tooltip so the price text can pick
up the hovered hour's rank colour. === */
.stats-hour-tooltip {
position: absolute;
pointer-events: none;
z-index: 99997;
background: rgba(20, 20, 20, 0.94);
border-radius: 12px;
padding: 9px 14px;
transform: translate(-50%, calc(-100% - 14px)) scale(0.94);
transform-origin: bottom center;
opacity: 0;
transition: opacity 0.16s ease, transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 8px 26px rgba(0, 0, 0, 0.28);
white-space: nowrap;
font-family: -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
text-align: center;
}
.stats-hour-tooltip.show {
opacity: 1;
transform: translate(-50%, calc(-100% - 14px)) scale(1);
}
.stats-hour-tooltip-time {
font-size: 11px;
font-weight: 600;
color: rgba(255, 255, 255, 0.62);
letter-spacing: 0.3px;
margin-bottom: 2px;
}
.stats-hour-tooltip-price {
font-size: 17px;
font-weight: 800;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
}
/* === Hour line chart — Chart.js canvas wrapper === */
.stats-hour-chart-wrap {
position: relative;
height: 160px;
margin-bottom: 4px;
animation: stats-chart-enter 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.stats-hour-chart-wrap canvas {
filter: drop-shadow(0 4px 14px rgba(255, 149, 0, 0.10));
}
@keyframes stats-chart-enter {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.stats-hour-chart-wrap { animation: none; }
}
/* === Station ranking — coloured left accent stripe === */
.stats-station-list .station-ranking-item {
position: relative;
padding-left: calc(var(--sp-4) + 6px);
}
.stats-station-list .station-ranking-item::before {
content: '';
position: absolute;
left: 6px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 60%;
border-radius: 2px;
background: var(--rank-color, var(--color-separator));
}
.stats-station-list .station-ranking-item.ranking-medal::before {
height: 70%;
width: 4px;
}
/* Small "Ø" mark in the ranking price so users understand the number is
the 30-day average rather than the current price. Hover-title row
carries min + sample count for the full picture. */
.ranking-price-avg-mark {
font-size: 11px;
font-weight: 600;
margin-right: 4px;
opacity: 0.65;
letter-spacing: 0.5px;
}
@media (max-width: 360px) {
.stats-fact-value { font-size: 14px; }
.stats-fact-icon { width: 26px; height: 26px; border-radius: 8px; }
.stats-hero-value { font-size: 32px; }
}
.card-list .card-row:last-child {
border-bottom: none;
}
.card-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
position: relative;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.card-row-left {
display: flex;
align-items: center;
gap: var(--sp-3);
flex: 1;
min-width: 0;
}
.account-avatar {
display: none;
flex-shrink: 0;
}
.account-avatar.visible {
display: block;
}
.account-avatar-img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.account-avatar-fallback {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--color-tint, #007aff);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 16px;
}
.card-icon {
font-size: 20px;
flex-shrink: 0;
width: 28px;
text-align: center;
}
.card-title {
font-size: 15px;
font-weight: 500;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-subtitle {
font-size: 13px;
color: var(--color-hint);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-value {
font-size: 16px;
font-weight: 600;
color: var(--color-text);
flex-shrink: 0;
}
.card-value.good { color: var(--color-good); }
.card-value.okay { color: var(--color-okay); }
.card-value.bad { color: var(--color-bad); }
.card-action {
background: var(--color-accent);
color: var(--color-accent-text);
border: none;
border-radius: var(--r-sm);
padding: var(--sp-2) var(--sp-4);
font-size: 14px;
font-weight: 600;
cursor: pointer;
flex-shrink: 0;
transition: transform 0.15s cubic-bezier(0.2, 0, 0, 1), opacity 0.15s ease;
}
.card-action:hover { opacity: 0.92; }
.card-action:active {
transform: scale(0.95);
}
/* Ripple effect */
.ripple {
position: relative;
overflow: hidden;
}
.ripple::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: var(--color-hint);
transform: translate(-50%, -50%);
opacity: 0;
}
.ripple:active::after {
width: 200%;
height: 200%;
opacity: 0.15;
transition: width 0.3s, height 0.3s, opacity 0.3s;
}
/* === Chips === */
.chip-row {
display: flex;
gap: var(--sp-2);
flex-wrap: wrap;
padding-bottom: var(--sp-1);
}
.chip {
background: var(--color-bg-secondary);
color: var(--color-text);
border: none;
border-radius: var(--r-pill);
padding: var(--sp-2) var(--sp-4);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, color 0.15s, transform 0.1s, box-shadow 0.15s;
-webkit-user-select: none;
user-select: none;
white-space: nowrap;
}
.chip:active {
transform: scale(0.95);
}
.chip.active {
background: var(--color-accent);
color: var(--color-accent-text);
box-shadow: var(--shadow-1);
}
.country-chip-row {
margin-bottom: var(--sp-3);
}
.country-chip-row .chip {
display: inline-flex;
align-items: center;
gap: var(--sp-2);
}
.country-flag {
display: inline-block;
flex-shrink: 0;
border-radius: 1.5px;
box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.15);
vertical-align: middle;
}
/* === Map === */
#map-container {
position: relative;
height: 55vh;
min-height: 250px;
}
#map-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: calc(env(safe-area-inset-top, 0px) + 56px);
background: #000;
z-index: 2;
pointer-events: none;
}
.top-cta-section {
margin-top: 12px;
}
#map {
width: 100%;
height: 100%;
z-index: 1;
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}
.map-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--color-bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
z-index: 10;
color: var(--color-hint);
font-size: 14px;
transition: opacity 0.3s;
}
.map-overlay.hidden {
opacity: 0;
pointer-events: none;
}
/* === Map Search Bar === */
#map-search-wrapper {
position: absolute;
top: 12px;
left: 12px;
right: 12px;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: stretch;
pointer-events: none;
}
#map-search-wrapper > * {
pointer-events: auto;
}
/* Top row holds the search box (grows) and the "Hier suchen" pill (right,
only visible after the map has moved). When the pill is hidden the box
takes the full width on its own. */
#map-search-top-row {
display: flex;
align-items: center;
gap: var(--sp-2);
}
/* Scan-picker mode hides the search box — keep the confirm/cancel pills
centred in the row instead of stuck to the left. */
#map-search-top-row:has(#map-search-box.hidden) {
justify-content: center;
}
#map-search-box {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
background: var(--color-bg);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
padding: 0 12px;
height: 44px;
gap: 8px;
}
.map-search-icon {
color: var(--color-hint);
flex-shrink: 0;
}
#map-search-input {
flex: 1;
border: none;
background: transparent;
color: var(--color-text);
font-size: 15px;
outline: none;
min-width: 0;
}
#map-search-input::placeholder {
color: var(--color-hint);
}
.map-search-clear {
border: none;
background: none;
color: var(--color-hint);
font-size: 20px;
cursor: pointer;
line-height: 1;
flex-shrink: 0;
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.15s;
}
.map-search-clear:hover,
.map-search-clear:focus-visible {
background: var(--color-bg-secondary);
}
.map-search-clear.hidden {
display: none;
}
#map-search-results {
margin-top: 4px;
background: var(--color-bg);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
overflow: hidden;
max-height: 240px;
overflow-y: auto;
}
#map-search-results.hidden {
display: none;
}
.map-search-result-item {
padding: 12px 14px;
font-size: 14px;
color: var(--color-text);
cursor: pointer;
border-bottom: 1px solid var(--color-separator);
display: flex;
align-items: center;
gap: 10px;
}
.map-search-result-item:last-child {
border-bottom: none;
}
.map-search-result-item:active {
background: var(--color-bg-secondary);
}
.map-search-station-item {
gap: 12px;
}
.map-search-station-rank {
width: 26px;
height: 26px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
}
.map-search-station-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.map-search-station-name {
font-size: 14px;
font-weight: 600;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.map-search-station-addr {
font-size: 12px;
color: var(--color-hint);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.map-search-station-price {
font-size: 16px;
font-weight: 700;
flex-shrink: 0;
}
/* ─── Route planner chip + panel ────────────────────────────── */
#map-route-chip-row {
display: flex;
justify-content: flex-start;
margin-top: 6px;
pointer-events: none;
}
.map-route-chip {
pointer-events: auto;
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--color-bg);
border: none;
border-radius: 999px;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
color: var(--color-text);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
transition: transform 0.12s ease, background 0.15s;
}
.map-route-chip:hover { background: var(--color-bg-secondary); }
.map-route-chip:active { transform: scale(0.96); }
.map-route-chip[aria-expanded="true"] {
background: var(--color-accent);
color: #fff;
}
.map-route-chip[aria-expanded="true"] svg { color: #fff; }
.map-route-panel {
margin-top: 6px;
background: var(--color-bg);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
padding: 10px;
display: flex;
flex-direction: column;
gap: 6px;
}
.map-route-panel.hidden { display: none; }
.map-route-row {
display: flex;
align-items: center;
gap: 10px;
}
.map-route-dot {
flex-shrink: 0;
width: 10px;
height: 10px;
border-radius: 50%;
}
.map-route-dot-start { background: var(--color-accent); }
.map-route-dot-dest {
background: transparent;
border: 2px solid var(--color-text);
box-sizing: border-box;
}
.map-route-input-wrapper {
flex: 1;
position: relative;
min-width: 0;
}
.map-route-input {
width: 100%;
border: 1px solid var(--color-separator);
background: var(--color-bg);
color: var(--color-text);
border-radius: 8px;
padding: 8px 10px;
font-size: 14px;
outline: none;
}
.map-route-input:focus {
border-color: var(--color-accent);
}
.map-route-suggest {
position: absolute;
top: calc(100% + 2px);
left: 0;
right: 0;
z-index: 1001;
background: var(--color-bg);
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
overflow: hidden;
max-height: 200px;
overflow-y: auto;
}
.map-route-suggest.hidden { display: none; }
.map-route-suggest-item {
padding: 10px 12px;
font-size: 13px;
color: var(--color-text);
cursor: pointer;
border-bottom: 1px solid var(--color-separator);
display: flex;
align-items: center;
gap: 8px;
}
.map-route-suggest-item:last-child { border-bottom: none; }
.map-route-suggest-item:active { background: var(--color-bg-secondary); }
.map-route-suggest-item.is-current-location {
color: var(--color-accent);
font-weight: 500;
}
.map-route-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 2px;
}
.map-route-close {
background: none;
border: none;
color: var(--color-hint);
font-size: 13px;
cursor: pointer;
padding: 6px 10px;
border-radius: 6px;
}
.map-route-close:hover { background: var(--color-bg-secondary); }
.map-route-go {
background: var(--color-accent);
color: #fff;
border: none;
border-radius: 8px;
padding: 7px 14px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.map-route-go[disabled] {
opacity: 0.4;
cursor: not-allowed;
}
.map-route-summary {
font-size: 12px;
color: var(--color-hint);
padding: 4px 2px 0;
}
.map-route-summary.hidden { display: none; }
.route-exit-btn {
position: absolute;
top: calc(12px + 44px + 6px);
right: 12px;
z-index: 1001;
background: var(--color-bg);
border: none;
border-radius: 999px;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
color: var(--color-text);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
display: inline-flex;
align-items: center;
gap: 6px;
}
.route-exit-btn.hidden { display: none; }
/* ── Route scan dots ─────────────────────────────────────────────── */
/* Yellow markers that appear along the route where live scans will run. */
.route-scan-dot {
pointer-events: none;
}
.route-scan-dot-inner {
width: 18px;
height: 18px;
border-radius: 50%;
background: #f5c518;
border: 2px solid #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
transition: background 0.3s ease, transform 0.3s ease, opacity 0.3s ease;
}
.route-scan-dot.pending .route-scan-dot-inner {
animation: route-scan-pulse 1.6s ease-in-out infinite;
}
.route-scan-dot.scanning .route-scan-dot-inner {
animation: route-scan-pulse 0.7s ease-in-out infinite;
background: #ffd84d;
}
.route-scan-dot.done .route-scan-dot-inner {
background: #4caf50;
animation: none;
}
.route-scan-dot.empty .route-scan-dot-inner {
background: #9e9e9e;
animation: none;
opacity: 0.7;
}
.route-scan-dot.error .route-scan-dot-inner {
background: #f44336;
animation: none;
}
@keyframes route-scan-pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.35); opacity: 0.75; }
}
#map-search-box.hidden { display: none; }
.map-search-no-results {
padding: 14px;
font-size: 13px;
color: var(--color-hint);
text-align: center;
}
/* === Map Location Banner === */
#map-location-banner {
position: absolute;
top: 64px;
right: 12px;
z-index: 1000;
background: rgba(255, 204, 0, 0.75);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
color: #5a4500;
border-radius: 10px;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 500;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
max-width: 220px;
}
#map-location-banner.hidden {
display: none;
}
#map-location-banner-close {
border: none;
background: none;
color: #5a4500;
font-size: 18px;
line-height: 1;
cursor: pointer;
flex-shrink: 0;
opacity: 0.7;
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: opacity 0.15s, background 0.15s;
}
#map-location-banner-close:hover,
#map-location-banner-close:focus-visible {
opacity: 1;
background: rgba(0, 0, 0, 0.08);
}
#map-location-banner-close:active {
opacity: 1;
}
.spinner {
width: 28px;
height: 28px;
border: 3px solid var(--color-bg-secondary);
border-top-color: var(--color-accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes slide-in-up {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.station-item.anim-in,
.ranking-item.anim-in,
.stat-card.anim-in,
.metric-card.anim-in,
.best-time-card.anim-in,
.stats-hero-card.anim-in,
.stats-fact-card.anim-in,
.stats-tile.anim-in,
.history-hero-card.anim-in,
.history-extreme-card.anim-in {
animation: slide-in-up 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Hour bars grow via their inner fill (height transition driven by JS),
no scaleY animation on the column container — that would squash the
layout since the cell now anchors content to the bottom. */
/* === Confetti burst — decorative click effect === */
.confetti-burst {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 99999;
overflow: visible;
}
.confetti-piece {
position: absolute;
will-change: transform, opacity;
pointer-events: none;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.18));
}
.confetti-trigger {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.confetti-trigger.stats-hero-glyph,
.confetti-trigger.stats-fact-icon,
.confetti-trigger.best-time-icon-wrap,
.confetti-trigger.about-heart,
.confetti-trigger.about-app-icon {
transition: transform 0.15s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.confetti-trigger.stats-hero-glyph:hover,
.confetti-trigger.stats-fact-icon:hover,
.confetti-trigger.best-time-icon-wrap:hover,
.confetti-trigger.about-heart:hover,
.confetti-trigger.about-app-icon:hover {
transform: scale(1.08);
}
.about-heart {
display: inline-block;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.about-app-icon {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
/* === Floating value chip — fades in above tiny tap targets to surface
their label (hour heatmap cells, weekday tiles, …) === */
.floating-value {
position: fixed;
pointer-events: none;
z-index: 99998;
font-size: 12px;
font-weight: 700;
padding: 5px 10px;
border-radius: 999px;
background: var(--color-bg-secondary);
border: 1px solid currentColor;
font-variant-numeric: tabular-nums;
opacity: 0;
transform: translate(-50%, calc(-100% - 14px)) translateY(8px) scale(0.9);
transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
white-space: nowrap;
}
.floating-value.show {
opacity: 1;
transform: translate(-50%, calc(-100% - 14px)) translateY(0) scale(1);
}
@media (prefers-reduced-motion: reduce) {
.floating-value {
transition: opacity 0.15s linear;
transform: translate(-50%, calc(-100% - 14px));
}
.floating-value.show {
transform: translate(-50%, calc(-100% - 14px));
}
}
@keyframes confetti-pop {
0% { transform: scale(1); }
45% { transform: scale(1.22); }
100% { transform: scale(1); }
}
.confetti-pop {
animation: confetti-pop 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (prefers-reduced-motion: reduce) {
.confetti-burst { display: none; }
.confetti-pop { animation: none; }
}
/* === Skeleton Loading === */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton-item {
display: flex;
align-items: center;
padding: 14px 16px;
gap: 12px;
border-bottom: 0.5px solid var(--color-separator);
}
.skeleton-item:last-child {
border-bottom: none;
}
.skeleton-bone {
background: linear-gradient(90deg, var(--color-bg-secondary) 25%, var(--color-separator) 50%, var(--color-bg-secondary) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 6px;
}
.skeleton-rank {
width: 28px;
height: 28px;
border-radius: 8px;
flex-shrink: 0;
}
.skeleton-text-long {
height: 14px;
width: 70%;
margin-bottom: 6px;
}
.skeleton-text-short {
height: 12px;
width: 45%;
}
.skeleton-price {
width: 52px;
height: 20px;
margin-left: auto;
flex-shrink: 0;
}
/* Fills the history price-chart box while the first fetch is in flight.
Sits over the (still-blank) canvas and is removed once the chart draws. */
.chart-skeleton {
position: absolute;
inset: var(--sp-4) var(--sp-2) var(--sp-2);
border-radius: 10px;
}
/* === Station Sort === */
.station-sort-bar {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
padding: 12px 16px 4px;
gap: 8px;
}
.station-sort-actions {
display: inline-flex;
align-items: center;
gap: 6px;
justify-self: start;
}
.station-sort-label {
font-size: 13px;
font-weight: 600;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.3px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
justify-self: center;
text-align: center;
}
.station-sort-label.is-route {
display: flex;
flex-direction: column;
align-items: center;
line-height: 1.15;
white-space: normal;
overflow: visible;
text-align: center;
}
.station-sort-label.is-route .station-count-sub {
font-size: 10px;
font-weight: 500;
letter-spacing: 0.2px;
opacity: 0.85;
margin-top: 2px;
}
.station-sort-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border: none;
border-radius: 8px;
background: var(--color-bg-secondary);
color: var(--color-text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
white-space: nowrap;
transition: opacity 0.15s, transform 0.15s cubic-bezier(0.2, 0, 0, 1);
flex-shrink: 0;
justify-self: end;
}
.station-sort-btn:active {
transform: scale(0.95);
}
.station-sort-btn svg {
flex-shrink: 0;
color: var(--color-accent);
}
/* === Station List (below map) === */
.station-list {
padding: var(--sp-2) 0;
margin: 0 var(--sp-4);
background: var(--color-bg-secondary);
border-radius: var(--r-md);
overflow: hidden;
}
.station-item {
display: flex;
align-items: center;
padding: var(--sp-3) var(--sp-4);
gap: var(--sp-3);
-webkit-tap-highlight-color: transparent;
transition: transform 0.15s cubic-bezier(0.2, 0, 0, 1);
position: relative;
border-bottom: 0.5px solid var(--color-separator);
}
.station-item:last-child {
border-bottom: none;
}
.station-item:active {
transform: scale(0.97);
}
.station-rank {
width: 28px;
height: 28px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
}
.station-info {
flex: 1;
min-width: 0;
}
.station-name {
font-size: 15px;
font-weight: 500;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.station-addr {
font-size: 13px;
color: var(--color-hint);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.station-price {
font-size: 20px;
font-weight: 700;
flex-shrink: 0;
}
.station-price sup {
font-size: 13px;
vertical-align: super;
}
.fav-btn {
background: none;
border: none;
cursor: pointer;
padding: 6px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
-webkit-tap-highlight-color: transparent;
}
.fav-btn {
position: relative;
overflow: visible;
}
.fav-btn svg {
transition: transform 0.3s cubic-bezier(0.2, 0, 0, 1);
position: relative;
z-index: 1;
}
.fav-btn:active svg {
transform: scale(0.8);
}
.fav-btn svg path {
fill: none;
stroke: var(--color-hint);
stroke-width: 1.5;
transition: fill 0.25s, stroke 0.25s;
}
.fav-btn.active svg path {
fill: var(--color-favorite);
stroke: var(--color-favorite);
}
/* Sparkle particles container */
.fav-sparkles {
position: absolute;
inset: -8px;
pointer-events: none;
z-index: 0;
}
.fav-sparkle {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
top: 50%;
left: 50%;
opacity: 0;
}
.fav-btn.anim-pop svg {
animation: fav-sparkle-star 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.fav-btn.anim-pop .fav-sparkle:nth-child(1) { animation: fav-spark-out 0.55s 0.05s ease-out forwards; --spark-x: -14px; --spark-y: -12px; background: var(--color-favorite); }
.fav-btn.anim-pop .fav-sparkle:nth-child(2) { animation: fav-spark-out 0.5s 0.08s ease-out forwards; --spark-x: 12px; --spark-y: -14px; background: #ffcc4d; }
.fav-btn.anim-pop .fav-sparkle:nth-child(3) { animation: fav-spark-out 0.6s 0.03s ease-out forwards; --spark-x: 15px; --spark-y: 4px; background: var(--color-favorite); }
.fav-btn.anim-pop .fav-sparkle:nth-child(4) { animation: fav-spark-out 0.5s 0.1s ease-out forwards; --spark-x: -12px; --spark-y: 10px; background: #ffcc4d; }
.fav-btn.anim-pop .fav-sparkle:nth-child(5) { animation: fav-spark-out 0.55s 0.06s ease-out forwards; --spark-x: 4px; --spark-y: 15px; background: #ffd983; }
.fav-btn.anim-pop .fav-sparkle:nth-child(6) { animation: fav-spark-out 0.5s 0.12s ease-out forwards; --spark-x: -16px; --spark-y: -2px; background: #ffd983; }
.fav-btn.anim-pop .fav-sparkle-ring {
animation: fav-ring-burst 0.5s 0.02s ease-out forwards;
}
.fav-sparkle-ring {
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
border: 2px solid var(--color-favorite);
transform: translate(-50%, -50%);
opacity: 0;
pointer-events: none;
}
@keyframes fav-sparkle-star {
0% { transform: scale(0.3); }
30% { transform: scale(1.2); }
60% { transform: scale(0.9); }
100% { transform: scale(1); }
}
@keyframes fav-spark-out {
0% { opacity: 1; transform: translate(0, 0) scale(1); }
50% { opacity: 1; transform: translate(var(--spark-x), var(--spark-y)) scale(1.2); }
100% { opacity: 0; transform: translate(calc(var(--spark-x) * 1.3), calc(var(--spark-y) * 1.3)) scale(0); }
}
@keyframes fav-ring-burst {
0% { width: 0; height: 0; opacity: 0.8; border-width: 3px; }
60% { opacity: 0.4; border-width: 1.5px; }
100% { width: 36px; height: 36px; opacity: 0; border-width: 0.5px; }
}
.fav-btn.anim-unpop svg {
animation: fav-unpop 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes fav-unpop {
0% { transform: scale(1); }
40% { transform: scale(0.7); }
100% { transform: scale(1); }
}
.station-dist {
font-size: 12px;
color: var(--color-hint);
text-align: right;
}
/* Toolbar toggles (favourites pin + group-by-price) sit in the sort bar
and match the sort button's chip-style theming so the row reads as a
cohesive control strip. Inactive sits on the secondary surface, active
fills with the accent and flips the icon to accent-text. */
.station-fav-btn {
background: var(--color-bg-secondary);
border: none;
padding: 7px;
cursor: pointer;
color: var(--color-hint);
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
transition: color 0.15s, background 0.15s, transform 0.15s cubic-bezier(0.2, 0, 0, 1);
-webkit-tap-highlight-color: transparent;
}
.station-fav-btn:hover {
background: var(--color-separator);
color: var(--color-text);
}
.station-fav-btn:active { transform: scale(0.94); }
.station-fav-btn.active {
background: var(--color-accent);
color: var(--color-accent-text);
}
.station-fav-btn.active:hover {
background: var(--color-accent);
color: var(--color-accent-text);
filter: brightness(1.05);
}
/* Cloud-sync indicator sits right beside the toolbar toggles. Only visible
during the syncing → synced burst, then fades back out — idle has no
point taking permanent visual space. */
body.logged-in .station-fav-sync {
display: inline-flex;
opacity: 0;
transition: opacity 0.25s ease;
pointer-events: none;
}
body.logged-in .station-fav-sync.syncing,
body.logged-in .station-fav-sync.synced {
opacity: 1;
pointer-events: auto;
}
/* === Chart === */
.chart-container {
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-4) var(--sp-2) var(--sp-2);
height: 280px;
position: relative;
}
.chart-container canvas {
width: 100% !important;
height: 100% !important;
}
/* === Bottom Sheet === */
.bottom-sheet {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
transition: visibility 0.3s;
}
.bottom-sheet.hidden {
visibility: hidden;
pointer-events: none;
}
.bottom-sheet-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.4);
opacity: 0;
transition: opacity 0.3s;
}
.bottom-sheet:not(.hidden) .bottom-sheet-backdrop {
opacity: 1;
}
.bottom-sheet-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: var(--color-bg);
border-radius: 16px 16px 0 0;
max-height: 85vh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
touch-action: pan-y;
transform: translateY(100%);
transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1),
max-height 0.35s cubic-bezier(0.32, 0.72, 0, 1);
padding-bottom: var(--safe-bottom);
}
.bottom-sheet:not(.hidden) .bottom-sheet-content {
transform: translateY(0);
}
.bottom-sheet-content.expanded {
max-height: 95vh;
border-radius: 8px 8px 0 0;
}
.bottom-sheet-content.dragging {
transition: none !important;
overflow-y: hidden;
}
.bottom-sheet-content.snapping {
transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1) !important;
}
.bottom-sheet-handle-area {
padding: 10px 0 6px;
cursor: grab;
touch-action: none;
}
.bottom-sheet-handle-area:active {
cursor: grabbing;
}
.bottom-sheet-handle {
width: 36px;
height: 4px;
border-radius: 2px;
background: var(--color-hint);
margin: 0 auto;
opacity: 0.5;
}
/* === Station detail modal — used when the user opens a station from
the Stats Tankstellen-Ranking or the Verlauf cheapest/most-expensive
cards. Same internal layout as the map bottom-sheet, but presented
as a floating card in the middle of the screen instead of a
drag-to-dismiss sheet glued to the bottom edge. */
.bottom-sheet.detail-modal {
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
box-sizing: border-box;
}
.bottom-sheet.detail-modal .bottom-sheet-backdrop {
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.bottom-sheet.detail-modal .bottom-sheet-content {
position: relative;
bottom: auto;
left: auto;
right: auto;
width: min(440px, 100%);
max-height: 86vh;
border-radius: 20px;
padding-bottom: 8px;
transform-origin: center center;
transform: scale(0.86);
opacity: 0;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.32), 0 8px 24px rgba(0, 0, 0, 0.18);
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.2s ease;
}
.bottom-sheet.detail-modal:not(.hidden) .bottom-sheet-content {
transform: scale(1);
opacity: 1;
/* Tiny overshoot on the way in: scale up to 1.02 mid-flight, settle
back to 1. Soft "pop" without the awkward translate-up. */
animation: detail-modal-pop 0.36s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes detail-modal-pop {
0% { transform: scale(0.86); opacity: 0; }
60% { transform: scale(1.02); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
.bottom-sheet.detail-modal .bottom-sheet-handle-area {
display: none;
}
.bottom-sheet.detail-modal .sheet-expand-btn {
display: none;
}
.detail-modal-close {
position: absolute;
top: 12px;
right: 12px;
z-index: 2;
width: 32px;
height: 32px;
border-radius: 50%;
background: color-mix(in srgb, var(--color-text) 8%, transparent);
border: none;
color: var(--color-text);
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.18s ease, transform 0.15s ease;
padding: 0;
}
.detail-modal-close:hover {
background: color-mix(in srgb, var(--color-text) 14%, transparent);
}
.detail-modal-close:active {
transform: scale(0.92);
}
/* Skeleton placeholders shown while we fetch the full detail data. */
.detail-skeleton {
display: flex;
flex-direction: column;
gap: 10px;
padding: 4px 20px 20px;
}
.detail-skeleton-line,
.detail-skeleton-block {
border-radius: 8px;
background: linear-gradient(
90deg,
color-mix(in srgb, var(--color-text) 8%, transparent) 0%,
color-mix(in srgb, var(--color-text) 14%, transparent) 50%,
color-mix(in srgb, var(--color-text) 8%, transparent) 100%
);
background-size: 200% 100%;
animation: detail-skeleton-shimmer 1.4s ease-in-out infinite;
}
.detail-skeleton-line {
height: 14px;
width: 100%;
}
.detail-skeleton-line.short {
width: 60%;
}
.detail-skeleton-line.detail-skeleton-price {
height: 36px;
width: 50%;
margin-bottom: 6px;
}
.detail-skeleton-block {
height: 140px;
margin-top: 8px;
}
@keyframes detail-skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
.bottom-sheet.detail-modal .bottom-sheet-content,
.bottom-sheet.detail-modal:not(.hidden) .bottom-sheet-content {
transition: opacity 0.18s ease;
transform: none;
animation: none;
}
.detail-skeleton-line,
.detail-skeleton-block {
animation: none;
}
}
/* Sheet station detail */
.sheet-station-header {
padding: 12px 20px 8px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.sheet-fav-btn {
margin-top: 2px;
}
.sheet-station-name {
font-size: 18px;
font-weight: 700;
color: var(--color-text);
}
.sheet-station-brand {
font-size: 14px;
color: var(--color-hint);
margin-top: 2px;
}
.sheet-station-price {
font-size: 36px;
font-weight: 800;
padding: 8px 20px;
}
.sheet-station-price sup {
font-size: 18px;
vertical-align: super;
}
.sheet-info-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 20px;
font-size: 14px;
color: var(--color-text);
border-top: 0.5px solid var(--color-separator);
}
.sheet-info-icon {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
/* Sheet opening hours */
.sheet-hours-section {
overflow: hidden;
}
.sheet-hours-toggle {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 20px;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
font-size: 14px;
color: var(--color-text);
border-top: 0.5px solid var(--color-separator);
transition: background 0.15s;
}
.sheet-hours-toggle:active {
background: var(--color-bg-secondary);
}
.sheet-hours-chevron {
transition: transform 0.25s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.sheet-hours-section.expanded .sheet-hours-chevron {
transform: rotate(180deg);
}
.sheet-hours-list {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s cubic-bezier(0.25, 0.1, 0.25, 1),
opacity 0.25s ease;
opacity: 0;
padding: 0 20px;
}
.sheet-hours-section.expanded .sheet-hours-list {
max-height: 300px;
opacity: 1;
padding: 0 20px 10px;
}
.sheet-hours-row {
display: flex;
justify-content: space-between;
/* 24 px icon slot + 10 px gap = 34 px text indent, matching the toggle row. */
padding: 5px 0 5px 34px;
font-size: 13px;
}
.sheet-hours-day {
color: var(--color-hint);
font-weight: 500;
}
.sheet-hours-time {
color: var(--color-text);
font-weight: 600;
font-variant-numeric: tabular-nums;
}
/* Sheet chart */
.sheet-chart-section {
padding: 0 20px 8px;
}
.sheet-chart-header-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0 8px;
}
.sheet-chart-header {
font-size: 13px;
font-weight: 500;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.sheet-chart-toggle {
display: flex;
gap: 4px;
background: var(--color-bg-secondary);
border-radius: 8px;
padding: 2px;
}
.sheet-toggle-btn {
border: none;
background: transparent;
color: var(--color-hint);
font-size: 12px;
font-weight: 500;
padding: 4px 10px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.sheet-toggle-btn.active {
background: var(--color-accent);
color: #fff;
}
.sheet-toggle-btn.disabled {
opacity: 0.35;
cursor: not-allowed;
}
.sheet-chart-container {
background: var(--color-bg-secondary);
border-radius: 12px;
padding: 12px 8px 8px;
height: 120px;
position: relative;
transition: height 0.35s cubic-bezier(0.32, 0.72, 0, 1);
}
.bottom-sheet-content.expanded .sheet-chart-container {
height: 28vh;
}
.sheet-chart-container canvas {
width: 100% !important;
height: 100% !important;
}
.sheet-chart-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--color-hint);
font-size: 13px;
}
/* Friendly empty-state for German stations that aren't in the scanner — points
the user at the location-request flow. */
.sheet-chart-empty-msg {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 6px;
padding: 12px 16px;
height: 100%;
max-width: 320px;
margin: 0 auto;
}
.sheet-chart-empty-title {
font-size: 13px;
font-weight: 600;
color: var(--color-text);
}
.sheet-chart-empty-hint {
font-size: 12px;
color: var(--color-hint);
line-height: 1.4;
}
.sheet-chart-empty-btn {
margin-top: 6px;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border-radius: 999px;
border: none;
background: var(--color-accent);
color: var(--color-accent-text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.sheet-chart-empty-btn:active { opacity: 0.85; transform: scale(0.97); }
/* Subtle pill for manual-scan stations — ephemeral data (15 min TTL).
Freshness colour comes from --scan-color which is set on
#bottom-sheet-body each tick (green → yellow → red). The pill itself
stays low-key: tinted background, hint text, small pulsing dot,
hairline progress strip along the inner-bottom edge. */
#bottom-sheet-body {
--scan-color: #22c55e;
}
.sheet-manual-scan-banner-wrap {
padding: 4px 20px 10px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
/* Small pill button in the sheet header that rescans the 25 km circle
* around the current station — same action as "Hier suchen" but anchored
* to the tapped pin. Sits next to the freshness banner when there is one,
* on its own otherwise. */
.sheet-refresh-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 11px 6px;
border-radius: 999px;
border: 1px solid var(--color-border);
background: var(--color-bg-secondary);
color: var(--color-text);
font-size: 11.5px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}
.sheet-refresh-btn:hover:not(:disabled) {
background: var(--color-border);
}
.sheet-refresh-btn:active:not(:disabled) {
transform: scale(0.97);
}
.sheet-refresh-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.sheet-refresh-btn svg {
flex-shrink: 0;
}
.sheet-manual-scan-banner {
--scan-track: rgba(148, 163, 184, 0.18);
display: inline-flex;
align-items: center;
gap: 7px;
padding: 5px 11px 6px;
border-radius: 999px;
background: color-mix(in srgb, var(--scan-color) 10%, transparent);
font-size: 11.5px;
color: var(--color-hint);
max-width: 100%;
position: relative;
overflow: hidden;
transition: background 1s linear;
}
.sheet-manual-scan-text {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sheet-manual-scan-countdown {
font-variant-numeric: tabular-nums;
font-weight: 600;
color: var(--color-text);
}
/* Small pulsing dot. */
.sheet-manual-scan-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--scan-color);
flex-shrink: 0;
animation: manualScanPulse 1.8s ease-in-out infinite;
transition: background 1s linear;
}
@keyframes manualScanPulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.55; transform: scale(0.8); }
}
/* Hairline progress strip hugging the inside-bottom of the pill. */
.sheet-manual-scan-progress {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: var(--scan-track);
}
.sheet-manual-scan-progress-bar {
height: 100%;
width: 100%;
background: var(--scan-color);
transition: width 0.9s linear, background 1s linear;
}
/* Last minute: only the dot pulses faster. */
.sheet-manual-scan-banner.urgent .sheet-manual-scan-dot {
animation-duration: 0.9s;
}
.sheet-manual-scan-banner.expired {
opacity: 0.55;
}
.sheet-manual-scan-banner.expired .sheet-manual-scan-dot {
animation: none;
}
.sheet-expand-btn {
display: none;
position: absolute;
top: 12px;
right: 16px;
width: 32px;
height: 32px;
border: none;
background: var(--color-bg-secondary);
border-radius: 8px;
cursor: pointer;
align-items: center;
justify-content: center;
color: var(--color-text);
z-index: 2;
transition: background 0.15s;
}
.sheet-expand-btn:hover {
background: var(--color-border);
}
.sheet-expand-btn svg {
width: 18px;
height: 18px;
}
@media (hover: hover) and (pointer: fine) {
.sheet-expand-btn {
display: flex;
}
.sheet-station-header {
padding-right: 52px;
}
}
.sheet-nav-buttons {
display: flex;
gap: 10px;
padding: 16px 20px;
}
.sheet-nav-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 13px 16px;
border-radius: 14px;
border: none;
font-size: 14px;
font-weight: 600;
cursor: pointer;
text-decoration: none;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.sheet-nav-btn:active {
transform: scale(0.95);
}
.sheet-nav-btn.gmaps,
.sheet-nav-btn.amaps {
background: var(--color-bg-secondary);
color: var(--color-text);
border: 1px solid var(--color-separator);
}
.sheet-nav-icon {
border-radius: 5px;
flex-shrink: 0;
}
/* === Slider === */
.slider-row {
display: flex;
align-items: center;
gap: 12px;
}
.slider-row span {
font-size: 15px;
font-weight: 600;
min-width: 50px;
color: var(--color-text);
}
.slider {
flex: 1;
-webkit-appearance: none;
appearance: none;
height: 4px;
border-radius: 2px;
background: var(--color-hint);
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--color-accent);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
/* === Stats === */
.stat-big {
text-align: center;
padding: var(--sp-5) var(--sp-4) var(--sp-3);
}
.stat-big-value {
font-size: 42px;
font-weight: 800;
color: var(--color-text);
letter-spacing: -0.02em;
}
.stat-big-label {
font-size: 14px;
color: var(--color-hint);
margin-top: var(--sp-1);
}
.stat-row {
display: flex;
gap: var(--sp-2);
padding: 0 var(--sp-4);
margin-bottom: var(--sp-2);
}
.stat-card {
flex: 1;
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
border-radius: var(--r-md);
padding: var(--sp-3) var(--sp-3);
text-align: center;
}
.stat-card-value {
font-size: 20px;
font-weight: 700;
color: var(--color-text);
letter-spacing: -0.01em;
}
.stat-card-label {
font-size: 12px;
color: var(--color-hint);
margin-top: var(--sp-1);
}
.ranking-item {
display: flex;
align-items: center;
padding: var(--sp-3) var(--sp-4);
gap: var(--sp-3);
border-bottom: 0.5px solid var(--color-separator);
transition: transform 0.15s cubic-bezier(0.2, 0, 0, 1), background 0.15s ease;
}
.ranking-item:hover {
background: var(--color-separator);
}
.ranking-item:active {
transform: scale(0.98);
}
.ranking-pos {
font-size: 13px;
font-weight: 700;
color: var(--color-hint);
width: 24px;
text-align: center;
font-variant-numeric: tabular-nums;
}
.ranking-name {
flex: 1;
font-size: 14px;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ranking-price {
font-size: 15px;
font-weight: 600;
flex-shrink: 0;
font-variant-numeric: tabular-nums;
}
/* === Tab Bar === */
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: calc(var(--tab-height) + var(--safe-bottom));
padding-bottom: var(--safe-bottom);
background: var(--color-bg-secondary);
border-top: 0.5px solid var(--color-separator);
display: flex;
align-items: center;
justify-content: space-around;
z-index: 100;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
background: none;
border: none;
cursor: pointer;
padding: 6px 14px;
border-radius: 10px;
-webkit-user-select: none;
user-select: none;
color: var(--color-hint);
transition: color 0.2s ease, background 0.2s ease;
}
.tab-item.active {
color: var(--color-accent);
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
@keyframes tab-bounce {
0% { transform: scale(1); }
35% { transform: scale(1.18); }
65% { transform: scale(0.94); }
100% { transform: scale(1); }
}
.tab-item.active .tab-icon {
animation: tab-bounce 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tab-icon {
width: 24px;
height: 24px;
fill: currentColor;
}
.tab-item span {
font-size: 10px;
font-weight: 500;
}
.tab-item.active span {
font-weight: 600;
}
/* === Location chip in settings === */
/* Leaflet popup override */
.leaflet-popup-content-wrapper {
border-radius: 12px !important;
box-shadow: 0 4px 20px rgba(0,0,0,0.15) !important;
}
.leaflet-popup-content {
margin: 10px 14px !important;
font-family: inherit !important;
font-size: 14px !important;
}
/* Map bubble markers (tankste! style) */
.map-bubble {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 3px 8px 4px;
border-radius: 8px;
color: #fff;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
text-align: center;
min-width: 48px;
}
.map-bubble-brand {
font-size: 9px;
font-weight: 500;
opacity: 0.9;
letter-spacing: 0.3px;
line-height: 1.1;
}
.map-bubble-price {
font-size: 15px;
font-weight: 800;
line-height: 1.2;
letter-spacing: -0.3px;
}
.map-bubble-price sup {
font-size: 9px;
vertical-align: super;
}
.map-bubble-arrow {
position: absolute;
bottom: -6px;
left: 50%;
margin-left: -6px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid;
}
/* Cluster marker overrides */
.marker-cluster-small,
.marker-cluster-medium,
.marker-cluster-large {
background: none !important;
}
.marker-cluster-small div,
.marker-cluster-medium div,
.marker-cluster-large div {
background: none !important;
}
/* Empty state */
.empty-state {
text-align: center;
padding: 40px 20px;
color: var(--color-hint);
}
.auth-hero-card {
background: linear-gradient(135deg, rgba(79,195,247,0.12), rgba(52,199,89,0.08));
border: 1px solid rgba(127,127,127,0.14);
border-radius: 18px;
padding: 18px;
display: grid;
gap: 14px;
}
.auth-hero-icon {
width: 44px;
height: 44px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-accent);
color: var(--color-accent-text);
font-size: 22px;
box-shadow: 0 8px 18px rgba(0,0,0,0.12);
}
.auth-hero-title {
font-size: 18px;
font-weight: 700;
color: var(--color-text);
}
.auth-hero-text,
.auth-hero-note {
color: var(--color-hint);
font-size: 14px;
}
.auth-hero-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.google-login-slot {
min-height: 44px;
}
.primary-btn,
.secondary-btn {
border: none;
border-radius: 999px;
padding: 10px 16px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.primary-btn {
background: var(--color-accent);
color: var(--color-accent-text);
}
.secondary-btn {
background: var(--color-bg-secondary);
color: var(--color-text);
border: 1px solid rgba(127,127,127,0.16);
}
.secondary-btn.compact {
padding: 8px 14px;
}
.login-hint-pill {
margin-bottom: var(--sp-3);
padding: var(--sp-2) var(--sp-3);
border-radius: var(--r-sm);
background: color-mix(in srgb, var(--color-okay) 15%, transparent);
color: var(--color-okay);
font-size: 13px;
text-align: center;
}
.empty-state-icon {
font-size: 48px;
margin-bottom: 12px;
}
.empty-state-text {
font-size: 15px;
}
/* Map floating action button — sits above the bottom sheet's resting peek
on mobile by leaving a generous bottom inset. On desktop the sheet is a
side panel, so the FAB returns to a tighter offset (overridden below). */
.map-fab {
position: absolute;
bottom: 20px;
right: 16px;
z-index: 1000;
width: 46px;
height: 46px;
border-radius: var(--r-md);
border: none;
background: var(--color-bg);
color: var(--color-accent);
box-shadow: var(--shadow-3);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
transition: transform 0.18s cubic-bezier(0.2, 0, 0, 1), opacity 0.15s ease;
}
.map-fab:active { opacity: 0.7; transform: scale(0.92); }
/* Row that holds the "Search here" pill (and, in scan-picker mode, a round
cancel button to its left). Lives inside #map-search-top-row, inline to
the right of the search box, so it sits on the same line as the search
bar. Hidden (display:none below) until the map has moved, at which point
the box shrinks to make room. */
.map-search-here-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex: 0 0 auto;
margin-top: 0;
pointer-events: none;
}
.map-search-here-row > * {
pointer-events: auto;
}
.map-search-here-row:empty,
.map-search-here-row:has(> .map-search-here.hidden:only-child) {
display: none;
}
.map-search-here {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
border-radius: 999px;
border: none;
background: var(--color-accent);
color: var(--color-accent-text);
font-size: 14px;
font-weight: 600;
box-shadow: 0 4px 14px rgba(0,0,0,0.2);
cursor: pointer;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
animation: searchHerePopIn 0.26s cubic-bezier(0.34, 1.56, 0.64, 1);
white-space: nowrap;
}
.map-search-here:active { opacity: 0.85; transform: scale(0.96); }
.map-search-here.hidden { display: none; }
.map-search-here[disabled] {
background: var(--color-hint);
color: var(--color-bg);
opacity: 0.75;
cursor: not-allowed;
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
.map-search-here[disabled]:active { transform: none; opacity: 0.75; }
@keyframes searchHerePopIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
/* "Confirm" state of the search-here pill (during scan-picker mode) */
.map-search-here.is-confirm {
background: var(--color-accent);
color: var(--color-accent-text);
box-shadow: 0 6px 18px rgba(0, 122, 255, 0.35);
}
/* Round cancel pill placed left of the confirm button while picking.
JS inserts it as a sibling of the search-here pill inside
.map-search-here-row, so flex centering handles the side-by-side layout. */
.map-scan-cancel {
width: 38px;
height: 38px;
border-radius: 50%;
border: none;
background: var(--color-bg);
color: var(--color-text);
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
cursor: pointer;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
animation: searchHerePopIn 0.22s ease-out;
flex-shrink: 0;
}
.map-scan-cancel:active { opacity: 0.85; transform: scale(0.94); }
/* Draggable pin shown in the centre of the scan-radius picker circle */
.scan-pin {
width: 28px;
height: 28px;
border-radius: 50%;
background: color-mix(in srgb, var(--scan-accent, #007aff) 18%, transparent);
border: 2px solid var(--scan-accent, #007aff);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
cursor: grab;
}
.scan-pin:active { cursor: grabbing; }
.scan-pin-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--scan-accent, #007aff);
box-shadow: 0 0 0 3px var(--color-bg);
animation: scanPinPulse 1.6s ease-in-out infinite;
}
@keyframes scanPinPulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(0.78); opacity: 0.85; }
}
/* Yellow pin shown for queued scan requests waiting on the cooldown.
Click to remove the pin (and its queue entry). */
.scan-queue-pin {
width: 24px;
height: 24px;
border-radius: 50%;
background: rgba(255, 193, 7, 0.22);
border: 2px solid #ffc107;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
cursor: pointer;
}
.scan-queue-pin-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #ffc107;
box-shadow: 0 0 0 2px var(--color-bg);
animation: scanQueuePinPulse 1.4s ease-in-out infinite;
}
@keyframes scanQueuePinPulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(0.65); opacity: 0.65; }
}
/* The scan UI follows what the user is LOOKING at (map centre), not where
their pin sits — so searching Salzburg then panning to Munich should
still show the "Hier suchen" pill. Keyed off data-view-country. */
[data-view-country="at"] .map-search-here,
[data-view-country="at"] .map-scan-cancel,
[data-view-country="at"] .map-search-here-row,
[data-view-country="at"] .scan-queue-pin {
display: none !important;
}
/* Brighten dark map labels for readability */
.bright-labels {
filter: brightness(1.8) contrast(1.1);
}
/* Lift dark-mode tiles so place names stay readable without washing
out the dark background — brightness pulls labels up, lower contrast
prevents them from collapsing back into the background. */
.map-tiles-dark {
filter: brightness(1.75) contrast(0.95);
}
/* Match the Leaflet container background to the active tile palette so
loading tiles don't flash white before they paint. */
[data-theme="dark"] .leaflet-container {
background: #313131;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]) .leaflet-container {
background: #313131;
}
}
/* Disable touch/select quirks on Leaflet chrome */
.leaflet-container,
.leaflet-pane,
.leaflet-control-container,
.leaflet-control-attribution,
.leaflet-control-zoom {
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}
/* Hidden attribution for cleaner UX */
.leaflet-control-attribution {
display: none !important;
}
/* Settings Row (label + dropdown) */
.settings-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--sp-3) var(--sp-4);
gap: var(--sp-3);
}
.settings-row-left {
display: flex;
align-items: center;
gap: var(--sp-3);
min-width: 0;
}
.settings-row-left span {
text-transform: none;
letter-spacing: 0;
}
.settings-select {
-webkit-appearance: none;
appearance: none;
background: var(--color-bg-secondary);
color: var(--color-text);
border: 1px solid var(--color-separator);
border-radius: 8px;
padding: 8px 36px 8px 12px;
font-size: 14px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
outline: none;
transition: border-color 0.15s;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2398989e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 16px;
min-width: 0;
max-width: 160px;
}
.settings-select:focus {
border-color: var(--color-accent);
}
.settings-select option {
background: var(--color-bg);
color: var(--color-text);
}
/* Theme Segmented Control */
.theme-seg {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 0;
border: none;
border-radius: 8px;
background: transparent;
color: var(--color-hint);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, color 0.2s, box-shadow 0.2s;
-webkit-user-select: none;
user-select: none;
}
.theme-seg.active {
background: rgba(127,127,127,0.25);
color: var(--color-text);
box-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
.theme-seg:active {
transform: scale(0.97);
}
.theme-seg svg {
flex-shrink: 0;
}
/* Alert Channel Picker (reuses theme-seg pattern) */
.alert-ch-seg {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 0;
border: none;
border-radius: 8px;
background: transparent;
color: var(--color-hint);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.18s, color 0.18s;
-webkit-user-select: none;
user-select: none;
}
.alert-ch-seg:hover:not(.active) {
color: var(--color-text-2);
}
.alert-ch-seg.active {
background: var(--color-accent);
color: var(--color-accent-text);
box-shadow: 0 2px 8px color-mix(in srgb, var(--color-accent) 30%, transparent);
}
.alert-ch-seg:active { transform: scale(0.97); }
.alert-ch-seg svg { flex-shrink: 0; }
/* Toggle Switch */
.toggle-switch { position: relative; display: inline-block; width: 51px; height: 31px; flex-shrink: 0; }
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-track {
position: absolute; cursor: pointer; inset: 0;
background: var(--color-hint);
border-radius: 31px; transition: 0.2s;
}
.toggle-track:before {
content: ""; position: absolute;
width: 27px; height: 27px; left: 2px; bottom: 2px;
background: white; border-radius: 50%;
transition: 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.toggle-switch input:checked + .toggle-track { background: #34c759; }
.toggle-switch input:checked + .toggle-track:before { transform: translateX(20px); }
/* === Price Alert card — redesigned for the settings tab === */
.alert-card {
padding: 0;
overflow: hidden;
transition: border-color 0.25s ease, background 0.25s ease;
}
.alert-card.is-armed {
border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-separator));
background: linear-gradient(180deg, color-mix(in srgb, var(--color-accent) 5%, var(--color-bg-secondary)) 0%, var(--color-bg-secondary) 60%);
}
.alert-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
padding: var(--sp-4);
}
.alert-toggle-left {
display: flex;
align-items: center;
gap: var(--sp-3);
min-width: 0;
}
.alert-toggle-text {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.alert-toggle-title {
font-size: 15px;
font-weight: 600;
color: var(--color-text);
}
.alert-toggle-sub {
font-size: 11px;
color: var(--color-hint);
line-height: 1.4;
}
.alert-config-body {
border-top: 1px solid var(--color-separator);
padding: var(--sp-4);
display: flex;
flex-direction: column;
gap: var(--sp-4);
}
.alert-config-group { display: flex; flex-direction: column; gap: 6px; }
.alert-config-label {
font-size: 11px;
font-weight: 600;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Threshold hero — integrated into the card body, no competing inner panel */
.alert-threshold {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 14px;
padding: 4px 0 2px;
}
.alert-threshold-head {
font-size: 11px;
font-weight: 600;
color: var(--color-hint);
text-transform: uppercase;
letter-spacing: 0.6px;
text-align: center;
}
.alert-threshold-row {
display: flex;
align-items: center;
justify-content: center;
gap: 22px;
}
.alert-step-btn {
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid transparent;
background: color-mix(in srgb, var(--color-text) 6%, transparent);
color: var(--color-text);
display: grid;
place-items: center;
cursor: pointer;
font-family: inherit;
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
transition: background 0.15s, transform 0.12s, color 0.15s;
}
.alert-step-btn:hover {
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
color: var(--color-accent);
}
.alert-step-btn:active { transform: scale(0.92); }
.alert-step-btn svg { width: 18px; height: 18px; }
.alert-threshold-display {
font-size: 42px;
font-weight: 700;
color: var(--color-text);
letter-spacing: -0.03em;
font-variant-numeric: tabular-nums;
min-width: 132px;
width: 132px;
text-align: center;
line-height: 1;
font-family: inherit;
background: transparent;
border: 0;
outline: 0;
padding: 6px 4px;
border-radius: 10px;
box-sizing: border-box;
caret-color: var(--color-accent);
-webkit-appearance: none;
appearance: none;
transition: background 0.15s;
}
.alert-threshold-display:focus {
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.alert-threshold-display::-webkit-outer-spin-button,
.alert-threshold-display::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.alert-threshold-bar {
position: relative;
margin-top: 2px;
cursor: pointer;
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
.alert-threshold-bar:focus { outline: none; }
.alert-threshold-bar:focus-visible .alert-threshold-bar-marker {
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 22%, transparent),
0 0 0 7px color-mix(in srgb, var(--color-accent) 36%, transparent),
0 2px 8px rgba(0, 0, 0, 0.22);
}
.alert-threshold-bar[hidden] { display: none; }
.alert-threshold-bar-track {
height: 5px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-text) 10%, transparent);
}
.alert-threshold-bar-current,
.alert-threshold-bar-marker {
position: absolute;
top: 2.5px; /* center of the 5px-tall track, regardless of label height below */
transform: translate(-50%, -50%);
}
.alert-threshold-bar-current {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--color-hint);
border: 2px solid var(--color-bg-secondary);
}
.alert-threshold-bar-marker {
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--color-accent);
border: 2px solid var(--color-bg-secondary);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 22%, transparent),
0 2px 8px rgba(0, 0, 0, 0.22);
transition: left 0.22s cubic-bezier(0.22, 1, 0.36, 1);
cursor: grab;
}
.alert-threshold-bar.is-dragging { cursor: grabbing; }
.alert-threshold-bar.is-dragging .alert-threshold-bar-marker {
transition: none;
cursor: grabbing;
}
.alert-threshold-bar-labels {
display: flex;
justify-content: space-between;
margin-top: 12px;
font-size: 11px;
color: var(--color-text-2);
font-variant-numeric: tabular-nums;
letter-spacing: 0.2px;
opacity: 0.7;
}
.alert-ref-price {
text-align: center;
font-size: 12px;
color: var(--color-hint);
margin-top: 4px;
letter-spacing: 0.1px;
}
/* Channel section */
.alert-channel-section {
display: flex;
flex-direction: column;
gap: 8px;
}
.alert-channel-picker {
display: flex;
border-radius: 10px;
background: color-mix(in srgb, var(--color-text) 5%, transparent);
padding: 3px;
gap: 3px;
}
.alert-input {
width: 100%;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid var(--color-separator);
background: var(--color-bg-secondary);
color: var(--color-text);
font-size: 14px;
font-family: inherit;
box-sizing: border-box;
transition: border-color 0.15s, box-shadow 0.15s;
}
.alert-input:focus-visible {
border-color: var(--color-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 15%, transparent);
outline: none;
}
.alert-input-hint {
font-size: 11px;
color: var(--color-hint);
margin-top: 6px;
line-height: 1.45;
}
/* Status / background-check panel */
.alert-status-panel {
display: flex;
flex-direction: column;
gap: 8px;
padding: 14px 16px;
border-radius: 10px;
background: color-mix(in srgb, var(--color-text) 5%, transparent);
transition: background 0.2s;
}
.alert-status-panel.is-armed {
background: color-mix(in srgb, var(--color-good) 8%, transparent);
}
.alert-status-panel.is-below {
background: color-mix(in srgb, var(--color-accent) 9%, transparent);
}
.alert-status-row {
display: flex;
align-items: center;
gap: 14px;
font-size: 13px;
color: var(--color-text);
font-weight: 500;
}
.alert-status-row-secondary {
font-size: 11.5px;
color: var(--color-hint);
font-weight: 500;
letter-spacing: 0.1px;
}
.alert-status-row-secondary[hidden] { display: none; }
.alert-status-dot {
display: inline-block;
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--color-hint);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-hint) 18%, transparent);
}
.alert-status-row.is-armed .alert-status-dot {
background: var(--color-good);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-good) 22%, transparent);
animation: alert-status-pulse 2.6s ease-in-out infinite;
}
.alert-status-row.is-below .alert-status-dot {
background: var(--color-accent);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 26%, transparent);
animation: none;
}
.alert-status-icon {
flex-shrink: 0;
opacity: 0.7;
}
@keyframes alert-status-pulse {
0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-good) 18%, transparent); }
50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-good) 28%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
.alert-status-dot { animation: none; }
}
/* Action buttons — clear primary/tertiary hierarchy */
.alert-actions {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 14px;
margin-top: 8px;
}
.alert-save-btn {
width: 100%;
padding: 13px;
border-radius: 12px;
border: none;
background: var(--color-accent);
color: var(--color-accent-text);
font-size: 15px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: box-shadow 0.18s ease, transform 0.12s ease, filter 0.15s ease;
}
.alert-save-btn:hover {
box-shadow: 0 6px 18px color-mix(in srgb, var(--color-accent) 35%, transparent);
filter: brightness(1.05);
}
.alert-save-btn:active { transform: scale(0.98); }
.alert-test-btn {
align-self: center;
padding: 8px 12px;
border-radius: 8px;
border: none;
background: transparent;
color: var(--color-hint);
font-size: 12px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: color 0.15s, background 0.15s;
}
.alert-test-btn:hover {
color: var(--color-text);
background: color-mix(in srgb, var(--color-text) 5%, transparent);
}
.alert-active-info {
margin-top: 10px;
padding: 10px 12px;
border-radius: 8px;
background: var(--color-bg-secondary);
font-size: 13px;
color: var(--color-hint);
text-align: center;
}
.settings-row-label {
font-size: 15px;
font-weight: 500;
}
.card-toggle-row {
padding: 12px 16px;
}
/* === Location picker select === */
.location-picker {
width: 100%;
padding: var(--sp-2) var(--sp-3);
border-radius: var(--r-sm);
border: 1px solid var(--color-separator);
background: var(--color-bg-secondary);
color: var(--color-text);
font-size: 14px;
font-family: inherit;
box-sizing: border-box;
transition: border-color 0.15s, box-shadow 0.15s;
}
.location-picker-row {
display: flex;
flex-direction: column;
gap: 4px;
}
/* === Custom desktop dropdown — desktop replaces the native <select>
with a styled trigger + animated panel. On mobile the native
select stays (the OS picker is the right affordance there). === */
.custom-select { position: relative; width: 100%; }
.custom-select-trigger,
.custom-select-panel { display: none; }
@media (min-width: 900px) {
.custom-select > select { display: none; }
.custom-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 9px 14px;
border-radius: 10px;
border: 1px solid var(--color-separator);
background: var(--color-bg-secondary);
color: var(--color-text);
font-size: 14px;
font-family: inherit;
cursor: pointer;
text-align: left;
transition: border-color 0.2s var(--dt-ease),
box-shadow 0.2s var(--dt-ease);
}
.custom-select-trigger:hover {
border-color: var(--color-hint);
}
.custom-select.open .custom-select-trigger {
border-color: var(--color-accent);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
}
.custom-select-value {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-select-caret {
flex-shrink: 0;
color: var(--color-hint);
transition: transform 0.22s var(--dt-ease);
}
.custom-select.open .custom-select-caret {
transform: rotate(180deg);
}
.custom-select-panel {
display: block;
position: absolute;
left: 0;
right: 0;
top: calc(100% + 6px);
z-index: 100;
border-radius: 12px;
background: var(--color-bg-secondary);
border: 1px solid var(--color-separator);
box-shadow: var(--dt-shadow-md);
overflow: hidden;
max-height: 280px;
overflow-y: auto;
opacity: 0;
transform: translateY(-6px) scale(0.98);
transform-origin: top center;
pointer-events: none;
transition: opacity 0.18s var(--dt-ease), transform 0.2s var(--dt-ease);
}
.custom-select.open .custom-select-panel {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.custom-select-option {
padding: 9px 14px;
cursor: pointer;
font-size: 14px;
color: var(--color-text);
line-height: 1.3;
transition: background 0.15s var(--dt-ease);
user-select: none;
}
.custom-select-option:hover {
background: color-mix(in srgb, var(--color-accent) 9%, transparent);
}
.custom-select-option.selected {
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
color: var(--color-accent);
font-weight: 600;
}
/* In the settings row, the picker sits to the right of an icon + label
and should match the existing settings-select width. */
.settings-row .custom-select {
width: auto;
min-width: 132px;
}
}
.location-auto-hint {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: var(--color-hint);
padding-left: 4px;
letter-spacing: 0.1px;
}
.location-auto-hint[hidden] { display: none; }
.location-auto-hint svg { opacity: 0.75; flex-shrink: 0; }
.location-picker:hover {
border-color: var(--color-hint);
}
.location-picker:focus-visible {
border-color: var(--color-accent);
outline: none;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent);
}
.request-location-btn {
width: 100%;
padding: 14px 16px;
background: transparent;
border: none;
cursor: pointer;
color: var(--color-accent);
font-size: 15px;
font-weight: 500;
border-top: 1px solid var(--color-separator);
font-family: inherit;
transition: background 0.15s;
}
.request-location-btn:hover {
background: var(--color-separator);
}
.account-card {
display: grid;
gap: 14px;
}
.account-main-row {
display: flex;
align-items: center;
gap: 14px;
}
.account-avatar {
width: 56px;
height: 56px;
border-radius: 18px;
overflow: hidden;
background: var(--color-bg-secondary);
display: none;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 700;
color: var(--color-accent);
flex-shrink: 0;
}
.account-avatar.visible {
display: flex;
}
.account-avatar.has-image {
background: transparent;
}
.account-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.account-meta {
min-width: 0;
flex: 1;
}
.account-name-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.account-subline {
margin-top: 4px;
white-space: normal;
}
.account-auth-badge {
background: rgba(52,199,89,0.14);
color: #34c759;
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
border-radius: 999px;
}
.account-actions {
display: flex;
justify-content: flex-end;
}
.toast {
position: fixed;
left: 50%;
bottom: calc(var(--tab-height) + var(--safe-bottom) + 18px);
transform: translateX(-50%) translateY(20px);
background: rgba(17,17,17,0.92);
color: #fff;
padding: 12px 16px;
border-radius: 12px;
font-size: 14px;
font-weight: 600;
box-shadow: 0 10px 24px rgba(0,0,0,0.24);
opacity: 0;
pointer-events: none;
transition: opacity 0.22s ease, transform 0.22s ease;
z-index: 1200;
max-width: min(92vw, 420px);
text-align: center;
}
.toast.visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.is-hidden {
display: none !important;
}
.admin-page {
position: fixed;
inset: 0;
overflow-y: auto;
background:
radial-gradient(circle at top left, rgba(0,122,255,0.12), transparent 30%),
radial-gradient(circle at top right, rgba(52,199,89,0.12), transparent 28%),
linear-gradient(180deg, #f4f8ff 0%, #eef3f7 100%);
color: #142033;
}
.admin-shell {
width: min(980px, calc(100vw - 32px));
margin: 0 auto;
padding: 32px 0 48px;
display: grid;
gap: 18px;
}
.admin-hero {
padding: 8px 2px 0;
}
.admin-eyebrow {
font-size: 12px;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #3d6cb9;
}
.admin-hero h1 {
font-size: clamp(30px, 4vw, 44px);
line-height: 1;
letter-spacing: -0.04em;
margin-top: 10px;
}
.admin-lead {
margin-top: 12px;
max-width: 680px;
font-size: 16px;
color: #49617d;
}
.admin-feedback {
display: none;
padding: 14px 16px;
border-radius: 14px;
font-weight: 600;
box-shadow: 0 16px 28px rgba(20, 32, 51, 0.08);
}
.admin-feedback.visible {
display: block;
}
.admin-feedback.info {
background: #e8f1ff;
color: #214d91;
}
.admin-feedback.success {
background: #e5f6eb;
color: #1b6b3b;
}
.admin-feedback.error {
background: #ffe8e8;
color: #982e2e;
}
.admin-card {
background: rgba(255,255,255,0.9);
border: 1px solid rgba(61,108,185,0.12);
border-radius: 24px;
padding: 24px;
box-shadow: 0 18px 44px rgba(20, 32, 51, 0.1);
backdrop-filter: blur(14px);
}
.admin-card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 20px;
}
.admin-card-head h2 {
font-size: 24px;
letter-spacing: -0.03em;
}
.admin-card-head p {
margin-top: 6px;
color: #5d738c;
}
.admin-form {
display: grid;
gap: 18px;
}
.admin-form.narrow {
max-width: 420px;
}
.admin-form-block {
display: grid;
gap: 14px;
padding-top: 4px;
}
.admin-form-block h3 {
font-size: 15px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #587291;
}
.admin-grid {
display: grid;
gap: 14px;
}
.admin-grid.two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.admin-field {
display: grid;
gap: 8px;
}
.admin-field.full {
grid-column: 1 / -1;
}
.admin-field span {
font-size: 13px;
font-weight: 700;
color: #40556f;
}
.admin-field input,
.admin-field select,
.admin-field textarea {
width: 100%;
border: 1px solid rgba(61,108,185,0.18);
border-radius: 14px;
background: #fff;
padding: 12px 14px;
font: inherit;
color: #142033;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
}
.admin-field textarea {
resize: vertical;
min-height: 168px;
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
font-size: 13px;
line-height: 1.5;
}
.admin-field input:focus,
.admin-field select:focus,
.admin-field textarea:focus {
outline: 2px solid rgba(0,122,255,0.2);
border-color: rgba(0,122,255,0.45);
}
.admin-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
@media (max-width: 720px) {
.admin-shell {
width: min(100vw - 20px, 980px);
padding-top: 20px;
}
.admin-card {
padding: 18px;
border-radius: 18px;
}
.admin-grid.two {
grid-template-columns: 1fr;
}
}
/* === PWA Install Popup === */
.pwa-popup {
position: fixed;
inset: 0;
z-index: 10000;
display: flex;
align-items: flex-end;
justify-content: center;
}
.pwa-popup.hidden { display: none; }
.pwa-popup-backdrop {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.45);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.pwa-popup-sheet {
position: relative;
width: 100%;
max-width: 420px;
background: var(--color-bg);
border-radius: 20px 20px 0 0;
padding: 28px 24px calc(24px + var(--safe-bottom));
box-shadow: 0 -4px 32px rgba(0,0,0,0.18);
animation: pwa-slide-up 0.35s cubic-bezier(0.32, 0.72, 0, 1);
text-align: center;
}
@keyframes pwa-slide-up {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.pwa-popup-icon {
margin-bottom: 16px;
}
.pwa-popup-icon img {
width: 72px;
height: 72px;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0,0,0,0.12);
}
html[data-theme="dark"] .pwa-popup-icon img,
:root:not([data-theme="light"]) .pwa-popup-icon img {
background: #1c1c1e;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) .pwa-popup-icon img {
background: transparent;
}
}
.pwa-popup-title {
font-size: 22px;
font-weight: 700;
color: var(--color-text);
margin-bottom: 6px;
letter-spacing: -0.3px;
}
.pwa-popup-desc {
font-size: 14px;
color: var(--color-hint);
line-height: 1.45;
margin-bottom: 20px;
max-width: 320px;
margin-left: auto;
margin-right: auto;
}
.pwa-popup-tabs {
display: flex;
background: var(--color-bg-secondary);
border-radius: 10px;
padding: 3px;
margin-bottom: 16px;
gap: 2px;
}
.pwa-popup-tab {
flex: 1;
padding: 7px 8px;
border: none;
background: transparent;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
color: var(--color-hint);
cursor: pointer;
transition: all 0.2s;
}
.pwa-popup-tab.active {
background: var(--color-bg);
color: var(--color-text);
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
}
.pwa-popup-panel {
display: none;
}
.pwa-popup-panel.active {
display: block;
animation: pwa-fade-in 0.2s ease;
}
@keyframes pwa-fade-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.pwa-popup-steps {
list-style: none;
text-align: left;
margin: 0;
padding: 0;
}
.pwa-popup-steps li {
display: flex;
align-items: center;
gap: 14px;
padding: 12px 14px;
border-radius: 12px;
background: var(--color-bg-secondary);
margin-bottom: 8px;
font-size: 14px;
color: var(--color-text);
line-height: 1.4;
}
.pwa-popup-steps li:last-child {
margin-bottom: 0;
}
.pwa-step-icon {
flex-shrink: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(--color-accent);
}
.pwa-step-icon svg {
fill: #fff;
}
.pwa-popup-btn {
display: block;
width: 100%;
margin-top: 20px;
padding: 15px;
border: none;
border-radius: 14px;
background: var(--color-accent);
color: #fff;
font-size: 17px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.15s;
letter-spacing: -0.2px;
}
.pwa-popup-btn:active {
opacity: 0.8;
}
/* === Pull-to-Refresh === */
#ptr-container {
position: fixed;
top: env(safe-area-inset-top, 0px);
left: 0;
right: 0;
display: flex;
justify-content: center;
pointer-events: none;
z-index: 90;
padding-top: 12px;
}
.native-spinner {
width: 20px;
height: 20px;
opacity: 0;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='%23000'%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.12'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.25' transform='rotate(45 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.37' transform='rotate(90 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.50' transform='rotate(135 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.62' transform='rotate(180 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.75' transform='rotate(225 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.87' transform='rotate(270 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='1' transform='rotate(315 10 10)'/%3E%3C/g%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='%23000'%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.12'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.25' transform='rotate(45 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.37' transform='rotate(90 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.50' transform='rotate(135 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.62' transform='rotate(180 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.75' transform='rotate(225 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='.87' transform='rotate(270 10 10)'/%3E%3Crect x='9' y='1' width='2' height='5' rx='1' opacity='1' transform='rotate(315 10 10)'/%3E%3C/g%3E%3C/svg%3E");
-webkit-mask-size: contain;
mask-size: contain;
background: var(--color-text);
animation: native-spin 0.8s steps(8) infinite;
transition: opacity 0.3s ease;
}
@keyframes native-spin {
to { transform: rotate(360deg); }
}
/* Shift app content during pull */
#app.ptr-pulling {
transition: none;
}
#app.ptr-snapping {
transition: transform 0.45s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
#app.ptr-refreshing {
transition: transform 0.35s cubic-bezier(0.25, 0.1, 0.25, 1);
}
/* === About Section === */
.about-card {
padding: 0 !important;
overflow: hidden;
}
.about-app-row {
display: flex;
align-items: center;
gap: 14px;
padding: 16px;
}
.about-app-icon {
border-radius: 14px;
flex-shrink: 0;
}
.about-app-name {
font-size: 17px;
font-weight: 600;
color: var(--color-text);
line-height: 1.2;
}
.about-app-version {
font-size: 13px;
color: var(--color-hint);
margin-top: 2px;
}
.about-links-card {
margin-top: 0;
}
.about-row {
display: flex;
align-items: center;
gap: 12px;
padding: 13px 16px;
width: 100%;
text-decoration: none;
color: var(--color-text);
background: none;
border: none;
font-family: inherit;
cursor: pointer;
text-align: left;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
transition: background 0.15s;
}
.about-row:active {
background: var(--color-bg-secondary);
}
.about-row-icon {
color: var(--color-hint);
flex-shrink: 0;
}
.about-row-label {
font-size: 15px;
font-weight: 500;
flex: 1;
}
.about-row-chevron {
opacity: 0.35;
flex-shrink: 0;
transition: transform 0.3s ease;
}
.about-row-divider {
height: 1px;
background: var(--color-border);
margin-left: 48px;
}
.about-contributors-chevron.open {
transform: rotate(90deg);
}
.about-contributors-list {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.about-contributors-list.open {
max-height: 300px;
}
.about-contributor {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px 10px 48px;
text-decoration: none;
color: var(--color-text);
-webkit-tap-highlight-color: transparent;
transition: background 0.15s;
}
.about-contributor:active {
background: var(--color-bg-secondary);
}
.about-contributor-avatar {
border-radius: 50%;
flex-shrink: 0;
}
.about-contributor-info {
flex: 1;
min-width: 0;
}
.about-contributor-name {
font-size: 14px;
font-weight: 500;
color: var(--color-text);
line-height: 1.3;
}
.about-contributor-role {
font-size: 12px;
color: var(--color-hint);
line-height: 1.3;
}
.about-footer {
text-align: center;
padding: 8px 0 24px;
font-size: 11px;
color: var(--color-hint);
opacity: 0.5;
}
/* === Desktop Station Panel (transparent wrapper on mobile) === */
.desktop-station-panel {
display: contents;
}
/* === Desktop Layout === */
@media (min-width: 900px) {
/* --- Desktop-specific custom properties --- */
:root {
--sidebar-w: 88px;
--panel-w: 360px;
--sheet-w: 440px;
--content-max: 920px;
--content-max-narrow: 660px;
--dt-ease: cubic-bezier(0.22, 1, 0.36, 1);
--dt-ease-bounce: cubic-bezier(0.34, 1.4, 0.64, 1);
--dt-duration: 0.4s;
--dt-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--dt-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08), 0 1px 4px rgba(0, 0, 0, 0.04);
--dt-shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06);
--dt-shadow-sheet: 0 16px 64px rgba(0, 0, 0, 0.14), 0 6px 20px rgba(0, 0, 0, 0.08);
--dt-blur: 20px;
}
html[data-theme="dark"] {
--dt-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
--dt-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
--dt-shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.35);
--dt-shadow-sheet: 0 16px 64px rgba(0, 0, 0, 0.45);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--dt-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
--dt-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
--dt-shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.35);
--dt-shadow-sheet: 0 16px 64px rgba(0, 0, 0, 0.45);
}
}
/* --- Sidebar Navigation --- */
.tab-bar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: auto;
width: var(--sidebar-w);
height: 100%;
flex-direction: column;
justify-content: flex-start;
padding: 16px 8px;
border-top: none;
border-right: 1px solid var(--color-separator);
gap: 2px;
z-index: 100;
background: var(--color-bg-secondary);
}
.tab-item {
padding: 10px 0;
width: 100%;
gap: 3px;
border-radius: 10px;
position: relative;
transition: color 0.2s ease,
background 0.2s ease;
}
.tab-item span {
font-size: 10px;
font-weight: 500;
letter-spacing: 0.01em;
transition: opacity 0.2s ease;
}
.tab-item:hover {
background: var(--color-separator);
}
.tab-item:active {
background: var(--color-separator);
opacity: 0.7;
}
.tab-item.active {
color: var(--color-accent);
background: rgba(0, 122, 255, 0.1);
}
.tab-item.active span {
font-weight: 600;
}
.tab-icon {
transition: transform 0.3s var(--dt-ease-bounce);
}
.tab-item.active .tab-icon {
animation: dt-tab-pop 0.4s var(--dt-ease-bounce);
}
@keyframes dt-tab-pop {
0% { transform: scale(1); }
35% { transform: scale(1.18); }
65% { transform: scale(0.94); }
100% { transform: scale(1); }
}
/* --- App container shifts right --- */
#app {
left: var(--sidebar-w);
bottom: 0;
}
/* --- Tab view transitions (desktop-refined) --- */
.tab-view {
transition: opacity var(--dt-duration) var(--dt-ease),
transform var(--dt-duration) var(--dt-ease);
transform: scale(0.97) translateY(8px);
}
.tab-view.active {
transform: scale(1) translateY(0);
}
/* --- Non-map views: centered content with padding --- */
#view-history,
#view-stats,
#view-settings {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 var(--sp-6) var(--sp-7);
}
/* On desktop the view already provides side padding, so sections shed
their inner 16 px gutter and let the centred max-width column do the
work. Cards/headers align flush to that column. */
#view-history > .section,
#view-stats > .section,
#view-settings > .section {
padding-left: 0;
padding-right: 0;
}
/* Stat-row's own 16 px gutter is now redundant for the same reason. */
#view-history .stat-row,
#view-stats .stat-row {
padding-left: 0;
padding-right: 0;
}
/* Page header sits flush with the content column and gets a more
generous vertical rhythm on desktop. */
.page-header {
padding: var(--sp-7) 0 var(--sp-5);
}
.page-title {
font-size: 32px;
}
.page-description {
font-size: 15px;
margin-top: var(--sp-2);
}
#view-history.active,
#view-stats.active,
#view-settings.active {
display: flex;
}
#view-history > *,
#view-stats > *,
#view-settings > * {
width: 100%;
max-width: var(--content-max);
}
/* Bigger chart on desktop — the extra horizontal space deserves a
taller graph instead of a 280px squish. Step it up at the tablet
breakpoint so 13" laptops don't get a 400px chart that dominates
the viewport. */
#view-history .chart-container,
#view-stats .chart-container {
height: 340px;
}
@media (min-width: 1200px) {
#view-history .chart-container,
#view-stats .chart-container {
height: 400px;
}
}
/* Pair the two summary cards side-by-side when there's room. The
section-header above spans both columns. */
#history-summary {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: var(--sp-3);
row-gap: var(--sp-2);
align-items: start;
}
/* Any non-extreme-card child (section-header, empty-state message) spans
the full width of the summary grid. */
#history-summary > *:not(.extreme-card) {
grid-column: 1 / -1;
}
#history-summary > .extreme-card {
padding: var(--sp-5) var(--sp-5);
}
#history-summary > .extreme-card .extreme-value {
font-size: 32px;
}
/* Desktop card refinements — border + a hint of shadow. The border does
the visual heavy lifting (shadcn-style), shadow just adds depth on hover. */
.card {
border-radius: var(--r-lg);
padding: var(--sp-4) var(--sp-5);
transition: box-shadow 0.25s var(--dt-ease),
border-color 0.25s var(--dt-ease);
}
.card:hover {
box-shadow: var(--shadow-1);
}
.card-list {
border-radius: var(--r-lg);
transition: box-shadow 0.25s var(--dt-ease);
}
.card-list:hover {
box-shadow: var(--shadow-1);
}
.metric-card,
.best-time-card,
.extreme-card,
.stats-hero,
.stats-hero-card,
.stats-fact-card {
border-radius: var(--r-lg);
transition: box-shadow 0.25s var(--dt-ease), transform 0.25s var(--dt-ease);
}
.metric-card { padding: var(--sp-4) var(--sp-5); }
.best-time-card { padding: var(--sp-5) var(--sp-5); }
.extreme-card { padding: var(--sp-5) var(--sp-5); }
.stats-hero { padding: var(--sp-6) var(--sp-6); }
.stats-hero-card { padding: var(--sp-6); }
.stats-fact-card { padding: var(--sp-4); gap: var(--sp-3); }
.stats-fact-card .stats-fact-icon { width: 38px; height: 38px; border-radius: 11px; }
.stats-fact-card .stats-fact-icon svg { width: 18px; height: 18px; }
.stats-fact-value { font-size: 18px; }
.stats-hero-value { font-size: 44px; }
.stats-hero-glyph { width: 52px; height: 52px; border-radius: 14px; }
.stats-hero-glyph svg { width: 28px; height: 28px; }
.stats-facts-row { gap: var(--sp-3); }
#stats-content > .metric-row {
margin-left: 0;
margin-right: 0;
}
#stats-content > .section {
padding-left: 0;
padding-right: 0;
}
#stats-content > .stats-overview-row {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
#stats-content .metric-card,
#stats-content .best-time-card {
min-height: 88px;
padding: var(--sp-4) var(--sp-5);
}
.metric-card:hover,
.best-time-card:hover,
.extreme-card:hover,
.stats-hero:hover,
.stats-fact-card:hover {
box-shadow: var(--shadow-1);
}
.stats-hero-card:hover {
box-shadow: var(--dt-shadow-md);
}
/* Desktop: roomier tiles & bar chart */
.stats-tile-grid { gap: 8px; }
.stats-tile {
min-height: 80px;
padding: 14px 4px 12px;
border-radius: 14px;
transition: transform 0.18s var(--dt-ease), box-shadow 0.2s var(--dt-ease), border-color 0.2s var(--dt-ease), background 0.2s var(--dt-ease);
}
.stats-tile:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-1);
}
.stats-tile-name { font-size: 11px; }
.stats-tile-value { font-size: 16px; }
.stats-tile-bar { height: 4px; }
.stats-hour-chart-wrap { height: 210px; }
.stats-hour-chart-wrap canvas {
filter: drop-shadow(0 6px 22px rgba(255, 149, 0, 0.12));
}
/* History tab: roomier hero card, bigger headline value, and lift
the extreme cards on hover so they read as obviously clickable. */
.history-hero-card {
padding: var(--sp-6);
border-radius: var(--r-lg);
transition: box-shadow 0.25s var(--dt-ease);
}
.history-hero-card:hover {
box-shadow: var(--dt-shadow-md);
}
.history-hero-value { font-size: 44px; }
.history-hero-glyph {
width: 52px;
height: 52px;
border-radius: 14px;
}
.history-hero-glyph svg { width: 28px; height: 28px; }
.history-hero-pills { gap: 10px; margin-top: 18px; }
.history-extreme-row { gap: var(--sp-3); }
.history-extreme-card {
border-radius: var(--r-lg);
padding: 14px 16px 14px 20px;
transition: transform 0.18s var(--dt-ease),
box-shadow 0.22s var(--dt-ease),
border-color 0.22s var(--dt-ease);
}
.history-extreme-card:hover {
transform: translateY(-1px);
box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 16%, transparent);
}
.history-extreme-icon {
width: 38px;
height: 38px;
border-radius: 11px;
}
.history-extreme-value { font-size: 22px; }
#price-chart, #hour-chart {
filter: drop-shadow(0 6px 20px rgba(255, 149, 0, 0.10));
}
.stats-station-list .station-ranking-item {
padding-left: calc(var(--sp-4) + 10px);
}
.stats-station-list .station-ranking-item::before {
left: 8px;
width: 4px;
}
.metric-value { font-size: 28px; }
.stats-hero-value { font-size: 48px; }
/* Desktop chip refinements */
.chip {
transition: background 0.2s var(--dt-ease),
color 0.2s var(--dt-ease),
transform 0.15s var(--dt-ease),
box-shadow 0.2s var(--dt-ease);
}
.chip:hover:not(.active) {
background: var(--color-separator);
}
.chip.active {
box-shadow: none;
}
/* Desktop chart container — matches card radius so the chart reads as a
hero card in the same family. Border carries the surface; shadow only
appears on hover for affordance. */
.chart-container {
border-radius: var(--r-lg);
padding: var(--sp-5) var(--sp-3) var(--sp-3);
transition: box-shadow 0.3s var(--dt-ease);
}
.chart-container:hover {
box-shadow: var(--shadow-1);
}
/* Stats card hover */
.stat-card {
border-radius: var(--r-lg);
padding: var(--sp-4) var(--sp-3);
transition: box-shadow 0.25s var(--dt-ease);
}
.stat-card:hover {
box-shadow: var(--shadow-1);
}
/* Stat row gets a touch more breathing room between cards on desktop. */
.stat-row {
gap: var(--sp-3);
}
/* Section header reads a little louder on desktop so the hierarchy
between header → cards is unmistakable. */
.section-header {
font-size: 12px;
padding: var(--sp-6) var(--sp-1) var(--sp-2);
letter-spacing: 0.7px;
}
.section-header:first-child,
.section > .section-header:first-child {
padding-top: var(--sp-3);
}
/* Section header sync-badge sits slightly higher to align with the
bolder header text. */
.section-header .sync-badge {
transform: translateY(-1px);
}
/* Ranking item hover */
.ranking-item {
transition: background 0.2s var(--dt-ease),
transform 0.15s var(--dt-ease);
border-radius: 0;
}
.ranking-item:hover {
background: var(--color-bg-secondary);
}
.ranking-item:active {
transform: scale(0.99);
}
/* Settings row hover */
.settings-row {
border-radius: 0;
transition: background 0.2s var(--dt-ease);
}
.settings-row:hover {
background: color-mix(in srgb, var(--color-text) 5%, transparent);
}
/* --- Map view: full width with side panel (panel on right) --- */
#view-map {
display: flex;
flex-direction: row;
overflow: hidden;
}
#view-map.active {
display: flex;
}
/* Remove the dark bar above the map on desktop */
#map-container::before {
display: none;
}
#map-container {
flex: 1;
height: 100%;
min-height: unset;
}
/* Map search bar refinement */
#map-search-wrapper {
top: 16px;
left: 16px;
max-width: 400px;
}
/* Desktop: the "Hier suchen" pill sits inline to the right of the search
box, same as mobile. Widen the wrapper only while the pill is visible so
the search input keeps its width. */
.map-search-here-row {
position: static;
transform: none;
width: auto;
margin-top: 0;
}
#map-search-wrapper:has(.map-search-here:not(.hidden)) {
max-width: 520px;
}
#map-search-box {
border-radius: 12px;
box-shadow: var(--dt-shadow-md);
background: var(--color-bg);
transition: box-shadow 0.25s var(--dt-ease);
height: 46px;
}
#map-search-box:focus-within {
box-shadow: var(--dt-shadow-lg), 0 0 0 3px rgba(0, 122, 255, 0.2);
}
#map-search-results {
border-radius: 12px;
box-shadow: var(--dt-shadow-lg);
background: var(--color-bg);
margin-top: 6px;
}
.map-search-result-item {
transition: background 0.15s ease;
}
.map-search-result-item:hover {
background: var(--color-bg-secondary);
}
/* Map FAB refinement */
.map-fab {
border-radius: 12px;
box-shadow: var(--dt-shadow-md);
background: var(--color-bg);
transition: box-shadow 0.25s var(--dt-ease),
transform 0.2s var(--dt-ease);
width: 46px;
height: 46px;
}
.map-fab:hover {
box-shadow: var(--dt-shadow-lg);
transform: scale(1.05);
}
.map-fab:active {
transform: scale(0.94);
opacity: 1;
}
/* --- Station panel (right sidebar on map) --- */
.desktop-station-panel {
display: flex;
flex-direction: column;
width: var(--panel-w);
flex-shrink: 0;
height: 100%;
overflow-y: auto;
border-left: 1px solid var(--color-separator);
background: var(--color-bg);
}
.desktop-station-panel .station-sort-bar {
padding: 16px 16px 8px;
position: sticky;
top: 0;
background: var(--color-bg);
z-index: 2;
backdrop-filter: blur(var(--dt-blur));
-webkit-backdrop-filter: blur(var(--dt-blur));
}
.desktop-station-panel .station-list {
margin: 0;
border-radius: 0;
flex: 1;
background: transparent;
overflow: visible;
}
/* Station list item hover on desktop */
.station-item {
transition: background 0.2s var(--dt-ease),
transform 0.15s var(--dt-ease);
cursor: pointer;
}
.station-item:hover {
background: var(--color-bg-secondary);
}
.station-item:active {
transform: scale(0.99);
}
/* Station item stagger animation */
.station-item.anim-in {
animation: dt-station-slide 0.35s var(--dt-ease) both;
}
@keyframes dt-station-slide {
from {
opacity: 0;
transform: translateX(16px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* --- Bottom sheet becomes right side panel --- */
.bottom-sheet {
left: var(--sidebar-w);
}
.bottom-sheet-backdrop {
background: rgba(0, 0, 0, 0.25);
transition: opacity 0.35s var(--dt-ease);
}
.bottom-sheet-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: auto;
width: var(--sheet-w);
max-width: var(--sheet-w);
max-height: 100%;
border-radius: 0;
border-left: 1px solid var(--color-separator);
box-shadow: var(--dt-shadow-sheet);
transform: translateX(100%);
transition: transform 0.45s var(--dt-ease);
padding-bottom: 0;
}
.bottom-sheet:not(.hidden) .bottom-sheet-content {
transform: translateX(0);
}
.bottom-sheet-content.expanded {
max-height: 100%;
border-radius: 0;
}
.bottom-sheet-handle-area {
display: none;
}
/* Sheet station header spacing for desktop (space for close button only) */
.sheet-station-header {
padding: 20px 20px 8px;
padding-right: 48px;
}
.sheet-station-name {
font-size: 20px;
}
.sheet-station-price {
font-size: 40px;
padding: 12px 20px;
}
/* Sheet chart container */
.sheet-chart-container {
height: 160px;
border-radius: 14px;
box-shadow: var(--dt-shadow-sm);
}
.bottom-sheet-content.expanded .sheet-chart-container {
height: 240px;
}
/* Sheet nav buttons */
.sheet-nav-btn {
border-radius: 12px;
transition: background 0.15s ease;
}
.sheet-nav-btn:hover {
background: var(--color-separator);
}
.sheet-nav-btn:active {
opacity: 0.7;
}
/* --- Desktop close button (Apple xmark.circle.fill style) --- */
.sheet-desktop-close {
position: absolute;
top: 14px;
right: 14px;
width: 28px;
height: 28px;
border-radius: 50%;
border: none;
background: var(--color-bg-secondary);
color: var(--color-hint);
font-size: 15px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
transition: background 0.2s ease,
color 0.2s ease,
opacity 0.2s ease;
opacity: 0.7;
line-height: 1;
}
.sheet-desktop-close:hover {
opacity: 1;
background: var(--color-separator);
color: var(--color-text);
}
.sheet-desktop-close:active {
opacity: 0.5;
}
/* The desktop close button overlays the top-right corner — give the fav
star enough right-side breathing room so they don't visually collide. */
.sheet-fav-btn {
margin-right: 36px;
}
/* --- Hide expand button on desktop (close button handles dismiss) --- */
.sheet-expand-btn {
display: none !important;
}
/* --- Hide pull-to-refresh on desktop --- */
#ptr-container {
display: none;
}
/* --- PWA popup centers on desktop --- */
.pwa-popup {
align-items: center;
justify-content: center;
}
.pwa-popup-sheet {
border-radius: 20px;
box-shadow: var(--dt-shadow-lg);
max-width: 440px;
}
/* --- Toast positioning (account for sidebar) --- */
.toast {
border-radius: 14px;
box-shadow: var(--dt-shadow-lg);
backdrop-filter: blur(var(--dt-blur));
-webkit-backdrop-filter: blur(var(--dt-blur));
}
.toast.visible {
left: calc(var(--sidebar-w) + 50%);
transform: translateX(-50%) translateX(calc(var(--sidebar-w) / -2));
}
/* --- About card refinements --- */
.about-card {
border-radius: 14px;
box-shadow: var(--dt-shadow-sm);
transition: box-shadow 0.25s var(--dt-ease);
}
.about-card:hover {
box-shadow: var(--dt-shadow-md);
}
.about-row {
transition: background 0.2s var(--dt-ease);
}
.about-row:hover {
background: rgba(127, 127, 127, 0.06);
}
/* Auth hero card */
.auth-hero-card {
border-radius: 20px;
box-shadow: var(--dt-shadow-md);
transition: box-shadow 0.3s var(--dt-ease);
}
.auth-hero-card:hover {
box-shadow: var(--dt-shadow-lg);
}
/* Section header subtle refinement */
.section-header {
padding-top: 20px;
font-size: 12px;
letter-spacing: 0.8px;
}
/* Location picker dropdown */
.location-picker {
border-radius: 10px;
transition: border-color 0.2s var(--dt-ease),
box-shadow 0.2s var(--dt-ease);
}
.location-picker:focus {
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
}
/* Scrollbar styling for panels */
.desktop-station-panel::-webkit-scrollbar,
.bottom-sheet-content::-webkit-scrollbar,
#view-history::-webkit-scrollbar,
#view-stats::-webkit-scrollbar,
#view-settings::-webkit-scrollbar {
width: 6px;
}
.desktop-station-panel::-webkit-scrollbar-track,
.bottom-sheet-content::-webkit-scrollbar-track,
#view-history::-webkit-scrollbar-track,
#view-stats::-webkit-scrollbar-track,
#view-settings::-webkit-scrollbar-track {
background: transparent;
}
.desktop-station-panel::-webkit-scrollbar-thumb,
.bottom-sheet-content::-webkit-scrollbar-thumb,
#view-history::-webkit-scrollbar-thumb,
#view-stats::-webkit-scrollbar-thumb,
#view-settings::-webkit-scrollbar-thumb {
background: var(--color-separator);
border-radius: 3px;
}
.desktop-station-panel::-webkit-scrollbar-thumb:hover,
.bottom-sheet-content::-webkit-scrollbar-thumb:hover,
#view-history::-webkit-scrollbar-thumb:hover,
#view-stats::-webkit-scrollbar-thumb:hover,
#view-settings::-webkit-scrollbar-thumb:hover {
background: var(--color-hint);
}
/* Firefox scrollbar */
.desktop-station-panel,
.bottom-sheet-content,
#view-history,
#view-stats,
#view-settings {
scrollbar-width: thin;
scrollbar-color: var(--color-separator) transparent;
}
/* --- Slider refinement --- */
.slider::-webkit-slider-thumb {
width: 22px;
height: 22px;
box-shadow: var(--dt-shadow-md);
transition: box-shadow 0.2s var(--dt-ease),
transform 0.15s var(--dt-ease);
}
.slider::-webkit-slider-thumb:hover {
box-shadow: var(--dt-shadow-lg);
transform: scale(1.12);
}
/* Toggle switch hover */
.toggle-switch {
transition: opacity 0.2s ease;
}
.toggle-switch:hover {
opacity: 0.85;
}
/* --- Button hover refinements (Apple-style: subtle opacity, no lifts) --- */
.card-action {
transition: opacity 0.15s ease;
}
.card-action:hover {
opacity: 0.85;
}
.card-action:active {
opacity: 0.65;
}
.primary-btn {
transition: opacity 0.15s ease;
}
.primary-btn:hover {
opacity: 0.85;
}
.secondary-btn {
transition: background 0.2s ease;
}
.secondary-btn:hover {
background: var(--color-separator);
}
/* Sort button hover */
.station-sort-btn {
transition: background 0.2s ease;
}
.station-sort-btn:hover {
background: var(--color-separator);
}
.station-sort-btn:active {
opacity: 0.7;
}
/* Map location banner refinement */
#map-location-banner {
border-radius: 12px;
box-shadow: var(--dt-shadow-md);
backdrop-filter: blur(var(--dt-blur));
-webkit-backdrop-filter: blur(var(--dt-blur));
}
}
/* ── History locations (Standorte mit Verlaufsdaten) ───────────────── */
.hl-summary-top {
display: flex;
align-items: center;
gap: 14px;
width: 100%;
padding: 14px 16px;
background: transparent;
border: none;
text-align: left;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: background 0.15s;
}
.hl-summary-top:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.hl-total-num {
font-size: 26px;
font-weight: 700;
line-height: 1;
color: var(--color-text);
font-variant-numeric: tabular-nums;
}
.hl-total-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.4px;
text-transform: uppercase;
color: var(--color-hint);
}
.hl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.hl-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 9px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-text) 8%, transparent);
font-size: 12px;
color: var(--color-text);
}
.hl-chip b { font-weight: 700; }
.hl-chevron { transition: transform 0.2s ease; }
.hl-group-header {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 16px;
border-top: 1px solid var(--color-separator);
background: color-mix(in srgb, var(--color-text) 4%, transparent);
}
.hl-row {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
border-top: 1px solid var(--color-separator);
transition: background 0.15s;
}
.hl-row:hover { background: color-mix(in srgb, var(--color-text) 3%, transparent); }
.hl-row-name {
font-size: 14px;
font-weight: 500;
color: var(--color-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hl-row-sub { font-size: 11px; color: var(--color-hint); margin-top: 2px; }
.hl-pin-btn {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 50%;
border: none;
background: rgba(0, 122, 255, 0.12);
color: #007aff;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: background 0.15s, transform 0.15s;
}
.hl-pin-btn:hover { background: rgba(0, 122, 255, 0.22); transform: scale(1.06); }
.hl-pin-btn:active { transform: scale(0.94); }
/* "Standort anfragen" reads as the primary action of the locations section. */
.request-location-btn { color: var(--color-accent); font-weight: 600; }
.request-location-btn svg { color: var(--color-accent); }