18 Commits
v2.1 ... v2.6

Author SHA1 Message Date
ed621b7ce2 Убрана надпись «Мурманская область», экран не гаснет
В шапке приложения убрана надпись «Мурманская область» вместе с её
стилем .route, который больше не используется. Заголовок
«Автобус №127» теперь начинает шапку.

В MainActivity добавлен флаг FLAG_KEEP_SCREEN_ON: экран телефона
не гаснет, пока открыто приложение с расписанием — не нужно
касаться экрана, чтобы посмотреть время отправления.

Также в этом же релизе переключатель дней в полном расписании
(Будни / Суббота / Воскресенье) из предыдущих коммитов.
2026-09-28 10:12:38 +00:00
0e9150b5ce Убрана надпись «Мурманская область», экран не гаснет
В шапке приложения убрана надпись «Мурманская область» вместе с её
стилем .route, который больше не используется. Заголовок
«Автобус №127» теперь начинает шапку.

В MainActivity добавлен флаг FLAG_KEEP_SCREEN_ON: экран телефона
не гаснет, пока открыто приложение с расписанием — не нужно
касаться экрана, чтобы посмотреть время отправления.

Также в этом же релизе переключатель дней в полном расписании
(Будни / Суббота / Воскресенье) из предыдущих коммитов.
2026-09-28 10:12:37 +00:00
6f591c10d5 Убрана надпись «Мурманская область», экран не гаснет
В шапке приложения убрана надпись «Мурманская область» вместе с её
стилем .route, который больше не используется. Заголовок
«Автобус №127» теперь начинает шапку.

В MainActivity добавлен флаг FLAG_KEEP_SCREEN_ON: экран телефона
не гаснет, пока открыто приложение с расписанием — не нужно
касаться экрана, чтобы посмотреть время отправления.

Также в этом же релизе переключатель дней в полном расписании
(Будни / Суббота / Воскресенье) из предыдущих коммитов.
2026-09-28 10:12:35 +00:00
a15dccf3c2 Переключатель дней в полном расписании
В разделе «Полное расписание» появились кнопки «Будни», «Суббота»,
«Воскресенье». По умолчанию подсвечивается реальный день недели
по часовому поясу МСК, далее таблица переключается вручную.

Единый массив SCHEDULE заменён на SCHEDULES с расписанием по типам
дней: 13 рейсов в будни, 10 в субботу, 6 в воскресенье.
Карточки ближайших рейсов переключаются вместе с таблицей.

Важно: по субботе и воскресенью в репозитории хранились только
отправления из Полярных Зорь и прибытия в Зашеек (app/src/main/java/
ru/ua1zbe/bus127/MainActivity.kt). Первая и четвёртая колонки
восстановлены по правилам будних дней — кол0 = кол1 − 50 мин,
кол3 = кол2 + 20 мин — и требуют сверки с официальным расписанием.
Также исправлен перепуск субботы и воскресенья: Date#getDay()
возвращает 0 для воскресенья, а не для субботы.
2026-09-28 08:05:39 +00:00
f82fe1a524 Переключатель дней в полном расписании
В разделе «Полное расписание» появились кнопки «Будни», «Суббота»,
«Воскресенье». По умолчанию подсвечивается реальный день недели
по часовому поясу МСК, далее таблица переключается вручную.

Единый массив SCHEDULE заменён на SCHEDULES с расписанием по типам
дней: 13 рейсов в будни, 10 в субботу, 6 в воскресенье.
Карточки ближайших рейсов переключаются вместе с таблицей.

Важно: по субботе и воскресенью в репозитории хранились только
отправления из Полярных Зорь и прибытия в Зашеек (app/src/main/java/
ru/ua1zbe/bus127/MainActivity.kt). Первая и четвёртая колонки
восстановлены по правилам будних дней — кол0 = кол1 − 50 мин,
кол3 = кол2 + 20 мин — и требуют сверки с официальным расписанием.
Также исправлен перепуск субботы и воскресенья: Date#getDay()
возвращает 0 для воскресенья, а не для субботы.
2026-09-28 08:05:37 +00:00
0b4852c582 Переключатель дней в полном расписании
В разделе «Полное расписание» появились кнопки «Будни», «Суббота»,
«Воскресенье». По умолчанию подсвечивается реальный день недели
по часовому поясу МСК, далее таблица переключается вручную.

Единый массив SCHEDULE заменён на SCHEDULES с расписанием по типам
дней: 13 рейсов в будни, 10 в субботу, 6 в воскресенье.
Карточки ближайших рейсов переключаются вместе с таблицей.

Важно: по субботе и воскресенью в репозитории хранились только
отправления из Полярных Зорь и прибытия в Зашеек (app/src/main/java/
ru/ua1zbe/bus127/MainActivity.kt). Первая и четвёртая колонки
восстановлены по правилам будних дней — кол0 = кол1 − 50 мин,
кол3 = кол2 + 20 мин — и требуют сверки с официальным расписанием.
Также исправлен перепуск субботы и воскресенья: Date#getDay()
возвращает 0 для воскресенья, а не для субботы.
2026-09-28 08:05:36 +00:00
37ff82a6ef Релиз 2.5: versionCode 7, versionName 2.5 (надпись в шапке, установлено на телефон) 2026-09-28 07:57:13 +00:00
5d1236dd87 Надпись в шапке: только «Мурманская область»
Убрано «· регулярный маршрут» — надпись в шапке теперь просто «Мурманская область».
2026-09-28 07:53:27 +00:00
4642ca0115 Надпись в шапке: только «Мурманская область»
Убрано «· регулярный маршрут» — надпись в шапке теперь просто «Мурманская область».
2026-09-28 07:53:25 +00:00
0a3d7baf82 Релиз 2.4: versionCode 6, versionName 2.4 (прибытие в Полярные Зори из Кандалакши) 2026-09-28 06:52:40 +00:00
885a96dced Карточка Кандалакши: добавлено прибытие в Полярные Зори
Раньше в карточке было только «прибытие Зашеек», хотя рейс идёт
через Полярные Зори и время прибытия туда есть в таблице (колонка 1).
Теперь показываются оба прибытия: Полярные Зори, затем Зашеек.
Время берётся из таблицы, ничего не рассчитывается.
2026-09-28 06:51:39 +00:00
f437fb8c5a Карточка Кандалакши: добавлено прибытие в Полярные Зори
Раньше в карточке было только «прибытие Зашеек», хотя рейс идёт
через Полярные Зори и время прибытия туда есть в таблице (колонка 1).
Теперь показываются оба прибытия: Полярные Зори, затем Зашеек.
Время берётся из таблицы, ничего не рассчитывается.
2026-09-28 06:51:38 +00:00
366d0dd454 Релиз 2.3: versionCode 5, versionName 2.3 (расчёт прибытия в Кандалакшу) 2026-09-28 06:45:40 +00:00
80b1b2b0cc Считается прибытие в Кандалакшу
Для направления Полярные Зори → Кандалакша время прибытия в таблице
расписания отсутствует, поэтому оно теперь рассчитывается: отправление
из Полярных Зорь плюс 50 минут. Время в пути взято из самой таблицы —
в 12 строках из 13 Кандалакша → Полярные Зори занимает ровно 50 минут.

Заодно исправлена ошибка из v2.2: в карточке Зашейка колонка 3 подписана
как «прибытие Кандалакша», хотя это время в Полярных Зорях (20–30 минут
после прибытия в Зашеек, а не час до Кандалакши). Теперь в карточке
Зашейка показываются оба прибытия: Полярные Зори, затем Кандалакша.

Модель направлений переведена на список прибытий arrs, где время берётся
либо из колонки таблицы (col), либо считается как offset от отправления.
2026-09-28 06:44:57 +00:00
269a651325 Считается прибытие в Кандалакшу
Для направления Полярные Зори → Кандалакша время прибытия в таблице
расписания отсутствует, поэтому оно теперь рассчитывается: отправление
из Полярных Зорь плюс 50 минут. Время в пути взято из самой таблицы —
в 12 строках из 13 Кандалакша → Полярные Зори занимает ровно 50 минут.

Заодно исправлена ошибка из v2.2: в карточке Зашейка колонка 3 подписана
как «прибытие Кандалакша», хотя это время в Полярных Зорях (20–30 минут
после прибытия в Зашеек, а не час до Кандалакши). Теперь в карточке
Зашейка показываются оба прибытия: Полярные Зори, затем Кандалакша.

Модель направлений переведена на список прибытий arrs, где время берётся
либо из колонки таблицы (col), либо считается как offset от отправления.
2026-09-28 06:44:56 +00:00
6386a3420e Релиз 2.2: versionCode 4, versionName 2.2 (карточки Кандалакша/Зашеек, 5 рейсов) 2026-09-28 06:36:40 +00:00
eecb0ed95b Остановки Кандалакша и Зашеек: одна карточка направления, 5 ближайших рейсов
Раньше для крайних остановок показывались две карточки, хотя все рейсы
отправляются в одно время (Кандалакша — по отправлению, Зашеек — по
отправлению) и различаются только конечной остановкой. Карточки объединены:
- Кандалакша: «Полярные Зори, Зашеек», отправление 05:20 → Зашеек 06:30;
- Зашеек: «Полярные Зори, Кандалакша», отправление 06:30 → Кандалакша 06:50.

Число рейсов в карточке увеличено с 3 до 5 (TRIPS_SHOWN). Одна карточка
занимает всю ширину. Время прибытия подписано остановкой, так как в
объединённой карточке время прибытия относится к конечной остановке.
2026-09-28 06:34:53 +00:00
99bb3b2981 Остановки Кандалакша и Зашеек: одна карточка направления, 5 ближайших рейсов
Раньше для крайних остановок показывались две карточки, хотя все рейсы
отправляются в одно время (Кандалакша — по отправлению, Зашеек — по
отправлению) и различаются только конечной остановкой. Карточки объединены:
- Кандалакша: «Полярные Зори, Зашеек», отправление 05:20 → Зашеек 06:30;
- Зашеек: «Полярные Зори, Кандалакша», отправление 06:30 → Кандалакша 06:50.

Число рейсов в карточке увеличено с 3 до 5 (TRIPS_SHOWN). Одна карточка
занимает всю ширину. Время прибытия подписано остановкой, так как в
объединённой карточке время прибытия относится к конечной остановке.
2026-09-28 06:34:51 +00:00
4 changed files with 265 additions and 66 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId "ru.ua1zbe.bus127" applicationId "ru.ua1zbe.bus127"
minSdk 26 minSdk 26
targetSdk 34 targetSdk 34
versionCode 3 versionCode 8
versionName "2.1" versionName "2.6"
} }
buildTypes { buildTypes {

View File

@@ -16,8 +16,7 @@
color:var(--txt);min-height:100vh;padding:24px 16px 48px} color:var(--txt);min-height:100vh;padding:24px 16px 48px}
.wrap{max-width:760px;margin:0 auto} .wrap{max-width:760px;margin:0 auto}
header{text-align:center;margin-bottom:18px} header{text-align:center;margin-bottom:18px}
.route{font-size:12px;letter-spacing:.14em;color:var(--muted);text-transform:uppercase} h1{font-size:clamp(22px,4.5vw,30px);margin:0 0 4px;font-weight:700}
h1{font-size:clamp(22px,4.5vw,30px);margin:6px 0 4px;font-weight:700}
.sub{color:var(--muted);font-size:14px} .sub{color:var(--muted);font-size:14px}
.clock{font-size:22px;font-weight:600;margin-top:10px;color:var(--accent)} .clock{font-size:22px;font-weight:600;margin-top:10px;color:var(--accent)}
.clock small{color:var(--muted);font-weight:400;font-size:12px} .clock small{color:var(--muted);font-weight:400;font-size:12px}
@@ -35,6 +34,7 @@
.where{text-align:center;color:var(--muted);font-size:13px;margin:2px 0 12px} .where{text-align:center;color:var(--muted);font-size:13px;margin:2px 0 12px}
.dir{display:grid;grid-template-columns:1fr 1fr;gap:12px} .dir{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.dir .card:only-child{grid-column:1 / -1} /* одна карточка — во всю ширину */
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px} .card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px}
.card h3{margin:0 0 4px;font-size:15px;font-weight:700} .card h3{margin:0 0 4px;font-size:15px;font-weight:700}
.card h3 .arr{color:var(--accent)} .card h3 .arr{color:var(--accent)}
@@ -58,6 +58,15 @@
.tablecard{background:var(--card);border:1px solid var(--line);border-radius:14px; .tablecard{background:var(--card);border:1px solid var(--line);border-radius:14px;
overflow:hidden;display:none;margin-bottom:8px} overflow:hidden;display:none;margin-bottom:8px}
/* переключатель типа дня в полном расписании */
.days{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;padding:12px 12px 10px;
border-bottom:1px solid var(--line)}
.days button{padding:9px 4px;border-radius:10px;border:1px solid var(--line);
background:var(--card2);color:var(--muted);font-size:13px;font-weight:600;
cursor:pointer;transition:.15s;font-family:inherit}
.days button:hover{border-color:var(--accent2);color:var(--txt)}
.days button.active{background:linear-gradient(135deg,var(--accent2),var(--accent));
color:#04101c;border-color:transparent}
.thead{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;background:var(--card2); .thead{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;background:var(--card2);
font-size:11px;color:var(--muted);text-align:center} font-size:11px;color:var(--muted);text-align:center}
.thead div{padding:11px 6px;border-left:1px solid var(--line)} .thead div{padding:11px 6px;border-left:1px solid var(--line)}
@@ -76,7 +85,6 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<header> <header>
<div class="route">Мурманская область · регулярный маршрут</div>
<h1>Автобус №127</h1> <h1>Автобус №127</h1>
<div class="sub">Кандалакша — Полярные Зори — Зашеек</div> <div class="sub">Кандалакша — Полярные Зори — Зашеек</div>
<div class="clock" id="clock">--:--:-- <small>МСК</small></div> <div class="clock" id="clock">--:--:-- <small>МСК</small></div>
@@ -95,6 +103,11 @@
<button class="fullbtn" id="fullBtn">Полное расписание <span class="chev">▾</span></button> <button class="fullbtn" id="fullBtn">Полное расписание <span class="chev">▾</span></button>
<section class="tablecard" id="fullTable"> <section class="tablecard" id="fullTable">
<div class="days" id="dayPick">
<button data-day="weekday" class="active">Будни</button>
<button data-day="saturday">Суббота</button>
<button data-day="sunday">Воскресенье</button>
</div>
<div class="thead"> <div class="thead">
<div><b>Кандалакша</b>отпр.</div> <div><b>Кандалакша</b>отпр.</div>
<div><b>Полярные Зори</b>→ Зашеек</div> <div><b>Полярные Зори</b>→ Зашеек</div>
@@ -108,33 +121,83 @@
</div> </div>
<script> <script>
const SCHEDULE = [ // Расписание по типам дней недели.
["05:20","06:20","06:30","06:50"], // Колонки: отправление из Кандалакши · Полярные Зори (в Зашеек) ·
["06:10","07:00","07:25","07:45"], // прибытие в Зашеек · Полярные Зори (обратно).
["07:40","08:30","09:00","09:20"], //
["08:00","08:50","09:20","09:50"], // Будни — полная таблица маршрута. По субботе и воскресенью в репозитории
["09:45","10:35","11:05","11:25"], // хранились только отправления из Полярных Зорь и прибытия в Зашеек
["11:00","11:50","12:20","12:40"], // (списки в app/src/main/java/ru/ua1zbe/bus127/MainActivity.kt), поэтому
["12:30","13:20","13:50","14:10"], // первая и четвёртая колонки восстановлены по тем же правилам, что и для
["13:30","14:20","14:50","15:10"], // будней, и требуют сверки с официальным расписанием:
["14:00","14:50","15:25","15:50"], // кол0 = кол1 − 50 мин (Кандалакша → Полярные Зори занимает ровно 50 мин)
["15:30","16:20","17:00","17:25"], // кол3 = кол2 + 20 мин (так разность кол3−кол2 выглядит в 8 строках из 13)
["16:50","17:40","18:10","18:35"], const SCHEDULES = {
["18:20","19:10","19:40","20:05"], weekday: [
["20:20","21:10","21:40","22:05"] ["05:20","06:20","06:30","06:50"],
]; ["06:10","07:00","07:25","07:45"],
["07:40","08:30","09:00","09:20"],
["08:00","08:50","09:20","09:50"],
["09:45","10:35","11:05","11:25"],
["11:00","11:50","12:20","12:40"],
["12:30","13:20","13:50","14:10"],
["13:30","14:20","14:50","15:10"],
["14:00","14:50","15:25","15:50"],
["15:30","16:20","17:00","17:25"],
["16:50","17:40","18:10","18:35"],
["18:20","19:10","19:40","20:05"],
["20:20","21:10","21:40","22:05"]
],
saturday: [
["06:10","07:00","07:25","07:45"],
["07:30","08:20","08:45","09:05"],
["08:45","09:35","10:05","10:25"],
["10:10","11:00","11:30","11:50"],
["12:30","13:20","13:50","14:10"],
["14:00","14:50","15:45","16:05"],
["15:30","16:20","16:45","17:05"],
["17:00","17:50","18:20","18:40"],
["18:10","19:00","19:30","19:50"],
["20:20","21:10","21:40","22:00"]
],
sunday: [
["06:10","07:00","07:25","07:45"],
["08:45","09:35","10:05","10:25"],
["12:30","13:20","13:50","14:10"],
["14:00","14:50","15:25","15:45"],
["16:50","17:40","18:10","18:30"],
["20:20","21:10","21:40","22:00"]
]
};
const DAY_KEYS = ["weekday","saturday","sunday"];
const DAY_TITLES = {weekday:"Будни", saturday:"Суббота", sunday:"Воскресенье"};
const TRIPS_SHOWN = 5; // сколько ближайших рейсов показывать в карточке
// Время в пути Полярные Зори ↔ Кандалакша. В таблице расписания его нет:
// в 12 строках из 13 Кандалакша → ПЗ занимает ровно 50 минут
// (в первой строке 60 — по всей видимости опечатка).
const RIDE_PZ_KAND = 50;
// arrs — список прибытий: {label, col} — время из колонки таблицы,
// {label, offset} — «время в пути» от отправления (если col не задан).
const DIRECTIONS = { const DIRECTIONS = {
// От Кандалакши и Зашейка все рейсы идут через Полярные Зори — одна карточка
"Кандалакша": [ "Кандалакша": [
{to:"Полярные Зори", dep:0, arr:1, via:"через 1 час"}, {to:"Полярные Зори, Зашеек", dep:0, via:"через Полярные Зори",
{to:"Зашеек", dep:0, arr:2, via:"через Полярные Зори"} arrs:[{label:"Полярные Зори", col:1},
{label:"Зашеек", col:2}]}
], ],
"Полярные Зори": [ "Полярные Зори": [
{to:"Зашеек", dep:1, arr:2, via:"≈ 30 мин в пути"}, {to:"Зашеек", dep:1, via:"≈ 30 мин в пути",
{to:"Кандалакша", dep:3, arr:null, via:"≈ 1 час в пути"} arrs:[{label:"Зашеек", col:2}]},
{to:"Кандалакша", dep:3, via:"≈ 50 мин в пути",
arrs:[{label:"Кандалакша", offset:RIDE_PZ_KAND}]}
], ],
"Зашеек": [ "Зашеек": [
{to:"Полярные Зори", dep:2, arr:3, via:"≈ 20 мин в пути"}, {to:"Полярные Зори, Кандалакша", dep:2, via:"через Полярные Зори",
{to:"Кандалакша", dep:2, arr:null, via:"через Полярные Зори"} arrs:[{label:"Полярные Зори", col:3},
{label:"Кандалакша", col:3, offset:RIDE_PZ_KAND}]}
] ]
}; };
const DAYS = ["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"]; const DAYS = ["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"];
@@ -173,6 +236,12 @@ function nowMs(){
second: d.getUTCSeconds(), day: d.getUTCDay() }; second: d.getUTCSeconds(), day: d.getUTCDay() };
} }
function fmtHM(m){
const p = n=>String(n).padStart(2,"0");
m = ((m % 1440) + 1440) % 1440; // на случай перехода через полночь
return `${p(Math.floor(m/60))}:${p(m%60)}`;
}
function fmtWait(m){ function fmtWait(m){
if(m <= 0) return "отправление сейчас"; if(m <= 0) return "отправление сейчас";
const h = Math.floor(m/60), mm = m%60; const h = Math.floor(m/60), mm = m%60;
@@ -183,24 +252,38 @@ function fmtWait(m){
let currentStop = "Полярные Зори"; let currentStop = "Полярные Зори";
// Date#getDay(): 0 — воскресенье, 6 — суббота
function dayKeyFor(dow){ return dow===0 ? "sunday" : dow===6 ? "saturday" : "weekday"; }
function sched(){ return SCHEDULES[activeDay]; }
function render(){ function render(){
const now = nowMs(); const now = nowMs();
const curMin = now.hour*60 + now.minute; const curMin = now.hour*60 + now.minute;
const dirs = DIRECTIONS[currentStop]; const dirs = DIRECTIONS[currentStop];
const data = sched();
document.getElementById("dirBox").innerHTML = dirs.map(d=>{ document.getElementById("dirBox").innerHTML = dirs.map(d=>{
const upcoming = SCHEDULE const upcoming = data
.map((r,ri)=>({dep:toMin(r[d.dep]), arr:d.arr!==null?r[d.arr]:null, ri})) .map((r,ri)=>{
const dep = toMin(r[d.dep]);
const arrs = d.arrs.map(a=>{
const base = a.col!==undefined ? toMin(r[a.col]) : dep;
return {label:a.label, t:fmtHM(base + (a.offset||0))};
});
return {dep, arrs, ri};
})
.filter(x=>x.dep >= curMin) .filter(x=>x.dep >= curMin)
.sort((a,b)=>a.dep-b.dep) .sort((a,b)=>a.dep-b.dep)
.slice(0,3) .slice(0,TRIPS_SHOWN)
.map(x=>{ .map(x=>{
const w = x.dep - curMin; const w = x.dep - curMin;
const arr = x.arr ? `<small>прибытие ${x.arr}</small>` : ""; // .trip .t small имеет display:block, поэтому каждый <small> — своя строка
const arr = x.arrs.map(a=>`<small>прибытие ${a.label} ${a.t}</small>`).join("");
return `<div class="trip ${w<15?'soon':''}"> return `<div class="trip ${w<15?'soon':''}">
<span class="t">${SCHEDULE[x.ri][d.dep]}${arr}</span> <span class="t">${data[x.ri][d.dep]}${arr}</span>
<span class="w">${fmtWait(w)}</span> <span class="w">${fmtWait(w)}</span>
</div>`; </div>`;
}).join("") || `<div class="empty">на сегодня всё — первый завтра в ${SCHEDULE[0][d.dep]}</div>`; }).join("") || `<div class="empty">на сегодня всё — первый завтра в ${data[0][d.dep]}</div>`;
return `<div class="card"> return `<div class="card">
<h3>→ <span class="arr">${d.to}</span></h3> <h3>→ <span class="arr">${d.to}</span></h3>
<div class="via">${d.via}</div> <div class="via">${d.via}</div>
@@ -210,7 +293,7 @@ function render(){
} }
function renderTable(){ function renderTable(){
document.getElementById("tableBody").innerHTML = SCHEDULE.map(row => document.getElementById("tableBody").innerHTML = sched().map(row =>
`<div class="row"><div>${row[0]}</div><div>${row[1]}</div><div>${row[2]}</div><div>${row[3]}</div></div>` `<div class="row"><div>${row[0]}</div><div>${row[1]}</div><div>${row[2]}</div><div>${row[3]}</div></div>`
).join(""); ).join("");
} }
@@ -220,6 +303,15 @@ function renderDay(){
document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`; document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`;
} }
// Подсветка выбранного типа дня и перерисовка расписания
function selectDay(key){
activeDay = key;
document.querySelectorAll("#dayPick button").forEach(b=>
b.classList.toggle("active", b.dataset.day === key));
render();
renderTable();
}
function tickClock(){ function tickClock(){
const d = nowMs(); const d = nowMs();
const p = n=>String(n).padStart(2,"0"); const p = n=>String(n).padStart(2,"0");
@@ -238,6 +330,10 @@ document.querySelectorAll(".pick").forEach(btn=>{
}); });
}); });
document.querySelectorAll("#dayPick button").forEach(btn=>{
btn.addEventListener("click",()=>selectDay(btn.dataset.day));
});
document.getElementById("fullBtn").addEventListener("click",()=>{ document.getElementById("fullBtn").addEventListener("click",()=>{
const t = document.getElementById("fullTable"); const t = document.getElementById("fullTable");
const b = document.getElementById("fullBtn"); const b = document.getElementById("fullBtn");
@@ -246,7 +342,9 @@ document.getElementById("fullBtn").addEventListener("click",()=>{
b.classList.toggle("open", !open); b.classList.toggle("open", !open);
}); });
tickClock(); renderDay(); render(); renderTable(); let activeDay = dayKeyFor(nowMs().day); // по умолчанию — реальный день недели
selectDay(activeDay);
tickClock(); renderDay();
setInterval(tickClock,1000); setInterval(tickClock,1000);
setInterval(render,30000); setInterval(render,30000);
setInterval(renderDay,60000); setInterval(renderDay,60000);

View File

@@ -2,6 +2,7 @@ package ru.ua1zbe.bus127;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.view.WindowManager;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
@@ -10,6 +11,8 @@ public class MainActivity extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// экран не гаснет, пока открыто расписание
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
WebView webView = new WebView(this); WebView webView = new WebView(this);
WebSettings settings = webView.getSettings(); WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true); settings.setJavaScriptEnabled(true);

View File

@@ -16,8 +16,7 @@
color:var(--txt);min-height:100vh;padding:24px 16px 48px} color:var(--txt);min-height:100vh;padding:24px 16px 48px}
.wrap{max-width:760px;margin:0 auto} .wrap{max-width:760px;margin:0 auto}
header{text-align:center;margin-bottom:18px} header{text-align:center;margin-bottom:18px}
.route{font-size:12px;letter-spacing:.14em;color:var(--muted);text-transform:uppercase} h1{font-size:clamp(22px,4.5vw,30px);margin:0 0 4px;font-weight:700}
h1{font-size:clamp(22px,4.5vw,30px);margin:6px 0 4px;font-weight:700}
.sub{color:var(--muted);font-size:14px} .sub{color:var(--muted);font-size:14px}
.clock{font-size:22px;font-weight:600;margin-top:10px;color:var(--accent)} .clock{font-size:22px;font-weight:600;margin-top:10px;color:var(--accent)}
.clock small{color:var(--muted);font-weight:400;font-size:12px} .clock small{color:var(--muted);font-weight:400;font-size:12px}
@@ -35,6 +34,7 @@
.where{text-align:center;color:var(--muted);font-size:13px;margin:2px 0 12px} .where{text-align:center;color:var(--muted);font-size:13px;margin:2px 0 12px}
.dir{display:grid;grid-template-columns:1fr 1fr;gap:12px} .dir{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.dir .card:only-child{grid-column:1 / -1} /* одна карточка — во всю ширину */
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px} .card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px}
.card h3{margin:0 0 4px;font-size:15px;font-weight:700} .card h3{margin:0 0 4px;font-size:15px;font-weight:700}
.card h3 .arr{color:var(--accent)} .card h3 .arr{color:var(--accent)}
@@ -58,6 +58,15 @@
.tablecard{background:var(--card);border:1px solid var(--line);border-radius:14px; .tablecard{background:var(--card);border:1px solid var(--line);border-radius:14px;
overflow:hidden;display:none;margin-bottom:8px} overflow:hidden;display:none;margin-bottom:8px}
/* переключатель типа дня в полном расписании */
.days{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;padding:12px 12px 10px;
border-bottom:1px solid var(--line)}
.days button{padding:9px 4px;border-radius:10px;border:1px solid var(--line);
background:var(--card2);color:var(--muted);font-size:13px;font-weight:600;
cursor:pointer;transition:.15s;font-family:inherit}
.days button:hover{border-color:var(--accent2);color:var(--txt)}
.days button.active{background:linear-gradient(135deg,var(--accent2),var(--accent));
color:#04101c;border-color:transparent}
.thead{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;background:var(--card2); .thead{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;background:var(--card2);
font-size:11px;color:var(--muted);text-align:center} font-size:11px;color:var(--muted);text-align:center}
.thead div{padding:11px 6px;border-left:1px solid var(--line)} .thead div{padding:11px 6px;border-left:1px solid var(--line)}
@@ -76,7 +85,6 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<header> <header>
<div class="route">Мурманская область · регулярный маршрут</div>
<h1>Автобус №127</h1> <h1>Автобус №127</h1>
<div class="sub">Кандалакша — Полярные Зори — Зашеек</div> <div class="sub">Кандалакша — Полярные Зори — Зашеек</div>
<div class="clock" id="clock">--:--:-- <small>МСК</small></div> <div class="clock" id="clock">--:--:-- <small>МСК</small></div>
@@ -95,6 +103,11 @@
<button class="fullbtn" id="fullBtn">Полное расписание <span class="chev">▾</span></button> <button class="fullbtn" id="fullBtn">Полное расписание <span class="chev">▾</span></button>
<section class="tablecard" id="fullTable"> <section class="tablecard" id="fullTable">
<div class="days" id="dayPick">
<button data-day="weekday" class="active">Будни</button>
<button data-day="saturday">Суббота</button>
<button data-day="sunday">Воскресенье</button>
</div>
<div class="thead"> <div class="thead">
<div><b>Кандалакша</b>отпр.</div> <div><b>Кандалакша</b>отпр.</div>
<div><b>Полярные Зори</b>→ Зашеек</div> <div><b>Полярные Зори</b>→ Зашеек</div>
@@ -108,33 +121,83 @@
</div> </div>
<script> <script>
const SCHEDULE = [ // Расписание по типам дней недели.
["05:20","06:20","06:30","06:50"], // Колонки: отправление из Кандалакши · Полярные Зори (в Зашеек) ·
["06:10","07:00","07:25","07:45"], // прибытие в Зашеек · Полярные Зори (обратно).
["07:40","08:30","09:00","09:20"], //
["08:00","08:50","09:20","09:50"], // Будни — полная таблица маршрута. По субботе и воскресенью в репозитории
["09:45","10:35","11:05","11:25"], // хранились только отправления из Полярных Зорь и прибытия в Зашеек
["11:00","11:50","12:20","12:40"], // (списки в app/src/main/java/ru/ua1zbe/bus127/MainActivity.kt), поэтому
["12:30","13:20","13:50","14:10"], // первая и четвёртая колонки восстановлены по тем же правилам, что и для
["13:30","14:20","14:50","15:10"], // будней, и требуют сверки с официальным расписанием:
["14:00","14:50","15:25","15:50"], // кол0 = кол1 − 50 мин (Кандалакша → Полярные Зори занимает ровно 50 мин)
["15:30","16:20","17:00","17:25"], // кол3 = кол2 + 20 мин (так разность кол3−кол2 выглядит в 8 строках из 13)
["16:50","17:40","18:10","18:35"], const SCHEDULES = {
["18:20","19:10","19:40","20:05"], weekday: [
["20:20","21:10","21:40","22:05"] ["05:20","06:20","06:30","06:50"],
]; ["06:10","07:00","07:25","07:45"],
["07:40","08:30","09:00","09:20"],
["08:00","08:50","09:20","09:50"],
["09:45","10:35","11:05","11:25"],
["11:00","11:50","12:20","12:40"],
["12:30","13:20","13:50","14:10"],
["13:30","14:20","14:50","15:10"],
["14:00","14:50","15:25","15:50"],
["15:30","16:20","17:00","17:25"],
["16:50","17:40","18:10","18:35"],
["18:20","19:10","19:40","20:05"],
["20:20","21:10","21:40","22:05"]
],
saturday: [
["06:10","07:00","07:25","07:45"],
["07:30","08:20","08:45","09:05"],
["08:45","09:35","10:05","10:25"],
["10:10","11:00","11:30","11:50"],
["12:30","13:20","13:50","14:10"],
["14:00","14:50","15:45","16:05"],
["15:30","16:20","16:45","17:05"],
["17:00","17:50","18:20","18:40"],
["18:10","19:00","19:30","19:50"],
["20:20","21:10","21:40","22:00"]
],
sunday: [
["06:10","07:00","07:25","07:45"],
["08:45","09:35","10:05","10:25"],
["12:30","13:20","13:50","14:10"],
["14:00","14:50","15:25","15:45"],
["16:50","17:40","18:10","18:30"],
["20:20","21:10","21:40","22:00"]
]
};
const DAY_KEYS = ["weekday","saturday","sunday"];
const DAY_TITLES = {weekday:"Будни", saturday:"Суббота", sunday:"Воскресенье"};
const TRIPS_SHOWN = 5; // сколько ближайших рейсов показывать в карточке
// Время в пути Полярные Зори ↔ Кандалакша. В таблице расписания его нет:
// в 12 строках из 13 Кандалакша → ПЗ занимает ровно 50 минут
// (в первой строке 60 — по всей видимости опечатка).
const RIDE_PZ_KAND = 50;
// arrs — список прибытий: {label, col} — время из колонки таблицы,
// {label, offset} — «время в пути» от отправления (если col не задан).
const DIRECTIONS = { const DIRECTIONS = {
// От Кандалакши и Зашейка все рейсы идут через Полярные Зори — одна карточка
"Кандалакша": [ "Кандалакша": [
{to:"Полярные Зори", dep:0, arr:1, via:"через 1 час"}, {to:"Полярные Зори, Зашеек", dep:0, via:"через Полярные Зори",
{to:"Зашеек", dep:0, arr:2, via:"через Полярные Зори"} arrs:[{label:"Полярные Зори", col:1},
{label:"Зашеек", col:2}]}
], ],
"Полярные Зори": [ "Полярные Зори": [
{to:"Зашеек", dep:1, arr:2, via:"≈ 30 мин в пути"}, {to:"Зашеек", dep:1, via:"≈ 30 мин в пути",
{to:"Кандалакша", dep:3, arr:null, via:"≈ 1 час в пути"} arrs:[{label:"Зашеек", col:2}]},
{to:"Кандалакша", dep:3, via:"≈ 50 мин в пути",
arrs:[{label:"Кандалакша", offset:RIDE_PZ_KAND}]}
], ],
"Зашеек": [ "Зашеек": [
{to:"Полярные Зори", dep:2, arr:3, via:"≈ 20 мин в пути"}, {to:"Полярные Зори, Кандалакша", dep:2, via:"через Полярные Зори",
{to:"Кандалакша", dep:2, arr:null, via:"через Полярные Зори"} arrs:[{label:"Полярные Зори", col:3},
{label:"Кандалакша", col:3, offset:RIDE_PZ_KAND}]}
] ]
}; };
const DAYS = ["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"]; const DAYS = ["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"];
@@ -173,6 +236,12 @@ function nowMs(){
second: d.getUTCSeconds(), day: d.getUTCDay() }; second: d.getUTCSeconds(), day: d.getUTCDay() };
} }
function fmtHM(m){
const p = n=>String(n).padStart(2,"0");
m = ((m % 1440) + 1440) % 1440; // на случай перехода через полночь
return `${p(Math.floor(m/60))}:${p(m%60)}`;
}
function fmtWait(m){ function fmtWait(m){
if(m <= 0) return "отправление сейчас"; if(m <= 0) return "отправление сейчас";
const h = Math.floor(m/60), mm = m%60; const h = Math.floor(m/60), mm = m%60;
@@ -183,24 +252,38 @@ function fmtWait(m){
let currentStop = "Полярные Зори"; let currentStop = "Полярные Зори";
// Date#getDay(): 0 — воскресенье, 6 — суббота
function dayKeyFor(dow){ return dow===0 ? "sunday" : dow===6 ? "saturday" : "weekday"; }
function sched(){ return SCHEDULES[activeDay]; }
function render(){ function render(){
const now = nowMs(); const now = nowMs();
const curMin = now.hour*60 + now.minute; const curMin = now.hour*60 + now.minute;
const dirs = DIRECTIONS[currentStop]; const dirs = DIRECTIONS[currentStop];
const data = sched();
document.getElementById("dirBox").innerHTML = dirs.map(d=>{ document.getElementById("dirBox").innerHTML = dirs.map(d=>{
const upcoming = SCHEDULE const upcoming = data
.map((r,ri)=>({dep:toMin(r[d.dep]), arr:d.arr!==null?r[d.arr]:null, ri})) .map((r,ri)=>{
const dep = toMin(r[d.dep]);
const arrs = d.arrs.map(a=>{
const base = a.col!==undefined ? toMin(r[a.col]) : dep;
return {label:a.label, t:fmtHM(base + (a.offset||0))};
});
return {dep, arrs, ri};
})
.filter(x=>x.dep >= curMin) .filter(x=>x.dep >= curMin)
.sort((a,b)=>a.dep-b.dep) .sort((a,b)=>a.dep-b.dep)
.slice(0,3) .slice(0,TRIPS_SHOWN)
.map(x=>{ .map(x=>{
const w = x.dep - curMin; const w = x.dep - curMin;
const arr = x.arr ? `<small>прибытие ${x.arr}</small>` : ""; // .trip .t small имеет display:block, поэтому каждый <small> — своя строка
const arr = x.arrs.map(a=>`<small>прибытие ${a.label} ${a.t}</small>`).join("");
return `<div class="trip ${w<15?'soon':''}"> return `<div class="trip ${w<15?'soon':''}">
<span class="t">${SCHEDULE[x.ri][d.dep]}${arr}</span> <span class="t">${data[x.ri][d.dep]}${arr}</span>
<span class="w">${fmtWait(w)}</span> <span class="w">${fmtWait(w)}</span>
</div>`; </div>`;
}).join("") || `<div class="empty">на сегодня всё — первый завтра в ${SCHEDULE[0][d.dep]}</div>`; }).join("") || `<div class="empty">на сегодня всё — первый завтра в ${data[0][d.dep]}</div>`;
return `<div class="card"> return `<div class="card">
<h3>→ <span class="arr">${d.to}</span></h3> <h3>→ <span class="arr">${d.to}</span></h3>
<div class="via">${d.via}</div> <div class="via">${d.via}</div>
@@ -210,7 +293,7 @@ function render(){
} }
function renderTable(){ function renderTable(){
document.getElementById("tableBody").innerHTML = SCHEDULE.map(row => document.getElementById("tableBody").innerHTML = sched().map(row =>
`<div class="row"><div>${row[0]}</div><div>${row[1]}</div><div>${row[2]}</div><div>${row[3]}</div></div>` `<div class="row"><div>${row[0]}</div><div>${row[1]}</div><div>${row[2]}</div><div>${row[3]}</div></div>`
).join(""); ).join("");
} }
@@ -220,6 +303,15 @@ function renderDay(){
document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`; document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`;
} }
// Подсветка выбранного типа дня и перерисовка расписания
function selectDay(key){
activeDay = key;
document.querySelectorAll("#dayPick button").forEach(b=>
b.classList.toggle("active", b.dataset.day === key));
render();
renderTable();
}
function tickClock(){ function tickClock(){
const d = nowMs(); const d = nowMs();
const p = n=>String(n).padStart(2,"0"); const p = n=>String(n).padStart(2,"0");
@@ -238,6 +330,10 @@ document.querySelectorAll(".pick").forEach(btn=>{
}); });
}); });
document.querySelectorAll("#dayPick button").forEach(btn=>{
btn.addEventListener("click",()=>selectDay(btn.dataset.day));
});
document.getElementById("fullBtn").addEventListener("click",()=>{ document.getElementById("fullBtn").addEventListener("click",()=>{
const t = document.getElementById("fullTable"); const t = document.getElementById("fullTable");
const b = document.getElementById("fullBtn"); const b = document.getElementById("fullBtn");
@@ -246,7 +342,9 @@ document.getElementById("fullBtn").addEventListener("click",()=>{
b.classList.toggle("open", !open); b.classList.toggle("open", !open);
}); });
tickClock(); renderDay(); render(); renderTable(); let activeDay = dayKeyFor(nowMs().day); // по умолчанию — реальный день недели
selectDay(activeDay);
tickClock(); renderDay();
setInterval(tickClock,1000); setInterval(tickClock,1000);
setInterval(render,30000); setInterval(render,30000);
setInterval(renderDay,60000); setInterval(renderDay,60000);