fitnes: apply the Fitness v2 design

Ports legacy/Fitnes v2 into the React landing.

- Copy: nav is now Экономика / Оборудование / Программы / Интерьер, every
  CTA reads «Получить консультацию», the hero has one button, a new label
  and new facts.
- Calculator: the average check defaults to 2 500 ₽, and a new investment
  field drives payback and 12-month ROI. Adds the ЭкзоКлиник source note
  and a new disclaimer.
- Constructor: «Состав оборудования», indications on the device cards. The
  CTA stays live with fewer than three devices picked, and the copy points
  to a consultation instead.
- «Юридическая рамка» is replaced by «Форматы размещения» (12 / 24 / 36 м²).
- Lead form: city and club format are dropped; company and email are
  optional. The server schema already treats them as optional.
- Section headings share one compact scale (headingScale + a `slim`
  640px variant), as in the design's v7 rule.

Deliberately kept from the current site rather than the mockup: the
+7 927 789-60-71 number and the callback modal behind the header call
button, both newer than the design.

With Inter substituted into the mockup (it ships no font and falls back to
Arial), every section height matches at 900 and 1440 px. At 375 px the
hero is 6 px shorter because of the existing fluid h1 on phones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Yuriy Panov
2026-09-11 18:37:03 +06:00
co-authored by Claude Opus 5
parent 8af2e463b3
commit 432284eefb
16 changed files with 166 additions and 127 deletions
+52 -8
View File
@@ -1,9 +1,9 @@
import { useState } from 'react'
import { econFormula, econGain, econLoss, econMetrics, econOps } from '../data/content'
import { useReveal } from '../hooks/useReveal'
import { formatMoney, formatNumber, parseAmount } from '../lib/format'
import { formatDecimal, formatMoney, formatNumber, parseAmount } from '../lib/format'
import { cx } from '../lib/cx'
import { Container, Section } from './Layout'
import { Container, Section, headingScale } from './Layout'
const panelItem = 'grid grid-cols-[34px_1fr] items-start gap-[11px] rounded-[16px] p-[13px]'
const badge = 'grid size-[28px] place-items-center rounded-[10px] text-[11px]'
@@ -27,7 +27,7 @@ export function EconomicsSection() {
<span className="flex items-center gap-[10px] text-[11px] font-[850] tracking-[0.095em] text-econ-teal-ink uppercase before:h-[2px] before:w-[28px] before:rounded-[3px] before:bg-teal before:content-['']">
Экономика действующей клиентской базы
</span>
<h2 className="text-[clamp(31px,5vw,58px)] leading-[1.02] tracking-[-0.048em] text-navy tiny:text-[34px]">
<h2 className={cx('max-w-[900px] text-navy', headingScale)}>
Клуб теряет деньги не на тренировке а между посещениями
</h2>
<p className="max-w-[860px] text-[clamp(15px,1.55vw,19px)] leading-[1.55] text-[#60778B]">
@@ -145,16 +145,28 @@ export function EconomicsSection() {
function RevenueCalculator() {
const { ref, revealClass } = useReveal<HTMLDivElement>()
const [programs, setPrograms] = useState('')
const [check, setCheck] = useState('')
// 2 500 ₽ is the ЭкзоКлиник average quoted in the note under the fields.
const [check, setCheck] = useState('2500')
const [days, setDays] = useState('30')
const [investment, setInvestment] = useState('')
const programsValue = parseAmount(programs)
const checkValue = parseAmount(check)
const daysValue = parseAmount(days)
const investmentValue = parseAmount(investment)
const ready = programsValue > 0 && checkValue > 0 && daysValue > 0
const volume = programsValue * daysValue
const month = volume * checkValue
// Payback and ROI need the investment on top of a ready revenue figure.
const hasInvestment = investmentValue > 0 && month > 0
const payback = hasInvestment
? `${formatDecimal(Math.round((investmentValue / month) * 10) / 10)} мес.`
: 'Укажите инвестиции'
const roi = hasInvestment
? `${formatNumber(Math.round(((month * 12 - investmentValue) / investmentValue) * 100))}%`
: 'Укажите инвестиции'
return (
<div
ref={ref}
@@ -180,8 +192,8 @@ function RevenueCalculator() {
))}
</div>
<div className="grid gap-[13px] md:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)] md:items-end">
<div className="grid grid-cols-2 gap-[9px] sm:grid-cols-3 tiny:grid-cols-[1fr]">
<div className="grid gap-[13px] md:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)] md:items-start">
<div className="grid grid-cols-2 gap-[9px] sm:grid-cols-3 md:grid-cols-2 tiny:grid-cols-[1fr]">
<CalcField
id="fitPrograms"
label="Оплаченных программ восстановления в день"
@@ -212,6 +224,16 @@ function RevenueCalculator() {
max={31}
step={1}
/>
<CalcField
id="fitInvestment"
label="Инвестиции в запуск, ₽"
hint="Нужны только для расчёта ROI и срока возврата инвестиций"
value={investment}
onChange={setInvestment}
placeholder="ваша сумма"
min={0}
step={10000}
/>
</div>
<div className="grid grid-cols-2 gap-[9px] md:self-stretch tiny:grid-cols-[1fr]">
@@ -229,12 +251,34 @@ function RevenueCalculator() {
value={ready ? formatMoney(month * 12) : '—'}
note="до вычета расходов"
/>
<CalcResult
label="Срок возврата инвестиций"
value={ready ? payback : '—'}
note="инвестиции ÷ дополнительная выручка в месяц"
/>
<CalcResult
label="ROI за 12 месяцев"
value={ready ? roi : '—'}
note="сценарный расчёт до операционных расходов"
/>
</div>
</div>
<div className="mt-[14px] rounded-[14px] border border-teal/25 bg-teal/[0.07] px-[15px] py-[13px] text-[10px] leading-[1.5] text-white/[0.72]">
<p>
Средний чек программы составляет <strong className="text-white">2 500 </strong>, по данным нашей клиники
«ЭкзоКлиник» в городе Тольятти за период 20252026гг.
</p>
<p className="mt-[5px]">
<strong className="text-white">
Средний чек программы может меняться в зависимости от региона и сезонных предложений.
</strong>
</p>
</div>
<p className="mt-[13px] text-[9px] leading-[1.45] text-white/[0.43]">
Расчёт показывает сценарную выручку, а не финансовую гарантию. Итоговая модель уточняется по конфигурации
аппаратов, тарифам, ФОТ, загрузке и формату работы Recovery Zone.
Расчёт показывает сценарную выручку и ROI, а не финансовую гарантию. ROI рассчитан без учёта ФОТ, налогов,
аренды, расходников и прочих операционных расходов. Финальная экономика уточняется после аудита.
</p>
</div>
)