diff --git a/README.md b/README.md index efd683e..e2ac172 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ npm start - Главный экран целиком отдан рейсам: ближайший крупно и следующие 2–6 рейсов. Строки рассчитываются по фактической доступной высоте, нижняя панель занимает отдельное место. Если все строки не помещаются с читаемым размером текста, список сменяется страницами каждые 7 секунд с явным счётчиком. На 879×593 и 390×667 шесть рейсов показаны страницами по три; на 855×723 четыре помещаются одновременно. В узкой компоновке время отправления находится под названием и не пересекается с ожиданием. - В шапке — название и направление конкретной площадки из API, часы и дата в `Europe/Minsk`. Поле «основная» не выдаётся за направление; если направления нет, оно не придумывается. Город, подпись «Остановка», «RideBus API» и «Все направления» убраны из шапки. - Информационный экран: маршруты, погода, QR-код и предупреждения. Часы и ближайший рейс с цветным номером остаются видимыми. В вертикальной и короткой компоновке показывается одна карточка; при следующих посещениях информационного экрана карточки сменяются. Их также можно выбрать кнопками «Маршруты», «Погода», «RideBus». -- В широком блоке маршрутов — несколько слегка наклонённых дорожек с плавным встречным движением, номерами, названиями, типом транспорта и заметным временем следующего отправления. Зазоры учитывают наклон на краях. В узком блоке, включая компоновку с тремя карточками, — вертикальные карточки, сменяющиеся каждые 6,5 секунды с прокруткой названий и чисел. Пауза останавливает оба варианта. Технический статус полного кеша пассажирам не показывается. -- Уведомления об отменах, объездах, задержках и другие сообщения оформлены с крупными иконками, цветными номерами маршрутов, сроками действия и ненавязчивой графикой. Исходные заголовки, тексты и детали отмен сохраняются. +- Блок маршрутов показывает только номер и название. На широком экране список занимает 2–3 колонки, в узкой карточке — одну. Вместимость определяется доступной высотой; остальные маршруты сменяются страницами каждые 6,5 секунды с плавным переходом названий и чисел. Пауза останавливает смену страниц. Тип транспорта, повторные времена, наклонённые дорожки, фоновые рисунки и технический статус кеша убраны. +- Уведомления об отменах, объездах, задержках и другие сообщения оформлены простой карточкой с иконкой, номерами маршрутов и сроком действия. Исходные заголовки, тексты и детали отмен сохраняются. Статус предупреждений отображается только при их недоступности. - Цикл 25 секунд / 8 секунд, настраиваемый. До ближайшего рейса ≤2 минут главный экран удерживается; можно отключить в настройках. Отмены и объезды исключают рекламную сцену из цикла. - Опциональная рекламная сцена с текстом и длительностью; по умолчанию выключена, помечена как предпросмотр. Загрузка медиа и кампании в MVP не входят. - Настройки остановки, маршрута, числа строк, интервалов и деморежима сохраняются на устройстве. Новая остановка загружает свои данные и свой кеш. @@ -46,9 +46,9 @@ npm start При сбое появляется пометка сохранённых данных. Истёкшие рейсы удаляются. Если сохранённые рейсы закончились, табло честно сообщает об этом и продолжает переподключение. Предупреждения фильтруются по городу, маршрутам, срокам действия; сохраняются полный текст и детали отмены. Полный кеш расписания не подтверждает актуальность предупреждений — без свежего ответа API это явно указано. -QR-код создаётся локально, без стороннего сервиса генерации, и открывает `https://pwa.ridebus.by/?city=1&stop=1` для текущей остановки (параметры меняются вместе с площадкой). Адрес PWA можно переопределить серверной переменной `BOARD_PWA_URL`. Карточка оформлена как приглашение забрать расписание: большой неподвижный QR на акцентной панели с плавно движущейся линией маршрута, название остановки, шаги открытия и заметная ссылка. В узкой и короткой карточке используется упрощённая компоновка, размер кода рассчитывается по свободному месту. Декоративное движение выключено при `prefers-reduced-motion`. QR использует скруглённые чёрные модули, белый фон и свободное поле шириной 4 модуля; рамка и внешняя подсветка следуют акценту темы. Декодирование нескольких ссылок и QR на снимках страницы проверено автоматически. +QR-код создаётся локально, без стороннего сервиса генерации, и открывает `https://pwa.ridebus.by/?city=1&stop=1` для текущей остановки (параметры меняются вместе с площадкой). Адрес PWA можно переопределить серверной переменной `BOARD_PWA_URL`. В карточке — большой неподвижный QR, одна инструкция и ссылка. Размер кода рассчитывается по свободному месту. QR использует скруглённые чёрные модули, белый фон и свободное поле шириной 4 модуля. Декодирование нескольких ссылок и QR на снимках страницы проверено автоматически. -Погода запрашивается независимо через `/api/weather` у [Open-Meteo](https://open-meteo.com/en/docs) по публичным координатам остановки из API. В шапке — температура и состояние; на информационном экране — ощущаемая температура, ветер в м/с и три следующих часа. Процент осадков — максимальная почасовая вероятность в этих трёх часах. Это модельные погодные данные. Обновление раз в 10 минут; сохранённая погода помечается и показывается не дольше часа, затем скрывается. При отсутствии координат или сбое погоды транспорт продолжает работать. Данные сохраняются по городу и остановке; RideBus-ключ не передаётся поставщику погоды. Указаны источник и CC BY 4.0. +Погода запрашивается независимо через `/api/weather` у [Open-Meteo](https://open-meteo.com/en/docs) по публичным координатам остановки из API. В шапке — температура и состояние; на информационном экране — текущая температура, состояние и прогноз температуры с крупными иконками на три следующих часа. Это модельные погодные данные. Обновление раз в 10 минут; сохранённая погода помечается и показывается не дольше часа, затем скрывается. При отсутствии координат или сбое погоды транспорт продолжает работать. Данные сохраняются по городу и остановке; RideBus-ключ не передаётся поставщику погоды. Указаны источник и CC BY 4.0. Бесплатный Open-Meteo подходит для прототипа; [условия сервиса](https://open-meteo.com/en/terms) требуют коммерческую подписку для приложения с рекламой. Сервер уже поддерживает `OPEN_METEO_API_KEY` и автоматически выбирает коммерческий endpoint, ключ остаётся на сервере. Панель оператора планируется отдельно в `polotsk-transit-api`. diff --git a/artifacts/info-simple-alerts.jpg b/artifacts/info-simple-alerts.jpg new file mode 100644 index 0000000..530a601 Binary files /dev/null and b/artifacts/info-simple-alerts.jpg differ diff --git a/artifacts/info-simple-light.jpg b/artifacts/info-simple-light.jpg new file mode 100644 index 0000000..c9b32f8 Binary files /dev/null and b/artifacts/info-simple-light.jpg differ diff --git a/artifacts/info-simple-qr-390x667.jpg b/artifacts/info-simple-qr-390x667.jpg new file mode 100644 index 0000000..9aa9ea9 Binary files /dev/null and b/artifacts/info-simple-qr-390x667.jpg differ diff --git a/artifacts/info-simple-qr-879x593.jpg b/artifacts/info-simple-qr-879x593.jpg new file mode 100644 index 0000000..df934ea Binary files /dev/null and b/artifacts/info-simple-qr-879x593.jpg differ diff --git a/artifacts/info-simple-routes-wide.jpg b/artifacts/info-simple-routes-wide.jpg new file mode 100644 index 0000000..0086b19 Binary files /dev/null and b/artifacts/info-simple-routes-wide.jpg differ diff --git a/artifacts/info-simple-three-cards.jpg b/artifacts/info-simple-three-cards.jpg new file mode 100644 index 0000000..90afe83 Binary files /dev/null and b/artifacts/info-simple-three-cards.jpg differ diff --git a/artifacts/info-simple-weather-390x667.jpg b/artifacts/info-simple-weather-390x667.jpg new file mode 100644 index 0000000..8db035b Binary files /dev/null and b/artifacts/info-simple-weather-390x667.jpg differ diff --git a/artifacts/info-simple-weather-879x593.jpg b/artifacts/info-simple-weather-879x593.jpg new file mode 100644 index 0000000..0536cbf Binary files /dev/null and b/artifacts/info-simple-weather-879x593.jpg differ diff --git a/artifacts/verification-info-simple.md b/artifacts/verification-info-simple.md new file mode 100644 index 0000000..cf7389d --- /dev/null +++ b/artifacts/verification-info-simple.md @@ -0,0 +1,21 @@ +# Упрощение информационного экрана — 9 октября 2026 + +Оставлены номер и название маршрута; текущая температура, состояние и прогноз на три часа; QR-код, одна инструкция и ссылка. Удалены дорожки с непрерывным движением, фоновые рисунки, водяные знаки, тип транспорта и повторные времена в каталоге маршрутов, пошаговая инструкция QR и дополнительные показатели погоды. Вместо «Пока вы ждёте» используется «Информация». Строка предупреждений появляется при недоступности данных, без постоянной подписи об отсутствии активных уведомлений. + +Маршруты используют адаптивную сетку из 1–3 колонок, сбалансированные страницы и прежнюю анимацию смены чисел и названий. Уведомления оформлены проще, но исходные сообщения, номера маршрутов и сроки сохранены. Сервер, кеш расписаний, правила актуальности погоды и предупреждений не изменены. + +Проверено: + +- `npm run build`: TypeScript и Vite проходят. `npm test`: 48/48. `git diff --check` проходит. +- В браузере загружена итоговая production-сборка `/assets/index-CvnZ7b-g.js`. +- 1280×900, тёмная и светлая темы: все три карточки видимы одновременно. У каждой clientHeight и scrollHeight равны 535 px; вертикальной обрезки нет. Дополнительные подписи и декоративные узлы отсутствуют в DOM. +- 879×593: одиночные карточки переключаются кнопками «Маршруты / Погода / RideBus». Погода и QR имеют clientHeight=scrollHeight=348 px. Основная погодная иконка ≈81 px, прогнозные ≈51 px. QR ≈262 px, ссылка указывает на текущую остановку. +- 390×667: QR ≈196 px, карточка clientHeight=scrollHeight=354 px; подпись и ссылка полностью видны. Погода использует компактные строки «иконка + температура», иконки прогноза 40 px. Прогноз заканчивается на y≈474,3, блок источника начинается на y=500,5; пересечения нет. Карточка маршрутов имеет ту же высоту без обрезки. +- 1500×600: каталог маршрутов занимает три колонки по 440 px. Карточка clientHeight=scrollHeight=344 px. После включения смены экранов номера, названия и счётчик страниц обновились с страницы 3/4 на 1/4; после паузы счётчик оставался 1/4. Распределение страниц не оставляет последнюю страницу с одним маршрутом. +- QR на снимках тёмной компоновки с тремя карточками, вертикального экрана и светлой темы декодирован через jsQR в `https://pwa.ridebus.by/?city=1&stop=1`. +- Уведомления проверены на существующей отдельной визуальной странице с тестовыми отменой, объездом и переносом посадки. Тексты, даты и номера сохранены; декоративные линии и водяные знаки отсутствуют. Это проверка примеров, а не активных сообщений API. Временная сборка страницы удалена повторной production-сборкой, временная вкладка закрыта. +- Ошибок и предупреждений в консоли не обнаружено. После проверки восстановлены тёмная тема, остановка 1, четыре следующих рейса, интервалы 25/8 и удержание ближайшего рейса. Пауза выключена, viewport сброшен. + +Снимки: `info-simple-three-cards.jpg`, `info-simple-light.jpg`, `info-simple-routes-wide.jpg`, `info-simple-weather-879x593.jpg`, `info-simple-weather-390x667.jpg`, `info-simple-qr-879x593.jpg`, `info-simple-qr-390x667.jpg`, `info-simple-alerts.jpg`. + +Проверка выполнена в браузере. Физическая Mi Box в этой проверке не использовалась. diff --git a/src/AlertCard.tsx b/src/AlertCard.tsx index c35e383..2e890a8 100644 --- a/src/AlertCard.tsx +++ b/src/AlertCard.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, Clock, Close, Info, Reset } from './Icons'; +import { Clock, Close, Info, Reset } from './Icons'; import { RouteBadge } from './RouteBadge'; import type { Alert } from './types'; @@ -12,13 +12,11 @@ export function AlertCard({ alert }: { alert: Alert }) { const periodAt = alert.endAt || alert.startAt; const period = periodAt && Number.isFinite(Date.parse(periodAt)) ? periodFormat.format(Date.parse(periodAt)) : null; return
- +
{label}{period && {alert.endAt ? 'До' : 'С'} {period}}
{!!alert.routeNumbers.length &&
{alert.routeNumbers.map(number => )}
}

{alert.title}

{alert.message}

- {!alert.routeNumbers.length &&
На этой остановке
}
-
; } diff --git a/src/App.tsx b/src/App.tsx index 09c2ed9..375a31b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -82,7 +82,7 @@ function DeparturesScreen({ data, departures, now, alerts, loading, rowCount, fr ; } -function InfoScreen({ data, routeDepartures, alerts, fresh, seconds, paused, page, onPage, config, weather, now }: { data: BoardData | null; routeDepartures: Departure[]; alerts: Alert[]; fresh: boolean; seconds: number; paused: boolean; page: number; onPage: (page: number) => void; +function InfoScreen({ data, alerts, fresh, seconds, paused, page, onPage, config, weather, now }: { data: BoardData | null; alerts: Alert[]; fresh: boolean; seconds: number; paused: boolean; page: number; onPage: (page: number) => void; config: Config; weather: ReturnType; now: number }) { const panel = useRef(null); const grid = useRef(null); @@ -104,14 +104,14 @@ function InfoScreen({ data, routeDepartures, alerts, fresh, seconds, paused, pag return () => { clearTimeout(start); clearInterval(timer); }; }, [seconds, paused, page]); return
-

{alerts.length ? 'Изменения движения' : 'Пока вы ждёте.'}

+

{alerts.length ? 'Изменения движения' : 'Информация'}

{!alerts.length && }
{alerts.length ?
{alerts.map(alert => )}
:
-

Маршруты остановки

+

Маршруты

} - {!alerts.length &&
{data?.alertsAvailable === false || !fresh ? 'Актуальные предупреждения сейчас недоступны' : 'Нет активных предупреждений для этой остановки'}
} + {!alerts.length && (data?.alertsAvailable === false || !fresh) &&
Предупреждения сейчас недоступны
}
; } @@ -210,7 +210,7 @@ export default function App() { {!fresh && !settings.demo &&
{data ? `Нет свежих данных. Сохранённое расписание · обновлено в ${updateLabel}` : loading ? 'Подключаемся к RideBus…' : 'Не удалось загрузить расписание. Проверьте подключение.'}
}
{scene === 'departures' && } - {scene === 'info' && Date.parse(item.scheduledAt) + 60000 > now) : upcomingDepartures(data, now)} alerts={alerts} fresh={fresh} seconds={seconds} paused={paused || settingsOpen} page={infoPage} onPage={setInfoPage} config={config} weather={weather} now={now} />} + {scene === 'info' && } {scene === 'ad' &&
РЕКЛАМА · ПРЕДПРОСМОТР

{settings.adTitle}

{settings.adText}

Время для нового.
}
{scene !== 'departures' &&
БЛИЖАЙШИЙ РЕЙС{departures[0] ? <>
{departures[0].destination}
: Нет доступных рейсов}
} diff --git a/src/PassengerQr.tsx b/src/PassengerQr.tsx index 98b3be5..e3ebba9 100644 --- a/src/PassengerQr.tsx +++ b/src/PassengerQr.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { ArrowUpRight, Camera, Smartphone } from './Icons'; +import { ArrowUpRight } from './Icons'; import type { Config, Stop } from './types'; import { createPassengerQr, passengerLink } from './lib/passenger.mjs'; @@ -8,14 +8,11 @@ export function PassengerQr({ config, stop }: { config: Config; stop: Stop | und const qr = useMemo(() => createPassengerQr(url), [url]); const stopName = stop?.name || config.stopName; return <>

Расписание в телефоне

- {qr.rectangles.map((rectangle, index) => )} -
-
С ВАМИ ПО ПУТИ

{stopName}

Заберите расписание этой остановки с собой.

-
  1. 01Наведите камеруна QR-код слева
  2. -
  3. 02Откройте RideBusв браузере телефона
+
+

Наведите камеру на QR-код

{new URL(url).host}
; } diff --git a/src/RouteCloud.tsx b/src/RouteCloud.tsx index 6f6af06..fec5b66 100644 --- a/src/RouteCloud.tsx +++ b/src/RouteCloud.tsx @@ -1,36 +1,22 @@ -import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import type { CSSProperties } from 'react'; -import type { Departure, Route } from './types'; -import { BusFront } from './Icons'; +import type { Route } from './types'; import { RouteBadge } from './RouteBadge'; import { FittedDestination } from './FittedDestination'; import { NumericText } from './NumericText'; -import { localDate } from './lib/timetable.mjs'; -type Props = { routes: Route[]; departures: Departure[]; now: number; paused: boolean; wide: boolean }; -export function RouteCloud({ routes, departures, now, paused, wide }: Props) { +export function RouteCloud({ routes, paused }: { routes: Route[]; paused: boolean }) { const element = useRef(null); - const [epoch] = useState(() => Date.now() / 1000); const [page, setPage] = useState(() => Math.floor(Date.now() / 6500)); const [area, setArea] = useState({ width: 300, height: 300 }); - const carousel = wide && area.width >= 650; - // Opposite inclines need extra clearance at the edges of a wide display. - const laneGap = Math.max(36, Math.ceil(area.width * .016) + 24); - const edge = Math.ceil(area.width * .008) + 8; - const lanes = Math.min(3, Math.max(2, Math.floor((area.height + laneGap) / (155 + laneGap))), Math.max(1, routes.length)); - const cardHeight = Math.max(1, (area.height - (lanes - 1) * laneGap) / lanes); - const cardWidth = cardHeight < 150 ? 420 : Math.max(300, Math.min(380, area.width * .32)); - const capacity = Math.min(Math.max(1, routes.length), Math.max(1, Math.floor(area.height / 128))); + const columns = area.width >= 1000 ? 3 : area.width >= 600 ? 2 : 1; + const capacity = columns * Math.max(1, Math.floor((area.height - 28) / 96)); const pages = Math.max(1, Math.ceil(routes.length / capacity)); const pageIndex = page % pages; const baseSize = Math.floor(routes.length / pages), extra = routes.length % pages; - const pageSize = Math.max(1, baseSize + (pageIndex < extra ? 1 : 0)); + const pageSize = baseSize + (pageIndex < extra ? 1 : 0); const pageStart = pageIndex * baseSize + Math.min(pageIndex, extra); - const nextByRoute = useMemo(() => { - const result = new Map(); - for (const departure of departures) if (!result.has(departure.routeId)) result.set(departure.routeId, departure); - return result; - }, [departures]); + const rows = Math.max(1, Math.ceil(pageSize / columns)); useLayoutEffect(() => { if (!element.current) return; const observer = new ResizeObserver(entries => { @@ -42,35 +28,13 @@ export function RouteCloud({ routes, departures, now, paused, wide }: Props) { return () => observer.disconnect(); }, []); useEffect(() => { - if (carousel || paused || pages < 2) return; + if (paused || pages < 2) return; const timer = setInterval(() => setPage(value => value + 1), 6500); return () => clearInterval(timer); - }, [carousel, paused, pages]); - const tile = (route: Route, key: string) => { - const next = nextByRoute.get(route.id); - return
- -
-
{next ? <>{localDate(Date.parse(next.scheduledAt)) > localDate(now) && завтра} : Расписание уточняется} - {route.type === 'minibus' ? 'Маршрутка' : 'Автобус'} -
-
-
; - }; - return
- {carousel ? <> -
    {routes.map(route =>
  • №{route.number} · {route.name}
  • )}
- {Array.from({ length: lanes }, (_, lane) => { - const items = routes.filter((_, index) => index % lanes === lane); - const group = items.length ? Array.from({ length: Math.max(1, Math.ceil(4 / items.length)) }, () => items).flat() : []; - const duration = Math.max(12, group.length * (cardWidth + 16) / 24); - return ; - })} : <>
{routes.slice(pageStart, pageStart + pageSize).map((route, index) => tile(route, `position-${index}`))}
- {pages > 1 &&
Через эту остановку
} - } + }, [paused, pages]); + return
+ {routes.length ? <>
{routes.slice(pageStart, pageStart + pageSize).map((route, index) =>
+
+
)}
{pages > 1 &&
} :

Маршруты недоступны

}
; } diff --git a/src/WeatherBlock.tsx b/src/WeatherBlock.tsx index 10d3f68..b2a0379 100644 --- a/src/WeatherBlock.tsx +++ b/src/WeatherBlock.tsx @@ -1,4 +1,4 @@ -import { Cloud, Moon, Rain, Snow, Storm, Sun, Wind } from './Icons'; +import { Cloud, Moon, Rain, Snow, Storm, Sun } from './Icons'; import { NumericText } from './NumericText'; import { weatherAvailable, weatherCondition } from './lib/weather.mjs'; import type { Weather } from './types'; @@ -19,12 +19,10 @@ export function WeatherSummary({ data, now, failed }: { data: Weather | null; no } export function WeatherCard({ data, now, loading, failed, demo }: { data: Weather | null; now: number; loading: boolean; failed: boolean; demo: boolean }) { const available = weatherAvailable(data, now); - if (!available || !data) return <>

Погода

{demo ? 'Погода доступна с реальными данными остановки.' : loading ? 'Получаем прогноз…' : 'Прогноз сейчас недоступен.'}

{data ? 'Сохранённый прогноз устарел' : 'Погода обновится при подключении'}
; + if (!available || !data) return <>

Погода

{demo ? 'Прогноз доступен для реальной остановки' : loading ? 'Получаем прогноз…' : 'Прогноз недоступен'}

; const hours = data.hourly.filter(hour => Date.parse(hour.at) > now).slice(0, 3); - const probability = hours.length ? Math.max(...hours.map(hour => hour.precipitationProbability)) : null; - return <>

Погода

СЕЙЧАС
-

{weatherCondition(data.code, data.isDay).label}

Ощущается как м/с
- {!!hours.length &&
{hours.map(hour =>
%осадки
)}
} - {probability !== null &&
Осадки в ближайшие 3 часа: %
}
-
{failed ? 'Сохранено' : 'Обновлено'} в {hourFormat.format(Date.parse(data.updatedAt))}Open-Meteo · CC BY 4.0
; + return <>

Погода

+

{weatherCondition(data.code, data.isDay).label}

+ {!!hours.length &&
{hours.map(hour =>
)}
}
+
{failed && Сохранённый прогноз}Open-Meteo · CC BY 4.0
; } diff --git a/src/styles.css b/src/styles.css index c60f687..15c8913 100644 --- a/src/styles.css +++ b/src/styles.css @@ -95,122 +95,66 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .info-card p { color: var(--secondary); font-size: clamp(18px, 1.75vw, 32px); line-height: 1.4; margin: 0 0 16px; } .info-caption { display: flex; align-items: center; gap: 9px; font-size: clamp(14px, 1.3vw, 23px); color: var(--secondary); margin-top: auto; line-height: 1.4; } .route-cloud { flex: 1 1 0; min-height: 0; min-width: 0; display: flex; flex-direction: column; overflow: hidden; } -.route-cloud[data-carousel='true'] { justify-content: center; gap: var(--lane-gap); padding: var(--lane-edge) 0; background-image: radial-gradient(color-mix(in srgb, var(--accent) 18%, transparent) 1px, transparent 1px); background-size: 26px 26px; mask-image: linear-gradient(to right, transparent, #000 4%, #000 96%, transparent); } -.route-lane { height: var(--lane-height, 180px); flex-shrink: 0; width: 100%; transform: rotate(var(--lane-angle, -.9deg)); } -.route-track { display: flex; width: max-content; animation: routes-scroll 60s linear infinite; will-change: transform; } -.route-group { display: flex; gap: 16px; padding-right: 16px; } -.route-tile { position: relative; overflow: hidden; width: var(--route-card-width, 300px); height: var(--lane-height, 180px); padding: 14px 18px; display: flex; flex-direction: column; justify-content: center; flex-shrink: 0; border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--line)); border-radius: 22px; background: color-mix(in srgb, var(--accent) 8%, var(--background)); } -.route-cloud .route-badge { --row-height: 100px; width: 105px; height: clamp(38px, calc(var(--lane-height, 180px) * .30), 72px); font-size: clamp(30px, calc(var(--lane-height, 180px) * .28), 54px); border-radius: 16px; padding: 0 10px; margin-bottom: 12px; } -.route-card-name { width: 100%; flex: 1 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; justify-content: center; } -.route-tile .destination-heading { width: 100%; font-size: clamp(17px, calc(var(--lane-height, 180px) * .14), 26px); line-height: 1.12; margin: 0; font-weight: 550; letter-spacing: -.025em; flex-shrink: 0; overflow-wrap: anywhere; } -.route-card-next { display: flex; align-items: center; flex-wrap: wrap; gap: 5px 8px; margin-top: 10px; font-size: 12px; color: var(--secondary); flex-shrink: 0; } -.route-card-next time { display: inline-flex; color: var(--accent); font-size: clamp(21px, calc(var(--lane-height, 180px) * .13), 28px); font-weight: 650; } -.route-kind { font-size: 11px; color: var(--muted); margin-left: auto; } -.route-cloud[data-carousel='true'][data-horizontal='true'] .route-tile { flex-direction: row; align-items: center; gap: 14px; padding: 10px 14px; } -.route-cloud[data-carousel='true'][data-horizontal='true'] .route-badge { width: 72px; height: min(56px, calc(var(--lane-height) - 20px)); font-size: 36px; padding: 0 8px; margin-bottom: 0; } -.route-cloud[data-carousel='true'][data-horizontal='true'] .route-card-name { height: 100%; } -.route-cloud[data-carousel='true'][data-horizontal='true'] .route-kind { display: none; } -.route-cloud[data-carousel='true'][data-horizontal='true'] .route-card-next { margin-top: 6px; } -.route-list { flex: 1 1 0; min-height: 0; display: grid; grid-template-rows: repeat(var(--route-rows), minmax(0, 1fr)); gap: 12px; } -.route-list .route-tile { width: 100%; height: 100%; flex-direction: row; align-items: center; gap: 14px; padding: 14px; border-radius: 18px; } -.route-list .route-badge { width: 70px; height: 62px; font-size: 35px; border-radius: 16px; margin: 0; } -.route-list .route-card-name { height: 100%; } -.route-list .destination-heading { font-size: clamp(18px, 5cqw, 25px); } -.route-list .route-card-next time { font-size: clamp(23px, 6cqw, 30px); } -.route-list-pages { display: flex; justify-content: space-between; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; padding-top: 12px; } -.route-list-pages > .numeric-text { color: var(--accent); } -.route-watermark { position: absolute; right: -18px; bottom: -22px; color: var(--accent); opacity: .06; transform: rotate(-12deg); } -@keyframes routes-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } } -.weather-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; min-height: 0; } -.weather-current { display: flex; align-items: center; gap: 18px; color: var(--accent); } -.weather-current strong { font-size: clamp(60px, 5.4vw, 100px); font-weight: 550; line-height: 1; } -.weather-info .weather-description { margin: 12px 0; font-size: clamp(20px, 1.7vw, 32px); line-height: 1.2; } -.weather-details { display: flex; flex-wrap: wrap; gap: 8px 18px; color: var(--secondary); font-size: clamp(15px, 1.15vw, 23px); } -.weather-details > span { display: flex; align-items: center; gap: 6px; } -.weather-hours { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; border-top: 1px solid var(--line); padding-top: 14px; } -.weather-hours > div { display: flex; flex-direction: column; align-items: center; gap: 10px; } -.weather-hours time { color: var(--secondary); font-size: clamp(14px, 1vw, 22px); } -.weather-hours svg { color: var(--accent); width: 40px !important; height: 40px !important; } -.weather-hours strong { font-size: clamp(22px, 1.8vw, 34px); font-weight: 550; } -.precipitation-chance { font-size: clamp(14px, 1vw, 21px); color: var(--secondary); margin-top: 12px; line-height: 1.35; } -.weather-attribution { margin-top: auto; padding-top: 16px; display: flex; flex-wrap: wrap; gap: 5px 12px; font-size: clamp(12px, .8vw, 17px); color: var(--muted); } +.route-list { flex: 1 1 0; min-height: 0; display: grid; grid-template-columns: repeat(var(--route-columns), minmax(0, 1fr)); grid-template-rows: repeat(var(--route-rows), minmax(0, 1fr)); gap: 8px 24px; } +.route-tile { min-width: 0; min-height: 0; display: flex; align-items: center; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--line); } +.route-list .route-badge { width: 72px; height: 60px; font-size: 35px; border-radius: 16px; margin: 0; padding: 0 8px; } +.route-card-name { flex: 1 1 0; min-width: 0; height: 100%; display: flex; flex-direction: column; justify-content: center; } +.route-list .destination-heading { width: 100%; font-size: clamp(22px, 3cqw, 30px); line-height: 1.2; margin: 0; font-weight: 550; letter-spacing: -.025em; overflow-wrap: anywhere; } +.route-list-pages { display: flex; justify-content: flex-end; color: var(--muted); font-size: 16px; padding-top: 10px; } +.weather-info { container-name: weather-card; } +.weather-body { flex: 1 1 0; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; align-items: center; justify-items: center; gap: 24px; } +.weather-now { min-width: 0; display: flex; flex-direction: column; align-items: center; } +.weather-current { display: flex; align-items: center; gap: .18em; color: var(--accent); font-size: clamp(64px, min(22cqw, 32cqh), 144px); } +.weather-current strong { font-size: 1em; font-weight: 550; line-height: 1; } +.weather-current svg { width: .8em !important; height: .8em !important; } +.weather-info .weather-description { margin: 12px 0 0; font-size: clamp(22px, 3cqw, 34px); line-height: 1.2; text-align: center; } +.weather-hours { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; border-top: 1px solid var(--line); padding-top: 20px; } +.weather-hours > div { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 10px; } +.weather-hours time { color: var(--secondary); font-size: clamp(16px, 3cqw, 22px); } +.weather-hours svg { color: var(--accent); width: clamp(40px, min(12cqw, 16cqh), 64px) !important; height: clamp(40px, min(12cqw, 16cqh), 64px) !important; } +.weather-hours strong { font-size: clamp(24px, 6cqw, 40px); font-weight: 550; } +.weather-attribution { flex-shrink: 0; padding-top: 16px; display: flex; flex-wrap: wrap; justify-content: center; gap: 5px 12px; font-size: 12px; color: var(--muted); } .weather-attribution a { color: inherit; text-underline-offset: 3px; } -.weather-now-label, .weather-hour-rain { display: none; } -.qr-info { text-align: left; container-name: qr-card; } -.qr-content { flex: 1 1 0; min-height: 0; width: 100%; display: grid; grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr); gap: clamp(16px, 2cqw, 36px); } -.qr-stage { position: relative; isolation: isolate; min-width: 0; min-height: 0; display: grid; place-items: center; padding: clamp(16px, 2cqw, 32px); border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--line)); border-radius: 24px; background: color-mix(in srgb, var(--accent) 8%, var(--background)); overflow: hidden; container-type: size; } -.qr-route-backdrop { position: absolute; inset: 0; overflow: hidden; z-index: -1; } -.qr-route-art { position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%; color: var(--accent); opacity: .22; transform: rotate(-12deg) scale(1.3); } -.qr-route-art path { fill: none; stroke: currentColor; stroke-width: 3; stroke-dasharray: 3 12; stroke-linecap: round; animation: qr-route-flow 12s linear infinite; } -.qr-route-art circle { fill: var(--background); stroke: currentColor; stroke-width: 3; } -.qr-stage-corner { position: absolute; right: 14px; top: 14px; color: var(--accent); } -.qr-paper { display: block; position: relative; z-index: 1; width: min(100cqw, 100cqh, 440px); aspect-ratio: 1; flex-shrink: 0; padding: 8px; border: 1px solid var(--outline); border-radius: 24px; background: #fff; box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent); } +.weather-info .weather-empty { margin-block: auto; text-align: center; } +@container weather-card (min-width: 600px) { + .weather-body[data-forecast='true'] { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 32px; } + .weather-hours { border: 0; padding: 0; } +} +@container weather-card (max-height: 300px) { + .weather-body { gap: 12px; } + .weather-current { font-size: clamp(44px, min(18cqw, 20cqh), 84px); } + .weather-info .weather-description { font-size: 20px; margin-top: 8px; } + .weather-hours { gap: 8px; padding-top: 12px; } + .weather-hours > div { display: grid; grid-template-columns: auto auto; align-items: center; justify-content: center; gap: 6px; } + .weather-hours time { grid-column: 1 / -1; text-align: center; } + .weather-hours svg { width: 40px !important; height: 40px !important; } + .weather-hours strong { font-size: 24px; } + .weather-attribution { padding-top: 10px; } +} +@container weather-card (max-width: 599px) and (max-height: 380px) { + .weather-body { gap: 16px; } + .weather-hours { padding-top: 14px; gap: 8px; } + .weather-hours > div { display: grid; grid-template-columns: auto auto; align-items: center; justify-content: center; gap: 6px; } + .weather-hours time { grid-column: 1 / -1; text-align: center; } + .weather-hours svg { width: 40px !important; height: 40px !important; } + .weather-hours strong { font-size: 24px; } +} +.qr-info { container-name: qr-card; } +.qr-content { flex: 1 1 0; min-height: 0; min-width: 0; display: grid; grid-template-rows: minmax(0, 1fr) auto; gap: 16px; } +.qr-stage { min-width: 0; min-height: 0; display: grid; place-items: center; padding: 4px; container-type: size; } +.qr-paper { display: block; width: min(100cqw, 100cqh, 440px); aspect-ratio: 1; padding: 6px; border-radius: 20px; background: #fff; border: 1px solid var(--outline); } .passenger-qr { width: 100%; height: 100%; aspect-ratio: 1; } -.qr-description { position: relative; min-width: 0; min-height: 0; display: flex; flex-direction: column; justify-content: space-between; gap: 16px; padding: clamp(8px, 1cqw, 18px) 0; } -.qr-eyebrow { display: block; color: var(--accent); font-size: clamp(12px, 1.4cqw, 20px); letter-spacing: .12em; margin-bottom: 12px; } -.qr-stop { margin: 0; font-size: clamp(26px, 4.3cqw, 70px); line-height: 1.08; letter-spacing: -.04em; font-weight: 600; overflow-wrap: anywhere; } -.qr-info .qr-summary { font-size: clamp(18px, 2.2cqw, 32px); line-height: 1.3; margin: 14px 0 0; } -.qr-steps { margin: 0; padding: 0; display: grid; gap: clamp(12px, 1.5cqw, 22px); list-style: none; } -.qr-steps li { display: flex; align-items: center; gap: 14px; } -.qr-step-icon { display: grid; place-items: center; width: clamp(42px, 5cqw, 68px); height: clamp(42px, 5cqw, 68px); color: var(--accent); border: 1px solid var(--line); border-radius: 16px; background: color-mix(in srgb, var(--accent) 5%, var(--background)); flex-shrink: 0; } -.qr-steps li > div { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 5px 9px; } -.qr-step-number { font-size: clamp(12px, 1.4cqw, 20px); color: var(--accent); align-self: center; } -.qr-steps strong { font-size: clamp(17px, 2cqw, 29px); font-weight: 550; line-height: 1.15; } -.qr-step-detail { grid-column: 2; color: var(--muted); font-size: clamp(13px, 1.5cqw, 21px); } -.passenger-link { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--accent); text-decoration: none; font-size: clamp(18px, 2.1cqw, 30px); font-weight: 550; border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); background: color-mix(in srgb, var(--accent) 8%, var(--background)); border-radius: 16px; padding: 14px 18px; } +.qr-description { min-width: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 12px; text-align: center; } +.qr-info .qr-description p { font-size: clamp(18px, 3cqw, 40px); line-height: 1.25; margin: 0; } +.passenger-link { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); text-decoration: none; font-size: clamp(18px, 2.5cqw, 30px); font-weight: 550; } .passenger-link span { min-width: 0; overflow-wrap: anywhere; } -@keyframes qr-route-flow { to { stroke-dashoffset: -150; } } -@container qr-card (max-width: 600px) { - .qr-content { display: flex; flex-direction: column; gap: 12px; } - .qr-stage { flex: 1 1 0; min-height: 0; padding: 12px; border-radius: 20px; } - .qr-stage-corner { display: none; } - .qr-paper { max-width: 100%; border-radius: 20px; padding: 6px; } - .qr-description { padding: 0; gap: 12px; flex-shrink: 0; } - .qr-eyebrow { font-size: 11px; margin-bottom: 6px; } - .qr-stop { font-size: clamp(22px, 6cqw, 30px); } - .qr-info .qr-summary, .qr-step-detail { display: none; } - .qr-steps { gap: 8px; } - .qr-steps li { gap: 9px; } - .qr-step-icon { width: 30px; height: 30px; border: 0; border-radius: 9px; } - .qr-step-icon svg { width: 24px; height: 24px; } - .qr-steps strong { font-size: 15px; } - .qr-step-number { font-size: 11px; } - .passenger-link { font-size: 18px; padding: 10px 12px; border-radius: 12px; } +@container qr-card (min-width: 601px) { + .qr-content { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); gap: 32px; } + .qr-description { align-items: flex-start; text-align: left; } } -@container qr-card (min-width: 601px) and (max-height: 420px) { - .qr-description { gap: 12px; padding: 0; } - .qr-eyebrow, .qr-step-detail { display: none; } - .qr-stop { font-size: clamp(26px, 3.5cqw, 40px); } - .qr-info .qr-summary { font-size: 18px; margin-top: 10px; } - .qr-steps { gap: 10px; } - .qr-step-icon { width: 42px; height: 42px; } - .qr-steps strong { font-size: 20px; } - .passenger-link { font-size: 22px; padding: 10px 14px; } -} -@container qr-card (min-width: 601px) and (max-height: 320px) { - .qr-description { gap: 10px; padding: 0; } - .qr-eyebrow, .qr-info .qr-summary, .qr-step-detail { display: none; } - .qr-stop { font-size: clamp(26px, 3.5cqw, 40px); } - .qr-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } - .qr-steps li { gap: 8px; } - .qr-step-icon { width: 36px; height: 36px; border: 0; } - .qr-step-icon svg { width: 28px; height: 28px; } - .qr-steps li > div { display: flex; flex-direction: column; gap: 3px; } - .qr-steps strong { font-size: 17px; } - .qr-step-number { align-self: flex-start; font-size: 12px; } - .passenger-link { font-size: 22px; padding: 10px 14px; } -} -@container qr-card (max-width: 600px) and (max-height: 380px) { - .qr-content { display: flex; flex-direction: column; } - .qr-stage { padding: 10px; } - .qr-paper { padding: 5px; border-radius: 16px; } - .qr-intro, .qr-steps li:nth-child(2) { display: none; } - .qr-description { flex-direction: row; align-items: center; justify-content: space-between; gap: 10px; } - .qr-step-number { display: none; } - .qr-steps li > div { display: block; } - .qr-step-icon { display: none; } - .qr-steps strong { font-size: 14px; } - .passenger-link { font-size: 14px; padding: 8px; gap: 4px; } +@container qr-card (max-width: 600px) and (max-height: 300px) { + .qr-description { gap: 6px; } + .qr-info .qr-description p, .passenger-link { font-size: 16px; } .passenger-link svg { width: 18px; height: 18px; } } .info-screen[data-compact='true'] .info-grid { grid-template-columns: minmax(0, 1fr); } @@ -220,14 +164,10 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .info-status { display: flex; align-items: center; gap: 10px; flex-shrink: 0; color: var(--secondary); font-size: clamp(14px, 1.2vw, 22px); padding-top: 14px; line-height: 1.3; } .info-status svg { color: var(--accent); } .info-notices { display: grid; gap: 18px; } -.notice { --notice-color: var(--accent); position: relative; isolation: isolate; display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 24px; padding: clamp(20px, 2vw, 36px); border: 1px solid color-mix(in srgb, var(--notice-color) 35%, var(--line)); border-radius: 26px; background: color-mix(in srgb, var(--notice-color) 5%, var(--background)); overflow: hidden; } +.notice { --notice-color: var(--accent); display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 20px; padding: clamp(20px, 2vw, 32px); border: 1px solid var(--line); border-radius: 24px; background: var(--background); overflow: hidden; } .notice[data-kind='cancellation'], .notice[data-kind='delay'] { --notice-color: var(--warning); } -.notice::after { content: ''; position: absolute; z-index: -1; width: 36%; top: -50%; right: -5%; height: 200%; transform: rotate(16deg); background: repeating-linear-gradient(0deg, transparent 0 24px, color-mix(in srgb, var(--notice-color) 5%, transparent) 24px 25px); pointer-events: none; } .notice-graphic { display: flex; flex-direction: column; align-items: center; color: var(--notice-color); padding: 2px 0; } -.notice-signal { display: grid; place-items: center; width: 76px; height: 76px; border-radius: 23px; background: color-mix(in srgb, var(--notice-color) 16%, var(--background)); border: 1px solid color-mix(in srgb, var(--notice-color) 45%, var(--line)); } -.notice-line { width: 2px; flex: 1 1 0; min-height: 30px; margin: 10px 0; background: linear-gradient(var(--notice-color), color-mix(in srgb, var(--notice-color) 20%, transparent)); } -.notice[data-kind='cancellation'] .notice-line { background: repeating-linear-gradient(to bottom, var(--notice-color) 0 5px, transparent 5px 11px); } -.notice-endpoint { width: 12px; height: 12px; border: 2px solid var(--notice-color); border-radius: 50%; } +.notice-signal { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 14px; background: color-mix(in srgb, var(--notice-color) 10%, var(--background)); } .notice-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 9px 18px; margin-bottom: 16px; } .notice-type { color: var(--notice-color); font-size: clamp(14px, 1.2vw, 22px); font-weight: 650; } .notice-period { color: var(--secondary); font-size: clamp(13px, 1vw, 18px); } @@ -235,8 +175,6 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .notice-routes .route-badge { width: auto; min-width: 54px; height: 42px; padding: 0 14px; font-size: 26px; border-radius: 12px; background: var(--notice-color); border-color: var(--notice-color); } .notice h3 { font-size: clamp(25px, 2.6vw, 46px); font-weight: 600; line-height: 1.12; margin: 0 0 14px; letter-spacing: -.035em; overflow-wrap: anywhere; } .notice p { font-size: clamp(18px, 1.65vw, 30px); line-height: 1.45; color: var(--secondary); margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; } -.notice-scope { display: flex; align-items: center; gap: 8px; margin-top: 18px; font-size: 14px; color: var(--notice-color); } -.notice-watermark { position: absolute; right: -30px; bottom: -50px; color: var(--notice-color); opacity: .04; transform: rotate(-15deg); z-index: -1; } .persistent-next { display: flex; align-items: center; gap: 18px; border-top: 1px solid var(--line); padding: 14px 0; flex: 0 0 auto; --row-height: 72px; } .persistent-next .route-badge { width: 78px; height: 50px; font-size: 31px; padding: 0 10px; border-radius: 14px; } .persistent-destination { min-width: 0; display: flex; flex-direction: column; gap: 7px; } @@ -346,33 +284,6 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .settings-scroll-button { min-width: 78px; padding-inline: 8px; } } -@container (max-height: 380px) { - .weather-current strong { font-size: 56px; } - .weather-info .weather-description { font-size: 20px; margin: 10px 0; } - .weather-body { gap: 12px; } - .weather-hours > div { display: grid; grid-template-columns: auto auto; justify-content: center; align-items: center; gap: 7px; } - .weather-hours time { grid-column: 1 / -1; text-align: center; } - .weather-hours strong { font-size: 22px; } - .weather-attribution { padding-top: 12px; } -} -@media (min-width: 701px) { - .info-screen[data-compact='true'] .weather-body { flex: 1 1 0; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; gap: 32px; } - .info-screen[data-compact='true'] .weather-now { height: 100%; min-height: 0; display: flex; flex-direction: column; justify-content: center; padding: clamp(12px, 1.5cqw, 24px); border: 1px solid var(--line); border-radius: 22px; background: color-mix(in srgb, var(--accent) 5%, var(--background)); } - .info-screen[data-compact='true'] .weather-now-label { display: block; font-size: clamp(12px, 1.5cqw, 20px); color: var(--muted); letter-spacing: .1em; margin-bottom: 12px; } - .info-screen[data-compact='true'] .weather-current { font-size: clamp(48px, min(13cqw, 28cqh), 180px); gap: .16em; } - .info-screen[data-compact='true'] .weather-current strong { font-size: 1em; } - .info-screen[data-compact='true'] .weather-current svg { width: .8em !important; height: .8em !important; } - .info-screen[data-compact='true'] .weather-description { font-size: clamp(20px, 3cqw, 36px); margin: 12px 0; } - .info-screen[data-compact='true'] .weather-details { font-size: clamp(16px, 2cqw, 23px); } - .info-screen[data-compact='true'] .weather-future { height: 100%; display: flex; flex-direction: column; min-height: 0; } - .info-screen[data-compact='true'] .weather-hours { flex: 1 1 0; min-height: 0; padding-top: 0; border: 0; } - .info-screen[data-compact='true'] .weather-hours > div { display: flex; justify-content: space-between; min-height: 0; padding: clamp(8px, 1cqw, 18px) 6px; gap: 8px; border: 1px solid var(--line); border-radius: 18px; background: color-mix(in srgb, var(--accent) 5%, var(--background)); } - .info-screen[data-compact='true'] .weather-hours time { font-size: clamp(15px, 2cqw, 26px); } - .info-screen[data-compact='true'] .weather-hours svg { width: clamp(36px, 5cqw, 64px) !important; height: clamp(36px, 5cqw, 64px) !important; } - .info-screen[data-compact='true'] .weather-hours strong { font-size: clamp(26px, 3.5cqw, 48px); } - .info-screen[data-compact='true'] .weather-hour-rain { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2px 4px; font-size: clamp(12px, 1.5cqw, 20px); color: var(--secondary); } - .info-screen[data-compact='true'] .precipitation-chance { text-align: center; font-size: 17px; } -} @media (max-width: 1100px) { .brand-word { display: none; } .header-right { column-gap: 18px; } @@ -465,10 +376,6 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .information-tabs button { font-size: 12px; padding: 7px 10px; } .info-card { padding: 18px; } .info-card h3 { font-size: 24px; margin-bottom: 12px; } - .weather-current strong { font-size: 58px; } - .weather-info .weather-description { font-size: 20px; } - .weather-hours { padding-top: 12px; } - .weather-body { gap: 12px; } .weather-attribution { padding-top: 12px; } .info-status { font-size: 12px; padding-top: 10px; } .notice { grid-template-columns: 44px minmax(0, 1fr); gap: 14px; padding: 18px 14px; border-radius: 20px; } @@ -498,13 +405,6 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .info-screen[data-compact='true'][data-cards='true'] .info-intro { position: absolute; top: 27px; right: 20px; z-index: 1; margin: 0; } .info-screen[data-compact='true'][data-cards='true'] .info-intro h2 { display: none; } .info-screen[data-compact='true'][data-cards='true'] .info-card > h3 { padding-right: 290px; min-height: 36px; text-align: left; display: flex; align-items: center; } - .info-screen[data-compact='true'] .weather-now-label { margin-bottom: 8px; } - .info-screen[data-compact='true'] .weather-description { font-size: 20px; margin: 8px 0; } - .info-screen[data-compact='true'] .weather-details { font-size: 15px; } - .info-screen[data-compact='true'] .weather-hours strong { font-size: 26px; } - .info-screen[data-compact='true'] .weather-hours time { font-size: 15px; } - .info-screen[data-compact='true'] .weather-hours svg { width: 36px !important; height: 36px !important; } - .info-screen[data-compact='true'] .weather-hour-rain { font-size: 12px; } .info-status { font-size: 13px; padding-top: 10px; } .persistent-next > .eyebrow { display: none; } .persistent-next { padding: 10px 0; } @@ -512,13 +412,6 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 @media (max-width: 700px) and (max-height: 740px) { .info-intro h2 { font-size: 25px; } .info-intro { gap: 8px; } - .weather-current strong { font-size: 46px; } - .weather-current svg { width: 44px !important; height: 44px !important; } - .weather-info .weather-description { font-size: 18px; margin: 8px 0; } - .weather-body { gap: 8px; } - .weather-hours { padding-top: 8px; } - .weather-hours > div { gap: 6px; } - .precipitation-chance { margin-top: 8px; } .weather-attribution { padding-top: 8px; font-size: 10px; } .weather-info { padding: 16px; } } @@ -530,8 +423,4 @@ h1 { font-size: clamp(30px, 3.8vw, 76px); letter-spacing: -.05em; line-height: 1 .numeric-current { transform: none !important; } .destination-old { visibility: hidden; } .destination-current { transform: none !important; } - .route-track { animation: none !important; } - .qr-route-art path { animation: none !important; } - .route-lane { transform: none; overflow: auto; } - .route-group:nth-child(2) { display: none; } }