/* То, что принадлежит одному экрану и больше нигде не встречается.
 *
 * Всё, что встретилось дважды, уезжает в `components.css` — иначе через месяц
 * здесь будет три похожие карточки, и ни одну нельзя будет поправить.
 */

/* --- вход ------------------------------------------------------------------ */

/* Макет входа не рисует. Сделан в его языке: та же капслок-подпись, то же поле,
 * то же тёмное главное действие. Центрирован, потому что решать здесь нечего. */
.login {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100dvh;
  padding: var(--s8);
}
.login form {
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  width: 360px;
  padding: var(--s9);
  background: var(--card);
  border: 1px solid var(--edge-card);
  border-radius: var(--r6);
  box-shadow: var(--sh-float);
}

/* --- поездки --------------------------------------------------------------- */

.trips {
  display: flex;
  flex-direction: column;
  gap: var(--s9);
  padding: var(--s10) var(--s10) var(--s10);
}

/* Тёмный блок «прямо сейчас».
 *
 * Отвечает круглый год, а не три недели поездки: во время — «день 6 из 25»
 * и четыре карточки контекста, до — «до поездки 92 дня» и что осталось закрыть.
 * Без второго состояния главный экран большую часть года выглядел бы пустым. */
.now-hero {
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  padding: var(--s9) var(--s9);
  border-radius: var(--r6);
  background: var(--rail);
  color: var(--rail-ink-2);
}
.now-hero .top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s8);
}
.now-hero .caps {
  color: var(--rail-ink-4);
  letter-spacing: 0.18em;
}
.now-hero h1 {
  font-size: 28px;
  color: var(--rail-ink);
}
.now-hero .cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s5);
}
.now-card {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s6) var(--s7);
  border-radius: var(--r4);
  background: var(--rail-card);
  min-width: 0;
}
.now-card .caps {
  color: var(--rail-ink-3);
  letter-spacing: var(--track-caps);
}
.now-card .value {
  font: 600 var(--t-16) / 1.25 var(--sans);
  color: var(--rail-ink);
}
.now-card .sub {
  font: 400 var(--t-13) / 1.4 var(--sans);
  color: var(--rail-ink-3);
}
/* Карточка, которой есть куда вести. Цвета не добавляет — только отклик:
 * блок тёмный, и подсветка фона здесь читается лучше рамки. */
.now-card.is-link {
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.now-card.is-link:hover {
  background: var(--rail-up);
}
.now-card.is-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Карточка со сроком — единственная янтарная: она про «решить до», а не про
 * «где я». Выдели так две — и не выделена ни одна. */
.now-card.is-warn .caps {
  color: var(--warn-ink);
}

/* Кнопки внутри тёмного блока обведены его же линией. */
.now-hero .btn.is-outline {
  height: auto;
  padding: var(--s5) var(--s7);
  border-color: var(--rail-line-2);
  background: transparent;
  color: var(--rail-ink-2);
  font-size: var(--t-14);
}
.now-hero .btn.is-outline:hover {
  background: var(--rail-up);
  color: var(--rail-ink);
}
.now-hero .btn.is-accent {
  padding: var(--s5) var(--s7);
  font-size: var(--t-14);
}

.trips-count {
  font-size: var(--t-13);
  color: var(--ink-4);
}

.trip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(430px, 1fr));
  gap: var(--s6);
}

.trip-card {
  display: flex;
  gap: var(--s7);
  padding: var(--s8);
  min-width: 0;
}
.trip-card .cover {
  width: 104px;
  height: 104px;
  flex: none;
  border-radius: var(--r4);
  overflow: hidden;
  background: var(--fill);
}
.trip-card .cover img,
.trip-card .cover svg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.trip-card .card-title {
  font: 600 var(--t-19) / 1.2 var(--sans);
}
.trip-card .when {
  font: 400 var(--t-13) / 1.3 var(--mono);
  color: var(--ink-3);
}
.trip-card .sub {
  margin-top: auto;
  font-size: var(--t-14);
  color: var(--ink-4);
}
.trip-card .cover-empty {
  display: flex;
  align-items: flex-end;
  height: 100%;
  padding: var(--s3);
  font: 500 var(--t-11) / 1.3 var(--sans);
  color: var(--card);
  text-shadow: 0 1px 2px rgba(23, 24, 26, 0.5);
}

/* --- рабочий экран: список слева, карта справа ----------------------------- */

.workspace {
  display: grid;
  flex: 1;
  min-height: 0;
  min-width: 0;
}
.workspace.is-split {
  grid-template-columns: 452px minmax(0, 1fr);
}
.workspace.is-split-wide {
  grid-template-columns: 556px minmax(0, 1fr);
}
.workspace.is-one {
  grid-template-columns: minmax(0, 1fr);
}

.side {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--card);
  border-right: 1px solid var(--edge);
}
.side-head {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  flex: none;
  padding: var(--s8) var(--s8) var(--s7);
  border-bottom: 1px solid var(--line);
}
.side-scroll {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--s5) var(--s5);
}
.side-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s4);
  flex: none;
  padding: var(--s6) var(--s8) var(--s4);
}
/* Подпись — строкой над фильтрами, как в макете. В одной строке с ними
 * ей оставалось несколько символов ширины, и «Места этапа · 2» ломалось
 * в три строки: капслок с трекингом переносится особенно грязно. */
.side-bar > .caps {
  flex: 1 0 100%;
}
/* Колонка этапа прокручивается целиком, а не одним списком мест. У склеенного
 * этапа над списком семь карточек — ночёвки и переезды, разрезанные по точкам
 * по пути, — и шапка в 1100 px сжимала список до 12 px: мест не было видно
 * вовсе, а прокручивать было нечего. Фильтры мест прилипают к верху, чтобы
 * не уезжать вместе с шапкой. */
.side.is-flowing {
  overflow-y: auto;
}
.side.is-flowing > .side-scroll {
  flex: none;
  overflow-y: visible;
}
.side.is-flowing > .side-bar:not(.stage-notes) {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--card);
}

/* --- якоря ---------------------------------------------------------------- */

/* Фаза города рядом с названием: здесь и сейчас, впереди, позади.
 *
 * Не бейдж и не чип: у них форма кнопки, а это не нажимается. Строчка
 * моноширинной в один уровень с заголовком — читается как подпись к нему,
 * а не как элемент управления. */
.phase {
  font: 500 var(--t-12) / 1 var(--mono);
  letter-spacing: var(--track-caps);
  color: var(--ink-4);
  white-space: nowrap;
}
.phase.is-now {
  color: var(--accent);
}
.phase.is-past {
  color: var(--faint);
}

/* --- дни города ------------------------------------------------------------
 * Колонка экрана города днями вместо цепочки якорей: дорога одной строкой,
 * заезд и выезд, места на свой день. Строки дня — те же, что у недели
 * «Всей поездки» (.day-line), дата — тем же столбцом (.week-date). */
.city-days {
  display: flex;
  flex-direction: column;
  padding: var(--s6) var(--s8) var(--s5);
  border-bottom: 1px solid var(--line);
}
.city-days > .caps {
  padding-bottom: var(--s3);
}
.city-day {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  column-gap: var(--s4);
  padding: var(--s4) var(--s3);
  margin-inline: calc(-1 * var(--s3));
}
.city-day + .city-day {
  border-top: 1px solid var(--line);
}
/* Сегодня — заливкой, прошедшее — бледнее, но на месте: «что мы тут уже
 * видели» тоже вопрос. Граница у сегодняшнего убирается с обеих сторон,
 * иначе полоса заливки перечёркнута линией. */
.city-day.is-today {
  border-radius: var(--r2);
  border-top-color: transparent;
  background: var(--accent-soft);
}
.city-day.is-today + .city-day {
  border-top-color: transparent;
}
.city-day.is-past {
  opacity: 0.5;
}
.day-mark {
  display: block;
  font: 600 var(--t-10) / 1.3 var(--mono);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--accent);
}
.stage-top {
  display: flex;
  align-items: center;
  gap: var(--s4);
}
/* Название сжимается многоточием, кнопки — никогда: без них с экрана не уйти. */
.stage-top > .page-title {
  flex: 1;
  min-width: 0;
}
/* День у места в списке — нейтральной меткой: синий занят «сейчас» и выбором. */
.place-day {
  flex: none;
  font: 500 var(--t-11) / 1 var(--mono);
  color: var(--ink-3);
  background: var(--fill);
  padding: 3px 7px;
  border-radius: var(--r-pill);
  white-space: nowrap;
}

/* --- место в списке ------------------------------------------------------ */

.place-row {
  display: flex;
  align-items: flex-start;
  gap: var(--s5);
  padding: var(--s5) var(--s4);
  border-radius: var(--r3);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.place-row:hover {
  background: var(--fill);
}
.place-row.is-on {
  background: var(--accent-soft);
}
.place-name {
  font: 500 var(--t-15) / 1.2 var(--sans);
}
.place-kind {
  flex: none;
  font: 600 var(--t-11) / 1 var(--mono);
  letter-spacing: 0.12em;
  color: var(--ink-5);
  text-transform: uppercase;
}
.place-meta {
  font: 400 var(--t-13) / 1.3 var(--sans);
  color: var(--ink-4);
}
.place-row.is-done .place-name {
  color: var(--ink-5);
  text-decoration: line-through;
}
/* Идеи идут в той же прокрутке под местами, отделённые подписью, а не рамкой:
 * это тот же пул города, только ещё не взятый в маршрут. */
.idea-head {
  display: block;
  padding: var(--s7) var(--s4) var(--s3);
}
.place-row.is-idea .place-name {
  color: var(--ink-2);
}
.place-row.is-idea .btn {
  flex: none;
  align-self: center;
}

/* --- карточка выбранного ------------------------------------------------- */

.detail-title {
  font: 600 var(--t-17) / 1.2 var(--sans);
}
/* Локальное название крупно: его показывают таксисту, а не читают. */
.detail-local {
  font: 500 var(--t-16) / 1.35 var(--sans);
  color: var(--ink-2);
}
.detail-note {
  margin: 0;
  font-size: var(--t-14);
  line-height: 1.45;
  color: var(--ink-2);
}
.detail-line {
  font-size: var(--t-13);
  line-height: 1.35;
  color: var(--ink-4);
}
.detail-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}
.detail-value {
  font: 500 var(--t-14) / 1.3 var(--sans);
}

.file-row {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s5) var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  background: var(--sunken);
  color: inherit;
  font-size: var(--t-14);
}
.file-row:hover {
  border-color: var(--accent-border);
  color: inherit;
}
.file-mark {
  width: 30px;
  height: 38px;
  flex: none;
  border-radius: var(--r1);
  background: var(--edge);
}
.when {
  color: var(--ink-4);
}

/* --- «Вся поездка»: где я сейчас и что дальше ----------------------------- */

/* Подробная карточка города: шапка, ночёвка с обложкой, места. Две таких
 * на колонку — «сейчас» и «дальше», — поэтому карточка не растягивается
 * по высоте: второй блок обязан быть виден без прокрутки хотя бы шапкой. */
.brief {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r4);
  background: var(--card);
  overflow: hidden;
}
/* Следующий город приглушён рамкой, а не содержимым: он про «потом»,
 * и одинаковый вес двух карточек стирает разницу между «здесь» и «туда». */
.brief.is-next {
  background: var(--sunken);
}
.brief-head {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s6) var(--s7);
  text-decoration: none;
  color: inherit;
  transition: background var(--dur-fast) var(--ease);
}
.brief-head:hover {
  background: var(--fill);
}
.brief-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.brief-city {
  font: 600 var(--t-19) / 1.15 var(--sans);
}
.brief-go {
  flex: none;
  color: var(--ink-4);
  font-size: var(--t-16);
}
.brief-empty {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s6) var(--s7);
  border: 1px dashed var(--line);
  border-radius: var(--r4);
  color: var(--ink-4);
  font-size: var(--t-14);
}
.brief .brief-empty {
  border: 0;
  color: var(--alarm);
}
/* Вторая и последующие ночёвки города — строкой без обложки: они отвечают
 * на тот же вопрос, а высоту отбирают у того, что ниже. */
.brief-alt {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  width: 100%;
  padding: var(--s3) var(--s7);
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  font-size: var(--t-14);
}
.brief-alt:hover {
  background: var(--fill);
}
.brief-alt.is-on {
  background: var(--accent-tint);
}
.brief-spots {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s5) var(--s7) var(--s6);
  border-top: 1px solid var(--line);
}
/* Сегодняшний день в карточке «Сейчас» — теми же строками, что неделя ниже. */
.brief-today {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s5) var(--s7) var(--s6);
  border-top: 1px solid var(--line);
}

/* --- «Дальше»: неделя по дням, по городам ---------------------------------- */

.week {
  display: flex;
  flex-direction: column;
}
.week-city {
  display: flex;
  flex-direction: column;
  gap: var(--hair);
  padding: var(--s5) 0 var(--s4);
  border-top: 1px solid var(--line);
}
.week-city:first-child {
  padding-top: 0;
  border-top: 0;
}
/* Заголовок города — ссылка в город, как строка рельсы: номер тот же, что
 * на карте, ночи — тем же знаком месяца. */
.week-head {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s2) 0 var(--s3);
  color: inherit;
  text-decoration: none;
  border-radius: var(--r2);
}
.week-head:hover .week-city-name {
  color: var(--accent);
}
.week-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.week-head .brief-go {
  margin-left: auto;
}
.num.is-small {
  width: 22px;
  height: 22px;
  font-size: var(--t-12);
  border-radius: var(--r1);
}
.week-city-name {
  font: 600 var(--t-15) / 1.2 var(--sans);
  transition: color var(--dur-fast) var(--ease);
}
.week-head .when {
  font: 400 var(--t-12) / 1 var(--mono);
  white-space: nowrap;
}
/* День: дата столбцом слева, строки дня справа. Отступ под номер города —
 * день читается как часть своего города, а не соседнего. */
.week-day {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  column-gap: var(--s3);
  padding: var(--s2) 0 var(--s2) var(--s9);
}
.week-date {
  font: 500 var(--t-12) / 20px var(--mono);
  color: var(--ink-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* Выходные — цветом: по ним планируют, места с толпами уводят на будни. */
.week-date.is-weekend {
  color: var(--alarm);
}
.week-free {
  font-size: var(--t-13);
  line-height: 20px;
  color: var(--ink-5);
}

/* День лентой: время столбцом, значок на линии, текст справа. Линия идёт
 * через все шаги дня — выезд, дорога, остановки и заезд читаются одним
 * куском, а не соседними строками одного веса. */
.day-lines {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  min-width: 0;
}
.day-tape {
  display: flex;
  flex-direction: column;
}
.tape-step {
  display: grid;
  grid-template-columns: 40px 20px minmax(0, 1fr);
  column-gap: var(--s3);
}
.tape-time {
  font: 600 var(--t-12) / 20px var(--mono);
  color: var(--ink-2);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Узел — значок в кружке фона поверх линии; линия рисуется ячейкой целиком,
 * поэтому шаги стоят вплотную, а воздух между ними — отступом текста. */
.tape-node {
  position: relative;
  display: flex;
  justify-content: center;
  color: var(--ink-3);
}
.tape-node::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(50% - 1px);
  width: 2px;
  background: var(--edge-dash);
}
.tape-step:first-child .tape-node::before {
  top: 10px;
}
.tape-step:last-child .tape-node::before {
  bottom: calc(100% - 10px);
}
.tape-step:only-child .tape-node::before {
  display: none;
}
.tape-node > svg {
  position: relative;
  box-sizing: content-box;
  padding: 3px 0;
  background: var(--card);
}
.tape-node.is-alarm {
  color: var(--alarm);
}
.tape-step.is-stop .tape-node {
  color: var(--ink);
}
.tape-body {
  min-width: 0;
  padding-bottom: var(--s3);
}
.tape-step:last-child .tape-body {
  padding-bottom: 0;
}
.day-line {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: 400 var(--t-14) / 20px var(--sans);
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}
.day-line b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.day-line .when {
  color: var(--ink-4);
}
button.day-line:hover,
.day-idea:hover,
a.day-note:hover {
  color: var(--accent);
}
.day-line:focus-visible,
.day-idea:focus-visible,
.day-note:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Остановка дороги — весом: это то, ради чего день дороги. */
.day-place.is-stop .day-place-name {
  font-weight: 500;
}
/* Заезд по пути — место другого города в этот день: бледнее своих. */
.day-place.is-enroute {
  color: var(--ink-3);
}
.day-place.is-done .day-place-name {
  text-decoration: line-through;
  color: var(--ink-4);
}
.day-sub {
  display: block;
  font-size: var(--t-12);
  line-height: 16px;
  color: var(--ink-4);
}
.day-line.is-missing {
  cursor: default;
  color: var(--alarm);
}

/* Заметки дня — плашками над лентой: обстановка дня, а не шаг в нём. */
.day-notes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}
.day-note {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  max-width: 100%;
  padding: var(--s1) var(--s3);
  border-radius: var(--r1);
  background: var(--line-soft);
  font: 400 var(--t-12) / 16px var(--sans);
  color: var(--ink-2);
  text-decoration: none;
}
.day-note .when {
  color: var(--ink-4);
}
.day-note > svg {
  flex: none;
  color: var(--ink-4);
}
.day-note.is-season {
  background: var(--warn-bg);
  color: var(--warn-ink);
}
.day-note.is-season > svg {
  color: var(--warn-ink);
}
.day-note.is-closed {
  background: var(--alarm-bg);
  color: var(--alarm-deep);
}
.day-note.is-closed > svg {
  color: var(--alarm);
}

/* Идеи дня — одной серой строкой под лентой: каждое имя — своя кнопка
 * в карточку места, между ними точка, а не запятая. */
.day-ideas {
  font: 400 var(--t-13) / 20px var(--sans);
  color: var(--ink-4);
}
.day-ideas-label {
  margin-right: var(--s2);
  font: 600 var(--t-11) / 20px var(--mono);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
}
.day-ideas-dot {
  margin: 0 var(--s2);
  color: var(--ink-5);
}
.day-idea {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-3);
  cursor: pointer;
}

/* Ночёвка карточкой с обложкой: город узнают по виду отеля быстрее, чем
 * по названию. Фото — во всю ширину колонки, метки поверх него по углам. */
.stay {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
}
.stay.is-on {
  background: var(--accent-tint);
}
.stay-cover {
  position: relative;
  cursor: pointer;
}
.stay-cover .photo {
  height: 168px;
  border-radius: 0;
}
.stay-kind,
.stay-when {
  position: absolute;
  top: var(--s4);
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.92);
  font: 600 var(--t-11) / 1 var(--mono);
  letter-spacing: 0.1em;
  color: var(--ink-2);
  text-transform: uppercase;
}
.stay-kind {
  left: var(--s4);
}
/* «Выезд завтра» — справа и в акценте: это единственное на карточке, что
 * зависит от сегодняшнего дня, и читают её ради него. */
.stay-when {
  right: var(--s4);
  color: var(--accent);
}
.stay-when.is-live {
  background: var(--accent);
  color: #fff;
}
.stay-body {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s6) var(--s7);
}
.stay-name {
  font: 600 var(--t-16) / 1.25 var(--sans);
}
.stay-line {
  font-size: var(--t-13);
  color: var(--ink-4);
  line-height: 1.4;
}
.stay-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  font-size: var(--t-13);
  color: var(--ink-3);
}
.stay-facts .is-warn {
  color: var(--warn-ink);
}

/* Место города чипом: их считают глазами, а ходят по ним с экрана города.
 * Отмеченное гаснет, идея остаётся пунктирной — как на всех прочих экранах. */
.chip.is-done {
  color: var(--ink-5);
  text-decoration: line-through;
}
.chip.is-idea {
  border-style: dashed;
  color: var(--ink-4);
  text-decoration: none;
}
.chip.is-on {
  border-color: var(--accent);
  color: var(--accent);
}

/* --- маршрут и даты -------------------------------------------------------- */

/* Три панели поверх одной карты, и места им хватает только по уговору: слева
 * карточка городов, снизу календарь, справа правка выбранного. Их размеры
 * названы здесь числами, потому что от них считают положение все остальные —
 * подпись карты, кнопки масштаба, предел высоты правки и поля кадра карты:
 * `Plan.js` читает эти же переменные, а не держит свои копии. */
.plan {
  --plan-card-w: 392px;
  --plan-editor-w: 420px;
  --cal-h: 186px;
}
/* Свёрнутый календарь — одна шапка. Высота меняется здесь, в одном месте,
 * потому что по ней считают себя карточка городов и панель правки. */
.plan.is-shut {
  --cal-h: 52px;
}

/* Плавающая карточка поверх карты, от верха до календаря: правят форму поездки,
 * а карта показывает, что из этого получилось. */
/* Атрибуция уходит из-под карточки городов и календаря: слева снизу их обоих,
 * свободен только верх полосы карты между ними. */
.plan .map-credit {
  left: calc(var(--s9) + var(--plan-card-w) + var(--s5));
  top: var(--s5);
  bottom: auto;
}

.plan-card {
  position: absolute;
  left: var(--s9);
  top: var(--s9);
  bottom: calc(var(--s9) + var(--cal-h) + var(--s5));
  /* Едет вместе с календарём: обе высоты считаются от `--cal-h`, и без этого
     карточка прыгала бы на своё место, пока календарь ещё складывается. */
  transition: bottom var(--dur) var(--ease);
  width: var(--plan-card-w);
  display: flex;
  flex-direction: column;
  border-radius: var(--r6);
  background: var(--card);
  box-shadow: var(--sh-panel);
  overflow: hidden;
}
.plan-head {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  flex: none;
  padding: var(--s8) var(--s8) var(--s7);
  border-bottom: 1px solid var(--line);
}
/* Название правится на месте: подчёркивание вместо рамки говорит «это текст,
 * который можно поменять», а не «это поле формы». */
.plan-name {
  width: 100%;
  padding: var(--s1) 0;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  font: 600 var(--t-22) / 1.1 var(--sans);
  letter-spacing: var(--track-tight);
  outline: none;
}
.plan-name:focus {
  border-bottom-color: var(--accent);
}

/* Строка-сводка: этапы · даты · ночи. Даты в ней правятся кликом, поэтому
 * набраны тем же текстом, но с пунктиром — «это можно поменять», а не
 * «это поле формы». Тот же приём, что у названия выше. */
.plan-when {
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.plan-when-btn {
  padding: 0;
  border: 0;
  border-bottom: 1px dashed var(--edge-dash);
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.plan-when-btn:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
/* Страны правятся строкой на том же месте, где написаны. Поле узкое
 * намеренно: стран у поездки одна-две, а широкое поле в строке-сводке
 * отодвигало бы даты и ночи. */
.plan-countries {
  width: 14ch;
  padding: 0 var(--s1);
  border: 1px solid var(--edge);
  border-radius: var(--r1);
  background: var(--field);
  font: inherit;
  color: var(--ink);
}
.plan-countries:focus {
  outline: none;
  border-color: var(--accent);
}
/* Поля дат мелкие и моноширинные: они встают на место написанных чисел,
 * и строка не должна из-за правки менять высоту. */
.plan-dates {
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.plan-dates input {
  padding: var(--hair) var(--s2);
  border: 1px solid var(--edge);
  border-radius: var(--r1);
  background: var(--field);
  font: 400 var(--t-13) / 1.2 var(--mono);
  color: var(--ink-2);
  transition: border-color var(--dur-fast) var(--ease);
}
.plan-dates input:hover {
  border-color: var(--faint);
}
.plan-dates input:focus {
  border-color: var(--accent);
  outline: none;
}

.plan-list {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--s7) var(--s7);
}
.plan-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--s5);
  padding: var(--s6) var(--s6);
  border: 1px solid var(--edge-card);
  border-radius: var(--r4);
  background: var(--card);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease),
    opacity var(--dur-fast) var(--ease);
}
.plan-row:hover {
  border-color: var(--accent-border);
}
.plan-row.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
}
/* Перетаскивание видно тремя вещами: курсор говорит о возможности, тень
 * отрывает строку от списка, полоса показывает, куда она ляжет. Прежде была
 * только приглушённая строка — «что-то тащится», а куда, приходилось угадывать. */
.plan-row.is-over::before,
.plan-row.is-under::after {
  content: "";
  position: absolute;
  left: var(--s5);
  right: var(--s5);
  height: 2px;
  border-radius: var(--r-pill);
  background: var(--accent);
}
.plan-row.is-over::before {
  top: -1px;
}
.plan-row.is-under::after {
  bottom: -1px;
}
.plan-row.is-dragging {
  opacity: 0.4;
}
.plan-city {
  font: 600 var(--t-16) / 1.2 var(--sans);
}
.plan-stays {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  font: 400 var(--t-13) / 1.3 var(--sans);
  color: var(--ink-4);
}
.plan-stays {
  align-items: flex-start;
}
.plan-stays svg {
  flex: none;
  transform: translateY(2px);
}
/* Название отеля переносится на вторую строку, но не на третью: у продавца
 * они длиннее любой панели, а обрезка съедает ровно то, чем один отель
 * отличается от другого. */
.plan-stay-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.plan-stays.is-missing {
  color: var(--alarm);
}
/* Подпись и две кнопки над списком: втроём в одну строку они жались,
 * и «Ставить на карте» обрезалось до «Ставить на ка…». */
.plan-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  padding: var(--s6) var(--s8) var(--s4);
}

/* Ручка перетаскивания. Курсор говорит о возможности раньше, чем подпись. */
.plan-grip {
  flex: none;
  color: var(--faint);
  font-size: var(--t-14);
  cursor: grab;
}

.plan-form {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  margin: 0 var(--s7) var(--s5);
  padding: var(--s6);
  border: 1px solid var(--edge);
  border-radius: var(--r4);
  background: var(--sunken);
}

.plan-foot {
  margin-top: auto;
  display: flex;
  gap: var(--s3);
  flex: none;
  padding: var(--s6) var(--s8);
  border-top: 1px solid var(--line);
}

/* Панель плана шире прочих панелей над картой: остальные показывают одну
 * выбранную запись, а эта — весь маршрут, и в ней стоят названия отелей.
 * На 320 px они обрезались до «Vienna I…», то есть до неразличимости.
 *
 * Поля здесь общие для всех трёх панелей экрана, а не собственные `.map-detail`:
 * панели стоят рядом, и разные отступы у соседей видно сразу. Высота считается
 * от календаря — панель длинная и легла бы на него. */
.plan-editor {
  top: var(--s9);
  right: var(--s9);
  width: var(--plan-editor-w);
  max-height: calc(100% - var(--s9) * 2 - var(--cal-h) - var(--s5));
}

/* Кнопки масштаба уходят влево от панели правки: она стоит в том же углу
 * и накрывала их целиком — с открытым городом карту нельзя было приблизить. */
.plan .map:has(.plan-editor) .map-zoom {
  right: calc(var(--s9) + var(--plan-editor-w) + var(--s5));
}

/* --- правка этапа: места ---------------------------------------------------- */

/* Та же раскладка, что у «Маршрута и дат», но без календаря: карточка мест
 * и форма тянутся до низа карты, а не до полосы, которой здесь нет. */
.stage-edit {
  --cal-h: 0px;
}
/* Заголовок списка — цель броска: в пустой план и в свёрнутое «Отложено»
 * бросить больше некуда. Подсветка та же, что у выбранной строки. */
.edit-head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  /* Высота кнопки: у «Отложено» в заголовке стоит «показать», и без неё
     заголовки двух списков разной высоты. */
  min-height: var(--control);
  margin-top: var(--s5);
  padding: var(--s2) var(--s4);
  border: 1px dashed transparent;
  border-radius: var(--r4);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.edit-head:first-child {
  margin-top: 0;
}
.edit-head.is-target {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.edit-empty {
  padding: var(--s4);
  font: 400 var(--t-13) / 1.3 var(--sans);
  color: var(--ink-4);
}
.place-drag {
  align-items: center;
}
.place-drag .btn {
  flex: none;
}
.place-drag.is-done .place-name {
  color: var(--ink-5);
  text-decoration: line-through;
}

/* --- календарь этапов ------------------------------------------------------ */

/* Полоса снизу во всю ширину. Ось у календаря горизонтальная: в колонке 392 px
 * день выходил бы в девять пикселей, и тянуть мышкой было бы нечего.
 *
 * Высота задана числом, а не содержимым. Панель правки справа считает по ней
 * свой предел, и календарь, растущий от числа дорожек, двигал бы её под рукой —
 * ровно в тот момент, когда наложение и надо разглядеть. */
.cal {
  position: absolute;
  left: var(--s9);
  right: var(--s9);
  bottom: var(--s9);
  height: var(--cal-h);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: var(--s5) var(--s6) var(--s6);
  border-radius: var(--r6);
  background: var(--card);
  box-shadow: var(--sh-panel-up);
  /* Сворачивается и разворачивается ростом, а не подменой: высота приходит
     из `--cal-h`, по ней же считают себя карточка городов и низ карты, и все
     трое едут одним движением. Мгновенная подмена читалась сменой экрана. */
  transition: height var(--dur) var(--ease);
}
.cal.is-dragging {
  cursor: grabbing;
}
/* Свёрнутый календарь — та же панель ростом в одну шапку: место отдаётся карте,
 * а тумблер и длина поездки остаются там, где их оставили. Собственной высоты
 * у свёрнутого нет намеренно: она приходит из `--cal-h`, по которой считают
 * себя соседние панели, — два независимых числа разошлись бы.
 *
 * Пока над календарём держат город из списка, он подаётся вперёд — иначе
 * непонятно, принимает ли он вообще. */
.cal.is-taking {
  outline: 1px solid var(--accent-border);
}

.cal-head {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex: none;
  min-width: 0;
}
/* Подсказка о жестах: их два и оба невидимы, пока не попробуешь. Гасится
 * первой, когда строка не влезает, — она нужна один раз. */
.cal-hint {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--ink-4);
  font-size: var(--t-12);
}
/* Под рукой подсказка говорит, чем кончится начатое, и говорит это цветом
 * тревоги: даты сотрутся, а стёртое молча не восстановить. */
.cal.is-off .cal-hint {
  color: var(--alarm);
}

.cal-scroll {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: auto;
}
.cal-grid {
  position: relative;
  display: flex;
  flex: none;
  flex-direction: column;
  min-width: 100%;
}

/* Месяц подписан там, где начинается: два десятка чисел подряд не говорят,
 * апрель это или май. */
.cal-months {
  position: relative;
  flex: none;
  height: 15px;
}
.cal-month {
  position: absolute;
  top: 0;
  padding-left: var(--s2);
  border-left: 1px solid var(--edge);
  font: 500 var(--t-11) / 1.3 var(--mono);
  color: var(--ink-3);
  white-space: nowrap;
}
.cal-days {
  display: flex;
  flex: none;
  border-bottom: 1px solid var(--line);
}
.cal-day {
  flex: none;
  padding: var(--s1) 0;
  text-align: center;
  font: 400 var(--t-11) / 1.3 var(--mono);
  color: var(--ink-4);
}
.cal-day.is-week {
  background: var(--sunken);
  color: var(--ink-3);
}
.cal-day.is-out {
  color: var(--faint);
}
.cal-day.is-today {
  color: var(--accent);
  font-weight: 600;
}

.cal-lanes {
  position: relative;
  flex: 1;
  margin-top: var(--s3);
}
/* Сетка дней — фоном, а не колонками: полос по одной на день вышло бы под
 * полсотни, и каждая перерисовывалась бы на каждом шаге перетаскивания.
 * Отдельным слоем поверх приглушения: под ним она пропадала ровно там,
 * где по ней и работают. */
.cal-lanes::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(to right, var(--line-soft) 0 1px, transparent 1px var(--day));
}
/* Дни вне поездки приглушены — гасится край, а не заливается середина:
 * город, уехавший за даты, встаёт на серое поле и виден без подсказок. */
.cal-out {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--sunken);
}
.cal-now {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--accent-border);
}
.cal-now::before {
  content: "";
  position: absolute;
  top: -2px;
  left: -2px;
  width: 5px;
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--accent);
}

/* Полоса города — тот же материал, что у точки на карте и номера в списке:
 * одна сущность названа одинаково во всех трёх видах. */
.cal-bar {
  position: absolute;
  z-index: 1;
  height: 20px;
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 0 var(--s3);
  border: 0;
  border-radius: var(--r1);
  background: var(--ink);
  color: var(--card);
  cursor: grab;
  overflow: hidden;
  touch-action: none;
  user-select: none;
  transition: left var(--dur-fast) var(--ease), width var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
/* Под рукой полоса идёт за курсором без сглаживания: плавность в 120 мс
 * на перетаскивании читается как задержка, а не как мягкость. */
.cal.is-dragging .cal-bar {
  transition: background var(--dur-fast) var(--ease);
}
.cal-bar:hover {
  background: var(--accent);
}
.cal-bar.is-on {
  background: var(--accent);
  box-shadow: var(--sh-float);
}
/* Полоса в руке названа тем же акцентом, что наведение и выбор: под захватом
 * указателя наведение теряется, и без цвета из трёх полос не видно, какая
 * из них едет. Тень отрывает её от дорожки. */
.cal-bar.is-held {
  z-index: 2;
  cursor: grabbing;
  background: var(--accent);
  box-shadow: var(--sh-float);
}
/* Город вне дат поездки: не ошибка, а вопрос — так же, как «ночёвка
 * не выбрана», он назван цветом тревоги и ничего не запрещает. */
.cal-bar.is-out {
  box-shadow: inset 0 0 0 1.5px var(--alarm);
}
/* Открытый город («пока не решил»): полоса идёт до соседа, но этот конец
 * выведен, а не выбран — поэтому правый край тает, а не обрезан. */
.cal-bar.is-open {
  border-radius: var(--r1) 0 0 var(--r1);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--s8)), rgb(0 0 0 / 0.3));
}

/* Полосу унесли за край: отпустишь — даты сотрутся, город вернётся в список
 * недатированным. Показано до отпускания, а не после: стёртое молча
 * не восстановить. */
.cal-bar.is-off {
  background: var(--card);
  color: var(--ink-3);
  box-shadow: inset 0 0 0 1px var(--edge-dash), var(--sh-row);
  opacity: 0.75;
}

/* Город, который тащат из списка: пунктирный след на тех числах, куда он
 * ляжет. Пунктир здесь значит то же, что у идей, — «ещё не решено». */
.cal-ghost {
  position: absolute;
  z-index: 2;
  height: 20px;
  border: 1px dashed var(--accent);
  border-radius: var(--r1);
  background: var(--accent-tint);
  pointer-events: none;
}
.cal-ghost-when {
  position: absolute;
  top: 50%;
  left: calc(100% + var(--s2));
  transform: translateY(-50%);
  color: var(--accent);
  font: 500 var(--t-11) / 1 var(--mono);
  white-space: nowrap;
}
/* У правого края подпись уходит влево — иначе она вылезает за календарь
 * и обрезается ровно тогда, когда город кладут в конец поездки. */
.cal-ghost.is-flipped .cal-ghost-when {
  left: auto;
  right: calc(100% + var(--s2));
}
/* Узкой полосе поля тоньше: на две ночи восемь пикселей с каждой стороны
 * не пускали название целиком. Ширину подписи под эти поля считает
 * `StageCalendar.js` (`labelOf`), читая тот же токен. */
.cal-bar.is-narrow {
  padding: 0 var(--s1);
}
/* Не влезло ни одно название — остаётся номер, и он ставится по центру, иначе
 * полоса читается чёрным прямоугольником. */
.cal-bar.is-tight {
  padding: 0 var(--s1);
  justify-content: center;
}
.cal-name {
  font: 500 var(--t-12) / 1 var(--sans);
}
.cal-nights {
  flex: none;
  margin-left: auto;
  font-size: var(--t-11);
  opacity: 0.6;
}
/* Края тянут даты по одной. Проявляются при наведении: постоянные ручки
 * на полосе в два дня заняли бы её целиком. */
.cal-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 8px;
  cursor: ew-resize;
}
.cal-edge.is-end {
  left: auto;
  right: 0;
}
.cal-bar:hover .cal-edge::after {
  content: "";
  position: absolute;
  top: 5px;
  bottom: 5px;
  left: 3px;
  width: 2px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.55);
}
.cal-bar:hover .cal-edge.is-end::after {
  left: auto;
  right: 3px;
}

.cal-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 400 var(--t-13) / 1.4 var(--sans);
  color: var(--ink-4);
}

/* --- таблица броней -------------------------------------------------------- */

/* Колонки списка броней названы один раз на самом экране: значок, название,
 * номер брони, цена, состояние, файлы. Шапка и строки обязаны стоять по одной
 * сетке, а две копии `grid-template-columns` разъезжаются при первой правке. */
.bookings {
  /* Ширины из макета. Цена мерена по «2 × 177,30 $» (за штуку с количеством),
   * состояние — по «не оплачено»: ни то, ни другое не переносится, значит
   * колонка обязана вмещать самое длинное, что в ней бывает. С капсулой
   * на Plex Sans слово занимает меньше, чем занимала моноширинная плашка,
   * и колонки вернулись к макетным 80 и 100. */
  /* Колонка номера считана по самому длинному: «1539 3674 6007 9527»
     у Trip.com — шестнадцать цифр группами, в моно 13 px это ~150. */
  --table-cols: 30px minmax(0, 1fr) 152px 80px 100px 30px;
}
.table-head,
.table-row {
  display: grid;
  grid-template-columns: var(--table-cols);
  gap: var(--s4);
  padding: var(--s5) var(--s7);
}
.table-head {
  padding-left: var(--s7);
}
.table-head {
  flex: none;
  background: var(--field);
  border-bottom: 1px solid var(--line);
}
/* Заголовок города над его бронями. Липкий: в списке на четыре десятка строк
 * город обязан оставаться виден, иначе группировка помогает только сверху.
 * Фон непрозрачный — под липкой строкой проезжают чужие. */
.table-group {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: var(--s4);
  padding: var(--s4) var(--s9);
  background: var(--sunken);
  border-bottom: 1px solid var(--line-soft);
}
/* Карточкой, а не полосой с нижней линией: так в макете и так уже сделано
 * на «Всех местах» — два списка поездки обязаны читаться одним приёмом.
 * Поля 11×12 и радиус 10 — оттуда же. */
.table-row {
  align-items: center;
  margin: 0 var(--s5) var(--s1);
  padding: 11px var(--s5);
  border: 1px solid var(--edge-card);
  border-radius: var(--r3);
  background: var(--card);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.table-row:hover {
  background: var(--sunken);
  border-color: var(--accent-border);
}
.table-row.is-on {
  background: var(--accent-soft);
  border-color: var(--accent-border);
}
/* Метка обнимает слово, а не колонку: элемент сетки по умолчанию растягивается
 * на всю её ширину, и «ОК» превращалось в цветную полосу на 118 px. Прижата
 * вправо — состояние стоит последним столбцом, и левый край у него рваный:
 * «оплачено» и «нет брони» разной длины. */
.table-row .badge {
  justify-self: end;
}

/* Значок вида — залитым квадратом, как в макете: в строке он опознаётся раньше
 * названия, и подпись словом («ОТЕЛЬ», «ПОЕЗД») ради этого больше не нужна —
 * вид переехал к названию капслоком, а место освободилось под номер брони. */
.row-mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r2);
  background: var(--fill);
  color: var(--ink-3);
}
.table-row.is-on .row-mark {
  background: var(--accent);
  color: var(--card);
}
/* Номер брони — моноширинным: его сверяют посимвольно, на стойке и в письме. */
.row-ref {
  font: 500 var(--t-12) / 1 var(--mono);
  color: var(--ink-2);
}
.row-ref.is-empty {
  color: var(--ink-5);
}
.row-price {
  text-align: right;
  white-space: nowrap;
  font: 500 var(--t-13) / 1 var(--mono);
  color: var(--ink);
}
/* Скрепка с числом: сколько файлов лежит у брони. Ноль приглушён, но не скрыт —
 * «файлов нет» и «я не посмотрел» это разные ответы. */
.row-docs {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s1);
  font: 400 var(--t-11) / 1 var(--mono);
  color: var(--ink-4);
}
.row-docs.is-empty {
  color: var(--faint);
}
.row-type {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font: 600 var(--t-12) / 1 var(--mono);
  letter-spacing: 0.1em;
  color: var(--ink-4);
  text-transform: uppercase;
}
/* Итог тихой строкой, а не отдельным экраном денег: фильтр его пересчитывает,
 * и «сколько стоят отели» становится одним кликом. */
.table-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: var(--s7);
  flex: none;
  padding: var(--s6) var(--s9);
  border-top: 1px solid var(--line);
  background: var(--field);
}

.panel {
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  min-height: 0;
  overflow-y: auto;
  padding: var(--s8);
  background: var(--paper);
}

/* --- все места ------------------------------------------------------------- */

.places-body {
  /* Ширины колонок таблицы: галка, место, город, тип, «занимает», статус.
   * Названы здесь, потому что по ним равняются и шапка, и каждая строка. */
  --places-cols: 22px minmax(0, 1fr) 100px 82px 124px 92px;
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  padding: 0 var(--s9) var(--s10);
}
.places-city {
  font: 600 var(--t-16) / 1 var(--sans);
  color: var(--ink);
}
.places-city:hover {
  color: var(--accent);
}
/* Таблица мест. Ширины колонок названы один раз на теле списка: шапка и строки
 * обязаны стоять по одной сетке, а две копии `grid-template-columns` разъезжаются
 * при первой же правке. */
.places-head,
.places-row {
  display: grid;
  grid-template-columns: var(--places-cols);
  gap: var(--s4);
  align-items: center;
}
/* Высота шапки задана числом, а не содержимым: под неё липнет заголовок города
 * (`top` у `.places-city-row`), и высота, зависящая от кегля подписи, разъехалась
 * бы с этим сдвигом — город наезжал бы на названия колонок или висел под ними. */
.places-head {
  position: sticky;
  top: 0;
  z-index: 2;
  height: var(--s9);
  padding: 0 var(--s5);
  background: var(--paper);
}
.places-row {
  padding: var(--s4) var(--s5);
  border: 1px solid var(--edge-card);
  border-radius: var(--r3);
  background: var(--card);
  min-width: 0;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.places-row:hover {
  background: var(--sunken);
  border-color: var(--accent-border);
}
.places-row.is-done .place-name {
  color: var(--ink-5);
  text-decoration: line-through;
}
/* Идея приглушена, но стоит рядом: держать её отдельным экраном значило бы
 * прятать то, что и так надо перебрать. Плашка у идеи и у плана одинаковая
 * по цвету намеренно — различает их приглушение всей строки, а не ещё один
 * оттенок: цвет в этом проекте занят состояниями. */
.places-row.is-idea {
  background: var(--field);
  border-style: dashed;
}
.places-cell {
  font: 400 var(--t-13) / 1.35 var(--sans);
  color: var(--ink-2);
}
.places-cell.is-kind {
  font: 400 var(--t-11) / 1.3 var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-5);
}
.places-cell.is-hint {
  color: var(--ink-3);
}
.places-cell.is-status {
  display: flex;
  justify-content: flex-end;
}
/* Подпись места — то, что не влезло в колонки: чем оно примечательно.
 * Тип и «занимает» стоят своими колонками и здесь не повторяются. */
.place-note {
  font: 400 var(--t-12) / 1.4 var(--sans);
  color: var(--ink-4);
}

/* --- заметки --------------------------------------------------------------- */

.note-row {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  width: 100%;
  padding: var(--s5);
  border: 0;
  border-radius: var(--r3);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.note-row:hover {
  background: var(--fill);
}
.note-row.is-on {
  background: var(--accent-soft);
}
.note-preview {
  font: 400 var(--t-13) / 1.4 var(--sans);
  color: var(--ink-4);
}
.note-where {
  font: 600 var(--t-11) / 1 var(--mono);
  letter-spacing: 0.12em;
  /* Приглушённой, а не акцентом: это справка «к чему привязана», а не действие. */
  color: var(--ink-4);
}

.note-pane {
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  min-height: 0;
  overflow-y: auto;
  padding: var(--s10);
  background: var(--field);
}
/* Заметка в чтении: блоки текста, карточка «Что сделать», файлы.
 *
 * Разметки два знака, и оба видны только здесь: «# » становится заголовком
 * абзаца, «> » — цитатой с полосой слева. В самом `body` они лежат как есть. */
.note-text {
  max-width: 640px;
  margin: 0;
  font: 400 var(--t-15) / 1.65 var(--sans);
  color: var(--ink-2);
  text-wrap: pretty;
}
.note-head {
  font: 600 var(--t-14) / 1.3 var(--sans);
  color: var(--ink);
}
.note-quote {
  max-width: 640px;
  padding: var(--s5) var(--s6);
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--accent-border);
  border-radius: var(--r3);
  background: var(--card);
  font: 400 var(--t-14) / 1.6 var(--sans);
  color: var(--ink-2);
}
/* Карточка «Что сделать». Галка нажимается и в чтении — ради этого список
 * и заводят: на ходу отмечают, а не открывают правку. */
.note-tasks {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  max-width: 640px;
  padding: var(--s6) var(--s7);
  border: 1px solid var(--edge-card);
  border-radius: var(--r4);
  background: var(--card);
}
.note-task {
  display: flex;
  align-items: center;
  gap: var(--s4);
}
.note-task-text {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  font: 400 var(--t-14) / 1.4 var(--sans);
  color: var(--ink);
  outline: none;
}
.note-task-text.is-done {
  color: var(--ink-4);
  text-decoration: line-through;
}

/* Заголовок и текст правятся на месте — без рамок: это документ, а не форма. */
.note-title {
  max-width: 640px;
  border: 0;
  background: none;
  font: 600 var(--t-27) / 1.15 var(--sans);
  letter-spacing: var(--track-tight);
  outline: none;
}
/* В чтении заголовок — это заголовок, а не поле: у него свои поля и высота,
 * иначе строка прыгает при переключении режима. */
.note-title.is-read {
  margin: 0;
}
.note-body {
  max-width: 640px;
  /* Занимает всю область чтения: с фиксированной высотой посреди пустой панели
   * болталась ручка растягивания, а привязки уезжали в середину экрана. Это
   * документ, и текстовое поле здесь и есть страница. */
  flex: 1;
  min-height: 320px;
  border: 0;
  background: none;
  font: 400 var(--t-16) / 1.6 var(--sans);
  color: var(--ink-2);
  resize: none;
  outline: none;
}

/* --- быстрое добавление ---------------------------------------------------- */

.quick {
  position: fixed;
  top: 96px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  width: 560px;
  max-height: calc(100dvh - 140px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s7);
  padding: var(--s9);
  border-radius: var(--r6);
  background: var(--card);
  box-shadow: var(--sh-modal);
}
.quick-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s3);
}
.quick-branch {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s5) var(--s6);
  border: 1px solid var(--edge);
  border-radius: var(--r4);
  background: var(--card);
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.quick-branch:hover {
  border-color: var(--accent-border);
}
.quick-branch.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--card);
}
.quick-hint {
  font: 400 var(--t-12) / 1.25 var(--sans);
  color: var(--ink-5);
}
.quick-branch.is-on .quick-hint {
  color: var(--rail-ink-3);
}
.quick-toggle {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s5) var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r4);
  background: var(--sunken);
}

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s5);
}
.form-wide {
  grid-column: 1 / -1;
}
.form-part {
  display: block;
  margin-top: var(--s7);
  padding-top: var(--s6);
  border-top: 1px solid var(--line);
}

/* Сворачиваемый раздел формы: редкое убрано с глаз, но не спрятано —
 * раздел с заполненным полем открыт. Треугольник браузера убран, стрелка своя:
 * стандартный маркер в Safari и Chrome разного размера и сидит вплотную. */
.form-fold {
  margin-top: var(--s7);
  padding-top: var(--s6);
  border-top: 1px solid var(--line);
}
.form-fold > summary {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s5);
  cursor: pointer;
  list-style: none;
  color: var(--ink-3);
  transition: color var(--dur-fast) var(--ease);
}
.form-fold > summary::-webkit-details-marker {
  display: none;
}
.form-fold > summary::before {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg) translate(-1px, 1px);
  transition: transform var(--dur-fast) var(--ease);
}
.form-fold[open] > summary::before {
  transform: rotate(45deg) translate(-1px, -1px);
}
.form-fold > summary:hover {
  color: var(--ink);
}
.form-fold > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r1);
}

.file-drop {
  padding: var(--s6);
  border: 1px dashed var(--edge-dash);
  border-radius: var(--r3);
  color: var(--ink-4);
  font-size: var(--t-13);
  text-align: center;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.file-drop.is-over {
  border-color: var(--accent);
  color: var(--accent);
}

/* --- результаты поиска ----------------------------------------------------- */

.results {
  position: absolute;
  top: calc(var(--topbar) - var(--s5));
  left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  width: 520px;
  max-height: 60dvh;
  overflow-y: auto;
  padding: var(--s4);
  background: var(--card);
  border: 1px solid var(--edge-card);
  border-radius: var(--r5);
  box-shadow: var(--sh-panel);
}
.result {
  display: flex;
  align-items: center;
  gap: var(--s5);
  width: 100%;
  padding: var(--s4) var(--s5);
  border: 0;
  border-radius: var(--r3);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.result:hover {
  background: var(--fill);
}
.result .caps {
  width: 76px;
  flex: none;
}

/* --- переключатель поездок ------------------------------------------------- */

.switch-pop {
  position: absolute;
  top: calc(var(--topbar) - var(--s5));
  left: var(--s8);
  z-index: 50;
  width: 320px;
  max-height: 70dvh;
  overflow-y: auto;
  padding: var(--s5);
  background: var(--card);
  border: 1px solid var(--edge-card);
  border-radius: var(--r5);
  box-shadow: var(--sh-panel);
}
.switch-pop .caps {
  display: block;
  padding: var(--s4) var(--s4) var(--s2);
}
.switch-row {
  display: flex;
  align-items: center;
  gap: var(--s4);
  width: 100%;
  padding: var(--s4);
  border: 0;
  border-radius: var(--r3);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.switch-row:hover {
  background: var(--fill);
}
.switch-row[aria-current="true"] {
  background: var(--accent-soft);
}
.switch-row .name {
  font: 500 var(--t-15) / 1.2 var(--sans);
}
.switch-row .when {
  font: 400 var(--t-12) / 1.3 var(--mono);
  color: var(--ink-4);
}

/* Ночёвка в строке плана — своя строка, а не часть перечисления.
 *
 * Перечисление через точку скрывало главное: снятая бронь выглядела ровно
 * как действующая, и «Dinghe Hotel · Dinghe Hotel» означало не две ночёвки,
 * а одну и её отменённую копию. */
.plan-row .plan-stays {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s1) var(--s2);
  min-width: 0;
}
/* Даты — **под названием, а не колонкой справа**. Колонка отбирала фиксированную
 * ширину, и в карточке на 400 px названию доставалось меньше половины: отель
 * читался как «Vienna I…». Отступ выравнивает их по названию, а не по значку. */
.plan-row .plan-stays .when {
  flex-basis: 100%;
  padding-left: calc(13px + var(--s2));
}
/* Название сжимается, а не переносится. С `flex-wrap` на родителе оно уезжало
 * на вторую строку, оставляя значок висеть в одиночестве: `min-width: 0`
 * разрешает сжатие, но не назначает его — нужен ещё и `flex`. */
.plan-row .plan-stays > .clip {
  flex: 1 1 0;
}


/* Заголовок города на «Всех местах» — липкий, как в списке броней: девятнадцать
 * мест в двенадцати городах прокручиваются на несколько экранов, и к середине
 * непонятно, чьи это места. Фон непрозрачный — под строкой проезжают плитки. */
.places-city-row {
  position: sticky;
  /* Под шапкой таблицы, а не на её месте: обе липкие, и с `top: 0` город
   * закрывал бы названия колонок. */
  top: var(--s9);
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: var(--s5);
  padding: var(--s4) 0;
  background: var(--paper);
}

/* Заметки города на его экране — капсулами, а не списком: их одна-две,
 * и полный текст живёт уровнем глубже, в разделе заметок. */
.stage-notes {
  padding-bottom: 0;
}
.note-chip {
  padding: var(--s2) var(--s5);
  border: 1px solid var(--edge);
  border-radius: var(--r-pill);
  background: var(--sunken);
  color: var(--ink-2);
  font: 500 var(--t-13) / 1.2 var(--sans);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.note-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.note-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Чем уезжают из города — строкой в его карточке, тем же ростом, что ночёвка.
 * Список видов транспорта нативный: их шесть, они не меняются, и своя выпадашка
 * ради шести строк — это ещё один компонент с фокусом и клавиатурой. */
.plan-move {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  font: 400 var(--t-13) / 1.3 var(--sans);
  color: var(--ink-4);
}
.plan-move svg {
  flex: none;
}
.plan-move select,
.plan-move-from {
  flex: none;
  max-width: 42%;
  padding: 0 var(--s1);
  border: 1px solid transparent;
  border-radius: var(--r1);
  background: transparent;
  font: inherit;
  color: var(--ink-2);
  cursor: pointer;
}
.plan-move select:hover,
.plan-move-from:hover {
  border-color: var(--edge);
  background: var(--field);
}
.plan-move select:focus,
.plan-move-from:focus {
  border-color: var(--accent);
  background: var(--card);
  outline: none;
}
.plan-move-from {
  flex: 1;
  cursor: text;
}
.plan-move-from:disabled {
  color: var(--ink-5);
  cursor: default;
}
.plan-move-to {
  color: var(--ink-4);
}

/* Дорога у перегона: длина с временем, а пока не проложена — предложение.
 * Кнопкой, а не текстом со ссылкой: она и показывает результат, и повторяет
 * прокладку, когда города переехали. */
.plan-road {
  align-self: flex-start;
  margin-left: calc(13px + var(--s2));
  padding: 0 var(--s2);
  border: 0;
  border-radius: var(--r1);
  background: transparent;
  font: 400 var(--t-12) / 1.3 var(--mono);
  color: var(--ink-3);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.plan-road {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
}
/* Значок бледнее чисел: он подсказка, что это кнопка, а не второе сообщение. */
.plan-road svg {
  color: var(--faint);
  transition: color var(--dur-fast) var(--ease);
}
.plan-road:hover {
  background: var(--fill);
  color: var(--ink);
}
.plan-road:hover svg {
  color: var(--accent);
}
.plan-road.is-empty {
  color: var(--ink-5);
  font-family: var(--sans);
}
.plan-road:disabled {
  color: var(--ink-4);
  cursor: default;
}

/* Билет: заказ шапкой, рейсы телом.
 *
 * Форма перелёта — единственное место, где сегменты правят вместе, и вёрстка
 * обязана это показывать: заказ идёт обычной сеткой формы, а каждый рейс —
 * карточкой со своим номером. Пока рейсы шли теми же полями подряд, «где
 * кончается первый» приходилось выяснять по содержимому. */
.ticket-legs {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}
.ticket-leg {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  padding: var(--s7);
  border: 1px solid var(--edge-card);
  border-radius: var(--r3);
  background: var(--card);
}
.ticket-leg-head {
  display: flex;
  align-items: flex-end;
  gap: var(--s5);
}
/* Номер сегмента тот же, что в показе билета: одна и та же ① в форме и в брони.
 * Приподнят до линии полей — иначе он висит у их подписей. */
.ticket-leg-head > .flight-num {
  margin-bottom: var(--s3);
}
.ticket-leg-head > .field {
  flex: 1 1 0;
  min-width: 0;
}
.ticket-leg-head > .btn {
  margin-bottom: var(--hair);
}

/* Конец рейса: город, аэропорт с кодом и терминалом, момент и зона — вместе.
 * Порознь они не отвечают ни на что: «Хунцяо T1» это один ответ, а «PEK»
 * без зоны — способ увезти рейс на восемь часов. */
.ticket-end {
  padding: var(--s6);
  border-radius: var(--r2);
  background: var(--sunken);
}
.ticket-end-mark {
  display: block;
  margin-bottom: var(--s5);
  color: var(--ink-3);
}
/* Три колонки, а не две: шесть полей конца в две колонки дают три ряда,
 * и время отъезжает от города, с которым его читают. Та же сетка у шапки заказа:
 * «Сколько: 1» в поле шириной в половину ящика читается как недозаполненное. */
.ticket-end .form-grid,
.ticket-order {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.ticket-span2 {
  grid-column: span 2;
}
/* Начало смыслового ряда. Ряды шапки заказа отвечают на разное — чем заказ
 * опознаётся, сколько стоил, где взят, — и без этого поля первого ряда
 * затягивают в себя первое поле второго, стоит убрать одно из них. */
.ticket-newrow {
  grid-column: 1;
}
/* Пассажиры: пять полей в ряд, ширина по длине ответа. «2A» и «30 кг» — два
 * знака и четыре, имя и номер билета длиннее всех: равные колонки отдали бы
 * полстроки под место и обрезали бы имя. */
.ticket-pax {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}
.ticket-pax-row {
  display: grid;
  grid-template-columns: 1.6fr 0.5fr 0.7fr 1fr 1.2fr;
  gap: var(--s2);
}
.ticket-pax-row input {
  min-width: 0;
  font-family: var(--mono);
  font-size: var(--t-14);
}

.ticket-code {
  display: flex;
  align-items: center;
  gap: var(--s3);
}
.ticket-code input {
  min-width: 0;
  text-transform: uppercase;
  font-family: var(--mono);
}
.ticket-code .btn {
  flex: none;
}

/* «Добавить пересадку» — пунктиром во всю ширину: это не действие над формой,
 * а место, куда встанет следующий рейс, и выглядеть оно должно как пустое
 * место, а не как кнопка рядом с «Сохранить». */
.ticket-add {
  justify-content: center;
  width: 100%;
  margin-top: var(--s5);
  border: 1px dashed var(--edge-dash);
  border-radius: var(--r3);
  padding: var(--s6);
  color: var(--ink-3);
}
.ticket-add:hover {
  border-color: var(--accent-border);
  color: var(--accent);
}
/* Стыковка внутри формы — тот же элемент, что в билете, и то же янтарное
 * «тесно». Отступы здесь свои: в показе она разделяет строки, а тут — карточки. */
.ticket-legs > .flight-layover {
  margin-left: var(--s7);
}

