/* === 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