b673367c6ff8a698e8e83571397d3dfaa056a14a
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b673367c6f |
Prepare the four landings for production deployment
Fixes that block or weaken a real deployment. Nothing here changes the rendered pages. Bind the lead API to loopback. Each server called app.listen() without a host, so it bound 0.0.0.0. Combined with a blanket `trust proxy: true` — which makes Express take the leftmost X-Forwarded-For entry as req.ip — the in-memory lead rate limiter was spoofable by anyone who could reach the port directly. HOST now defaults to 127.0.0.1 and trust is narrowed to 'loopback', so a request arriving from anywhere but the local proxy has its forged header ignored. Give each landing its own port. All four .env files claimed PORT=3000, and fitnes/.env.example collided with medcenter/.env.example, so three of the four could never have started on one host. Now 3000/3001/3002/3003 consistently across the code defaults, the env templates and the vite dev proxies, so all four also run side by side locally. Template the JSON-LD url. canonical and og:url already resolved from %VITE_SITE_URL%, but the JSON-LD block hardcoded an exodevices.ru sub-path that would not follow the environment. All four now read from the same variable. Declare the Node version. Nothing stated it, yet transitive deps impose a >=22.12 floor (@rolldown/binding, yargs, concurrently). Added engines and .nvmrc so a too-old runtime fails clearly. Typechecked and production-built on all four; verified the socket binds 127.0.0.1 only, health reports amo:true, the site still boots with the CRM unconfigured, and the limiter returns 429 with Retry-After on the ninth request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cd51cb5632 |
Fix horizontal truncation across the phone layer
Four defects, all of them real clipping rather than styling taste. The header's two action pills were `shrink-0` at 288px inside a 343px container, so flexbox took the difference out of the logo, which carried `min-w-0`. The brand mark rendered 37px wide at 375px and 0px at 320px, and the CTA ran past the right edge. The wordmark stays legible now and the call button goes square and icon-only below 760px; the CTA moves to a new MobileActionBar, which has room for it and puts it in the thumb zone. It waits for the hero's own buttons to scroll away and drops back down while the form is on screen, so it never covers the fields it points at. `.scroll-cards` bleeds to the viewport edge with a 16px inline padding and a matching negative margin, but had no `scroll-padding-inline`, so `scroll-snap-align: start` resolved to scroll position 16 and mandatory snapping forced that scroll on load: the first card sat on the bezel with no gutter, and one row had drifted a full card over. Matching the padding fixes the resting position on all seven rows. Card tracks move from a percentage of the row to `calc(100vw - 62px)`. A percentage peek grows with the screen, and at 82-90% it exposed ~68px of the next card -- enough legible half-words to read as broken text. The value stays in the markup as `auto-cols-*` so it keeps its sort position against `sm:grid-still`, which has to win at the breakpoint. The market stat card pinned its source line to the card bottom while the longest label wraps to four lines at phone width, running text through text by 75px. The source is in flow under `mt-auto` instead. The header CTA switches on `max-md` rather than the `narrow` variant: `narrow` is max-width 760 and the bar is min-width 760, so at exactly 760px both would have hidden and the page would have had no CTA at all. Verified at 320 / 375 / 759 / 760 / 800 and desktop: no horizontal page scroll, no in-flow overflow, desktop grids unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9b4f93bb16 |
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> |
||
|
|
cd3414d8d0 |
Convert "physiotherapy from scratch" landing to React + Tailwind v4
Ports the last single-file page, MedCbezFiz — the medical centre that has no physiotherapy yet — to the architecture the other three landings now share: Vite + React 19 + TypeScript + Tailwind v4, Express 5 for the API, zod schema shared between client and server. The original is kept in legacy/index.html as the visual reference, with its instructions alongside it in legacy/README.md. Its 4 MB collapse to 317 KB of JS plus assets loaded on demand. Fourteen inlined images were extracted; twelve turned out byte-identical to the medcenter landing's, and the two large device PNGs (1.5 MB and 750 KB) had identical sources, so the webp conversions from that landing are reused rather than redone — 664 KB of assets instead of 2.8 MB. Text was verified rather than eyeballed: the app is server-rendered to static HTML and its visible text diffed against the legacy file. 280 blocks against 281, and the only difference is the honeypot's screen-reader label, which the original has no equivalent for. Two things in the original are dead and were deliberately not ported: 39 .patient-economics / .base-economics-card rules that no element uses, and the [data-counter] animation handler, which likewise matches nothing on the page. Both are documented in the README. Departures from the medcenter landing this was built from, all following the original: the hero photo is anchored at 65% (72% from 980px), the header is transparent until 18px of scroll instead of always painted, the revenue band keeps 32px gutters at every width, and the launch timeline breaks into two columns at 900px rather than 760px. Leads post to /api/leads/medical-centers-no-physio, the address the original page already referenced, into pipeline 10980758. The cabinet_state select has no counterpart on this form and was dropped from the schema, mapper and check script. Vite runs on 5175 and the API on 3002 so all four landings can run at once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8b954ea5db |
Convert medical-centre landing to React + Tailwind v4 with amoCRM lead capture
Ports the single-file MedCsFiz page — the medical centre that already runs physiotherapy and wants the existing room to earn more — to the same architecture as the fitness and hotel landings: Vite + React 19 + TypeScript + Tailwind v4 on the client, Express 5 for the API, zod schema shared between the two. The original stays in legacy/index.html as the visual reference. Its 4 MB of inlined CSS, JS and base64 images become 12 asset files (664 KB) plus a bundle loaded on demand; the two 1.5 MB / 750 KB device PNGs are recompressed to webp. The page is split into 18 components with all copy moved to src/data/content.ts. Leads reuse the fitness amoCRM integration: contact lookup by phone in every Russian spelling, deal in the first stage of pipeline 10980758, account fields matched automatically with the rest written to a note. Rate limit, honeypot and a localStorage fallback so a lead survives the CRM being down. Endpoint is /api/leads/medical-centers-existing-physio; Vite runs on 5173 and the API on 3000. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
35f713a267 |
Convert hotel landing to React + Tailwind v4 with amoCRM lead capture
Ports the single-file ЭкзоОтель page to the same architecture as the fitness
landing: Vite + React 19 + TypeScript + Tailwind v4 on the client, Express 5 for
the /api/leads/hotels endpoint, zod schema shared between the two.
The original page is kept in legacy/index.html as the visual reference. Its 15
inlined base64 images are extracted to files (the 1 MB HTML becomes ~318 KB of
JS plus assets loaded on demand), and its text is reproduced line for line —
verified with an innerText diff. Section heights stay within 0.6% at 375 and
1440 px; the drift comes from Inter actually loading, which the original asked
for but never served.
Three deliberate departures, documented in the README:
* eyebrow and lead in the CTA block were dark teal on navy (3.4:1); they now
match the other dark sections
* hero fact values overflowed their 80px column into the label below 430px
* "2025–2026г.." typo in the market source note
Leads reuse the fitness amoCRM integration: contact lookup by phone in every
spelling, deal in the first stage of pipeline 10980758, account fields matched
automatically with the rest written to a note. Rate limit, honeypot, and a
localStorage fallback so a lead survives the CRM being down. Vite runs on 5174
and the API on 3001 so both landings can run at once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
60de4ef27c |
Convert fitness landing to React + Tailwind v4 with amoCRM lead capture
Port the single-file landing (2.6 MB of inlined CSS, JS and base64 images, kept as fitnes/legacy/index.html) to Vite + React 19 + TypeScript, with an Express API that files every form submission into amoCRM pipeline 10980758. - Extract the 14 embedded images to src/assets/images and public/ - Rebuild the design system as Tailwind v4 @theme tokens; the stock palette and breakpoints are cleared so only the EXO scale is reachable from utilities - Split the page into 15 components; all copy moves to src/data - Lead endpoint: find-or-create the contact (Russian phone spellings compared on the last 10 digits), create the lead in the pipeline's first stage, map the fields the account already has and put the rest in a note. If amoCRM is unreachable the payload is logged and kept in localStorage rather than lost. - Add a callback modal as a second entry point, tagged separately in the pipeline - Self-host Inter Variable so the layout's 760/850/900 weights render as real weights instead of snapping to bold Fidelity was checked by comparing section offsets and heights against the original at 375/480/640/900/1120/1440 px; every section and the total page height matched exactly. Loading Inter deliberately changes text metrics, so the byte-exact comparison holds against the pre-font build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |