Redesign the phone layer of the landing

Three layout bugs and a mobile experience that had no wayfinding.

The `#proposal` section was clipped, not merely cramped: the submit
button carried `whitespace-nowrap` from the shared button base, giving
it a min-content width of 358px, which sized the section's implicit
`auto` grid track to 406px inside a 343px container. `overflow-hidden`
then sliced the copy and every form field off the right edge of a 375px
screen. Fixed at the root — `whitespace-nowrap` moves out of `base` and
into `headerSize`, where buttons are sized by their label — plus an
explicit `grid-cols-[minmax(0,1fr)]` on the section.

The market stat cards put their source line in absolute position against
a guessed min-height, so the longest label ran underneath it. It is in
the flow now, pinned by `mt-auto`.

Below 980px the header nav is not rendered, which left seventeen screens
of scroll with no way to navigate them. The header gives that width back
to the page — logo and call button only — and <ThumbBar /> takes over:
scroll progress, a live section indicator that opens a section sheet,
and one short CTA, hidden over the hero and over the form.

Card rows now say what they are. Rows of unordered peers keep scrolling
sideways and gain a measured segment indicator (<Rail />), replacing the
two prose "swipe sideways" hints. The launch timeline goes the other
way: sideways scrolling hid week 3-5 from someone reading week 1-2, so
below 900px it becomes four disclosures on a spine — which also recovers
the scroll length the taller hero spent.

Below 560px the type scale steps down half a level and the display
tracking opens back up, and the hero photo zooms past `cover` onto the
wave wall, since fitting by height left only ceiling tiles above the
copy.

Also: the route card inside the market section split into two 165px
columns at 760px, so that split now waits for 980px.

Desktop above 980px is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 11:32:28 +06:00
co-authored by Claude Opus 5
parent cd3414d8d0
commit 9b4f93bb16
22 changed files with 724 additions and 114 deletions
@@ -2,6 +2,7 @@ import { marketNotes, marketStats, routeWith, routeWithout } from '../data/conte
import { useReveal } from '../hooks/useReveal'
import { cx } from '../lib/cx'
import { Container, Eyebrow, Section, SectionHead, SmallNote } from './Layout'
import { Rail } from './Rail'
export function MarketSection() {
const stats = useReveal<HTMLDivElement>()
@@ -17,39 +18,41 @@ export function MarketSection() {
lead="Забудьте о расходах на привлечение новых клиентов. Мы встраиваем готовое направление физиотерапии в вашу структуру, превращая лояльную базу пациентов и текущий штат врачей в источник новой прибыли."
/>
<div className="grid gap-[24px] md:grid-cols-[minmax(0,1.08fr)_minmax(360px,0.92fr)] md:items-stretch">
<div
ref={stats.ref}
className={cx(
'scroll-cards auto-cols-[minmax(255px,82%)] gap-[12px] sm:grid-still sm:grid-cols-2',
stats.revealClass,
)}
<div className="grid grid-cols-[minmax(0,1fr)] gap-[24px] md:grid-cols-[minmax(0,1.08fr)_minmax(360px,0.92fr)] md:items-stretch">
<Rail
outerRef={stats.ref}
wrapperClassName={stats.revealClass}
label="Рыночные показатели"
className="scroll-cards auto-cols-[minmax(255px,82%)] gap-[12px] sm:grid-still sm:grid-cols-2"
>
{marketStats.map((stat) => (
/* The source line used to be absolutely positioned against the
card's min-height, so the longest label ran underneath it.
It is in the flow now, pinned to the bottom by `mt-auto`. */
<article
key={stat.label}
className="relative min-h-[185px] overflow-hidden rounded-lg border border-navy/[0.12] bg-white p-[24px] shadow-[0_16px_48px_rgb(4_29_46/0.06)]
phone:min-h-[172px] phone:p-[20px] sm:min-h-[205px]"
className="relative flex min-h-[185px] flex-col overflow-hidden rounded-lg border border-navy/[0.12] bg-white p-[24px] shadow-[0_16px_48px_rgb(4_29_46/0.06)]
phone:min-h-[168px] phone:p-[20px] sm:min-h-[205px]"
>
<span
aria-hidden="true"
className="teal-bloom-soft absolute -right-[36px] -bottom-[48px] size-[150px] rounded-full"
/>
<strong className="mb-[8px] block text-[clamp(34px,5vw,54px)] leading-none font-[830] tracking-[-0.06em] text-navy">
<strong className="relative mb-[8px] block text-[clamp(34px,5vw,54px)] leading-none font-[830] tracking-[-0.06em] text-navy">
{stat.value}
</strong>
<p className="max-w-[240px] text-[14px] leading-[1.45] text-muted">{stat.label}</p>
<span className="absolute bottom-[19px] left-[24px] text-[10px] tracking-[0.09em] text-[#7E93A3] uppercase phone:bottom-[15px] phone:left-[20px]">
<p className="relative max-w-[240px] text-[14px] leading-[1.45] text-muted">{stat.label}</p>
<span className="relative mt-auto pt-[14px] text-[10px] tracking-[0.09em] text-[#7E93A3] uppercase">
{stat.source}
</span>
</article>
))}
</div>
</Rail>
<article
ref={gap.ref}
className={cx(
'gap-card-surface relative grid gap-[16px] overflow-hidden rounded-xl p-[27px] text-white shadow-deep',
'gap-card-surface relative grid gap-[16px] overflow-hidden rounded-xl p-[27px] text-white shadow-deep compact:p-[20px]',
gap.revealClass,
)}
>
@@ -60,12 +63,15 @@ export function MarketSection() {
<div className="relative z-1">
<Eyebrow tone="dark">Исходная точка</Eyebrow>
<h3 className="max-w-[600px] text-[clamp(27px,3.5vw,42px)]">
<h3 className="max-w-[600px] text-[clamp(27px,3.5vw,42px)] compact:text-[22px] compact:tracking-[-0.02em]">
Маршрут пациента: как перестать отдавать прибыль конкурентам.
</h3>
</div>
<div className="relative z-1 grid gap-[12px] md:grid-cols-2">
{/* Not `md:` — at 760 the card is already sharing the row with the
stats, so splitting it again gives two 165px columns of
four-word lines. It waits for 980, where the card is wide. */}
<div className="relative z-1 grid gap-[12px] lg:grid-cols-2">
<RouteColumn sign="—" title="Пока физиотерапии нет" items={routeWithout} />
<RouteColumn sign="+" title="После запуска направления" items={routeWith} accent />
</div>