Split schedule details into three separate frame blocks

Each flight card and the Пересадка strip are now sibling elements
inside .schedule-details — each flight in its own <section class="frame">,
the TransferBar standalone between them. The shared outer frame
wrapper is gone, so the dark page background shows through the
between-block gaps instead of one continuous white surface.

That produces the 'three separate white cards on dark bg'
visual Angular uses for a connecting itinerary (flight 1 frame |
Пересадка | flight 2 frame) — 40px white margins that previously
bled into the surrounding frame disappeared because the flex gap
now renders against the actual page background.
This commit is contained in:
2026-04-23 17:23:20 +03:00
parent fbd819c707
commit 62136dcde3
2 changed files with 53 additions and 78 deletions
@@ -42,30 +42,22 @@
}
}
// The connecting-itinerary "Пересадка" strip between two flights.
// Angular renders it with generous vertical margin AND the same
// rounded-card treatment as `.transfer-bar--separated` — otherwise
// the strip sits flush against the preceding FlightSchedule block
// and the next flight's header, reading as a shared edge rather than
// a separator.
.schedule-details__transfer {
// $space-xxl (40px) so the strip reads unmistakably as a SEPARATOR.
// $space-l (15px) was physically present but visually indistinguishable
// from the regular between-card gap set by `.schedule-details { gap }`,
// so the Пересадка looked glued to its neighbours. 40/40 mirrors
// Angular's breathing room between `.multi-flight--details` blocks.
margin: vars.$space-xxl 0;
> .transfer-bar {
border-radius: vars.$border-radius;
}
}
.schedule-details {
display: flex;
flex-direction: column;
// Each flight card and the Пересадка strip are siblings inside
// `.schedule-details`. The gap between them shows the dark page
// background (the `.frame` cards themselves are white), which is
// what produces the "three separate blocks" look.
gap: vars.$space-l;
// Standalone TransferBar between two flight frames — round its
// corners the same way Angular's `.transfer-bar--separated` does,
// since it's no longer wrapped in a white card.
> .transfer-bar {
border-radius: vars.$border-radius;
}
&__header-left {
display: flex;
flex-direction: column;
@@ -9,7 +9,7 @@
*/
import type { FC } from "react";
import { useCallback, useMemo } from "react";
import { Fragment, useCallback, useMemo } from "react";
import { Link, useNavigate, useSearchParams } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import { localeToLanguage, normalizeLocaleParam, DEFAULT_LANGUAGE } from "@/i18n/resolver.js";
@@ -525,33 +525,34 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
)}
</section>
)}
<section className="frame">
<div className="schedule-details" data-testid="schedule-details">
{flights.map((flight, idx) => {
const jsonLd = buildScheduleFlightJsonLd(flight);
const flightNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
const nextFlight = flights[idx + 1];
{/* Each flight renders in its OWN .frame section, and the
Пересадка strip sits between them as a sibling — NOT inside
a shared frame. That way the dark page background shows
through the gaps set by `.schedule-details { gap }`, giving
the three-distinct-blocks layout Angular uses for a
connecting itinerary. A single shared frame would extend
white behind the transfer and kill the separation. */}
<div className="schedule-details" data-testid="schedule-details">
{flights.map((flight, idx) => {
const jsonLd = buildScheduleFlightJsonLd(flight);
const flightNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
const nextFlight = flights[idx + 1];
// For connecting itineraries assembled from the URL we
// render a TransferBar between consecutive legs (matches
// Angular's `transfer-inline-extended` panel between flight
// details cards). Use the last leg of this flight and the
// first leg of the next.
const lastLegOfThis = (() => {
const legs = getLegs(flight) as IFlightLeg[];
return legs[legs.length - 1];
})();
const firstLegOfNext = nextFlight
? (() => {
const legs = getLegs(nextFlight) as IFlightLeg[];
return legs[0];
})()
: undefined;
const lastLegOfThis = (() => {
const legs = getLegs(flight) as IFlightLeg[];
return legs[legs.length - 1];
})();
const firstLegOfNext = nextFlight
? (() => {
const legs = getLegs(nextFlight) as IFlightLeg[];
return legs[0];
})()
: undefined;
return (
<div
key={flight.id}
className="schedule-details__flight"
return (
<Fragment key={flight.id}>
<section
className="frame schedule-details__flight"
data-testid={`flight-${flight.id}`}
>
<JsonLdRenderer data={jsonLd} />
@@ -565,48 +566,30 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
</span>
</div>
{/* Collapsed summary row, then the rich per-leg body. */}
<FlightCard flight={flight} direction="schedule" />
{/* iFly operator warning (SU5801-5948) — Angular
renders this inside flight-schedule-details between
the header and the route strip. */}
<IFlyWarning flightNumber={flight.flightId.flightNumber} />
{renderBody(flight)}
{/* Angular's `flight-details-wrapper` accordion: aircraft
type + on-board meal classes. */}
<ScheduleLegDetails flight={flight as unknown as ISimpleFlight} />
{/* Angular's flight-schedule-details renders the weekly
operating schedule below the leg body for direct
flights. Skip it for multi-leg chains (Angular also
hides it there). */}
{/* Angular hides the weekly operating schedule on
multi-leg chains; keep it on direct flights. */}
{flight.routeType === "Direct" && (
<FlightSchedule flight={flight as unknown as ISimpleFlight} />
)}
</section>
{/* Angular `transfer-inline-extended` between the two
flight cards on a connecting itinerary. The two
flights have different flight numbers, so this is a
"Пересадка" (not an "Промежуточная посадка"). The
wrapper adds the breathing room above/below the
strip — without it the bar sits flush against the
first flight's FlightSchedule/leg-details card and
the next flight's header. */}
{firstLegOfNext && lastLegOfThis && (
<div className="schedule-details__transfer">
<TransferBar
leg={lastLegOfThis}
nextLeg={firstLegOfNext}
viewType="Schedule"
isIntermediateLanding={false}
/>
</div>
)}
</div>
);
})}
</div>
</section>
{firstLegOfNext && lastLegOfThis && (
<TransferBar
leg={lastLegOfThis}
nextLeg={firstLegOfNext}
viewType="Schedule"
isIntermediateLanding={false}
/>
)}
</Fragment>
);
})}
</div>
</PageLayout>
);
};