diff --git a/src/features/online-board/components/TransferBar/TransferBar.scss b/src/features/online-board/components/TransferBar/TransferBar.scss
new file mode 100644
index 00000000..2e26b8d6
--- /dev/null
+++ b/src/features/online-board/components/TransferBar/TransferBar.scss
@@ -0,0 +1,45 @@
+.transfer-bar {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ background: #e3f0ff;
+ border: 1px solid #b8d4f0;
+ padding: 12px 16px;
+ min-height: 50px;
+ font-size: 13px;
+
+ &--separated {
+ margin: 12px 0;
+ border-radius: 3px;
+ }
+
+ &__icon svg {
+ fill: #ff9000;
+ width: 20px;
+ height: 20px;
+ }
+
+ &__content {
+ flex: 1;
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ flex-wrap: wrap;
+ }
+
+ &__type { font-weight: 500; }
+
+ &__times { color: #1a3a5c; }
+
+ @media (max-width: 768px) {
+ &__content {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ }
+}
+
+.transfer-time {
+ font-weight: 500;
+ color: #ff9000;
+}
diff --git a/src/features/online-board/components/TransferBar/TransferBar.test.tsx b/src/features/online-board/components/TransferBar/TransferBar.test.tsx
new file mode 100644
index 00000000..71c8db93
--- /dev/null
+++ b/src/features/online-board/components/TransferBar/TransferBar.test.tsx
@@ -0,0 +1,130 @@
+// @vitest-environment jsdom
+import { describe, it, expect, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import type { IFlightLeg } from "../../types.js";
+
+vi.mock("@/i18n/provider.js", () => ({
+ useTranslation: () => ({ t: (k: string) => k }),
+}));
+
+vi.mock("../FullRouteTimeline/StationChange.js", () => ({
+ StationChange: ({ from, to }: { from: { scheduled: { cityCode: string } }; to: { scheduled: { cityCode: string } } }) => (
+
{from.scheduled.cityCode}-{to.scheduled.cityCode}
+ ),
+}));
+
+import { TransferBar } from "./TransferBar.js";
+
+function makeLeg(overrides: {
+ arrCity?: string;
+ depCity?: string;
+ arrCityCode?: string;
+ depCityCode?: string;
+ arrAirportCode?: string;
+ depAirportCode?: string;
+ arrUtc?: string;
+ depUtc?: string;
+ arrLocal?: string;
+ depLocal?: string;
+} = {}): IFlightLeg {
+ const arrCity = overrides.arrCity ?? "Moscow";
+ const depCity = overrides.depCity ?? "Moscow";
+ const arrCityCode = overrides.arrCityCode ?? "MOW";
+ const depCityCode = overrides.depCityCode ?? "MOW";
+ const arrAirportCode = overrides.arrAirportCode ?? "SVO";
+ const depAirportCode = overrides.depAirportCode ?? "SVO";
+ const arrUtc = overrides.arrUtc ?? "2026-04-17T10:00:00Z";
+ const depUtc = overrides.depUtc ?? "2026-04-17T11:30:00Z";
+ const arrLocal = overrides.arrLocal ?? "10:00";
+ const depLocal = overrides.depLocal ?? "11:30";
+
+ return {
+ arrival: {
+ scheduled: {
+ airport: "",
+ airportCode: arrAirportCode,
+ city: arrCity,
+ cityCode: arrCityCode,
+ countryCode: "RU",
+ },
+ terminal: "",
+ times: {
+ scheduledArrival: {
+ dayChange: { value: 0, title: "" },
+ local: arrLocal,
+ localTime: arrLocal,
+ tzOffset: 0,
+ utc: arrUtc,
+ },
+ },
+ },
+ departure: {
+ scheduled: {
+ airport: "",
+ airportCode: depAirportCode,
+ city: depCity,
+ cityCode: depCityCode,
+ countryCode: "RU",
+ },
+ terminal: "",
+ checkingStatus: "",
+ times: {
+ scheduledDeparture: {
+ dayChange: { value: 0, title: "" },
+ local: depLocal,
+ localTime: depLocal,
+ tzOffset: 0,
+ utc: depUtc,
+ },
+ },
+ },
+ dayChange: 0,
+ equipment: {},
+ flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
+ flyingTime: "1h 30m",
+ index: 0,
+ operatingBy: {},
+ status: "Scheduled",
+ updated: "",
+ } as IFlightLeg;
+}
+
+describe("TransferBar", () => {
+ it("renders with data-testid=transfer-bar", () => {
+ const leg = makeLeg({ arrUtc: "2026-04-17T10:00:00Z", arrLocal: "10:00" });
+ const nextLeg = makeLeg({ depUtc: "2026-04-17T11:30:00Z", depLocal: "11:30" });
+ render();
+ expect(screen.getByTestId("transfer-bar")).toBeTruthy();
+ });
+
+ it("renders SHARED.INTERMEDIATE-LANDING label and both times", () => {
+ const leg = makeLeg({ arrLocal: "10:00", arrUtc: "2026-04-17T10:00:00Z" });
+ const nextLeg = makeLeg({ depLocal: "11:30", depUtc: "2026-04-17T11:30:00Z" });
+ render();
+ expect(screen.getByText("SHARED.INTERMEDIATE-LANDING")).toBeTruthy();
+ expect(screen.getByText("10:00")).toBeTruthy();
+ expect(screen.getByText("11:30")).toBeTruthy();
+ });
+
+ it("renders TransferTime component", () => {
+ const leg = makeLeg({ arrUtc: "2026-04-17T10:00:00Z" });
+ const nextLeg = makeLeg({ depUtc: "2026-04-17T11:30:00Z" });
+ render();
+ expect(screen.getByTestId("transfer-time")).toBeTruthy();
+ });
+
+ it("renders StationChange when arrival/departure cities differ", () => {
+ const leg = makeLeg({ arrCity: "Moscow", arrCityCode: "MOW" });
+ const nextLeg = makeLeg({ depCity: "Saint Petersburg", depCityCode: "LED", depAirportCode: "LED" });
+ render();
+ expect(screen.getByTestId("station-change")).toBeTruthy();
+ });
+
+ it("omits --separated class when arrival/departure cities match", () => {
+ const leg = makeLeg({ arrCity: "Moscow", arrCityCode: "MOW", arrAirportCode: "SVO" });
+ const nextLeg = makeLeg({ depCity: "Moscow", depCityCode: "MOW", depAirportCode: "SVO" });
+ const { container } = render();
+ const bar = container.querySelector(".transfer-bar");
+ expect(bar?.className).not.toContain("transfer-bar--separated");
+ });
+});
diff --git a/src/features/online-board/components/TransferBar/TransferBar.tsx b/src/features/online-board/components/TransferBar/TransferBar.tsx
new file mode 100644
index 00000000..6ce1e8c8
--- /dev/null
+++ b/src/features/online-board/components/TransferBar/TransferBar.tsx
@@ -0,0 +1,62 @@
+import type { FC } from "react";
+import { useTranslation } from "@/i18n/provider.js";
+import type { IFlightLeg } from "../../types.js";
+import { StationChange } from "../FullRouteTimeline/StationChange.js";
+import { detectStationChange } from "../FullRouteTimeline/detectStationChange.js";
+import { TransferTime } from "./TransferTime.js";
+import "./TransferBar.scss";
+
+export interface TransferBarProps {
+ leg: IFlightLeg;
+ nextLeg: IFlightLeg;
+ viewType: "Onlineboard" | "Schedule";
+}
+
+function arrivalLocal(leg: IFlightLeg, viewType: "Onlineboard" | "Schedule"): string {
+ const t = leg.arrival.times;
+ if (viewType === "Schedule") return t.scheduledArrival.local;
+ return t.actualBlockOn?.local ?? t.scheduledArrival.local;
+}
+
+function departureLocal(leg: IFlightLeg, viewType: "Onlineboard" | "Schedule"): string {
+ const t = leg.departure.times;
+ if (viewType === "Schedule") return t.scheduledDeparture.local;
+ return t.actualBlockOff?.local ?? t.scheduledDeparture.local;
+}
+
+export const TransferBar: FC = ({ leg, nextLeg, viewType }) => {
+ const { t } = useTranslation();
+ const stationChange = detectStationChange(leg.arrival, nextLeg.departure);
+ const separated = stationChange !== "noChange";
+
+ const className = `transfer-bar${separated ? " transfer-bar--separated" : ""}`;
+
+ return (
+
+
+
+
{t("SHARED.INTERMEDIATE-LANDING")}
+
+
+
+
+ {arrivalLocal(leg, viewType)}
+ —
+ {departureLocal(nextLeg, viewType)}
+
+ {separated && (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/src/features/online-board/components/TransferBar/index.ts b/src/features/online-board/components/TransferBar/index.ts
new file mode 100644
index 00000000..9cf2dbb3
--- /dev/null
+++ b/src/features/online-board/components/TransferBar/index.ts
@@ -0,0 +1,2 @@
+export { TransferBar } from "./TransferBar.js";
+export type { TransferBarProps } from "./TransferBar.js";