docs(site): отчёт и скриншоты Круга 4 (цена не разрывается, 320–402px, Safari)
Автопроверка 44/44 на ширинах 320/375/390/402 × главная/pricing/signup — скриншоты k4-*.png и скрипт site-fixes-verify-4.mjs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
@ -208,3 +208,45 @@ net8.0` (не `net8.0-windows`) + Avalonia (кроссплатформенный
|
|||
Скриншоты: `k3-pricing-1280.png`, `k3-pricing-390.png`, `k3-about-1280.png`.
|
||||
Автопроверка (Playwright): 5/5. Юридические реквизиты не трогал (отложены до
|
||||
продакшна). Прод-сайт не трогал.
|
||||
|
||||
---
|
||||
|
||||
## Круг 4 (прогон этапа 1 в Safari на iPhone 17, ширина 402 — 12.10)
|
||||
|
||||
Круг 3 закрыл разрыв цены **только на /pricing**. Прогон в Safari (симулятор
|
||||
iPhone 17) нашёл ещё два места, где «сумма + ₸ + /мес» рвётся по строкам:
|
||||
|
||||
1. **Главная, строка под кнопками героя** — «Подписка от 5 000 ₸/мес»: «5» на
|
||||
одной строке, «000 ₸/мес» на следующей.
|
||||
2. **/signup после конфигуратора «Бизнес»** — итог «14 000 ₸/мес» рвался после
|
||||
«₸/» (слэш — точка переноса), плитки тарифов — так же.
|
||||
|
||||
Исправлено **системно**, а не точечно (commit `a0f9e0b`, деплой только на тест
|
||||
`test.food-market.kz`, статус `OK`):
|
||||
|
||||
- **`lib/pricing.ts` — единый источник правды:**
|
||||
- `fmtKzt` теперь ставит неразрывные пробелы (U+00A0) между разрядами и перед
|
||||
«₸» → сумма с «₸» никогда не рвётся;
|
||||
- новый `fmtKztMonth` = сумма + «₸» + «/мес» одним куском: word-joiner (U+2060)
|
||||
вокруг «/» запрещает перенос на слэше.
|
||||
- **`SignupForm.tsx`** — плитки «Старт»/«Бизнес» и итог «Ваша конфигурация
|
||||
„Бизнес“» используют `fmtKztMonth` (+ `whitespace-nowrap` на итоге).
|
||||
- **`index.astro`** — hero-строка и карточки «Старт»/«Бизнес» (`whitespace-nowrap`
|
||||
+ ` `); «от» в «Бизнес» остаётся переносимым, цена — нет.
|
||||
- **`BusinessTariffBuilder.tsx`** — подпись «База 10 000 ₸» обёрнута в `nowrap`.
|
||||
- **Контент `kb/billing.md`, `kb/faq.md`, `blog/launch.md`, `FAQ.tsx`** — суммы
|
||||
с nbsp + word-joiner, чтобы цена не рвалась и в тексте статей/ответов.
|
||||
|
||||
**Автопроверка (Playwright, Safari-UA iPhone 17):** ширины **320, 375, 390, 402**
|
||||
× страницы **главная, /pricing, /signup?plan=start, /signup после «Бизнес»** —
|
||||
**44/44 passed**. Высоты строк цен: hero 15px, плитки 16px, итог «Бизнес» 20px,
|
||||
конструктор 36px — везде **одна строка**. Горизонтальной прокрутки нет ни на одной
|
||||
ширине. Текст итога «Бизнес» = `14 000 ₸/мес` (неразрывный).
|
||||
|
||||
Скриншоты (`docs/site-fixes-2026-10-11/`): `k4-home-{320,375,390,402}.png`,
|
||||
`k4-pricing-*.png`, `k4-signup-start-*.png`, `k4-signup-biz-*.png`.
|
||||
Скрипт: `tests/e2e/tools/site-fixes-verify-4.mjs`.
|
||||
|
||||
Остальное в прогоне владельца чисто (меню, «Назад», тарифы, выбор
|
||||
Старт/Бизнес/Сеть, нет горизонтальной прокрутки на 25 страницах) — не трогал.
|
||||
Юридические реквизиты не трогал. Прод-сайт не трогал. Весы — пауза в силе.
|
||||
|
|
|
|||
BIN
docs/site-fixes-2026-10-11/k4-home-320.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
docs/site-fixes-2026-10-11/k4-home-375.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/site-fixes-2026-10-11/k4-home-390.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/site-fixes-2026-10-11/k4-home-402.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/site-fixes-2026-10-11/k4-pricing-320.png
Normal file
|
After Width: | Height: | Size: 541 KiB |
BIN
docs/site-fixes-2026-10-11/k4-pricing-375.png
Normal file
|
After Width: | Height: | Size: 566 KiB |
BIN
docs/site-fixes-2026-10-11/k4-pricing-390.png
Normal file
|
After Width: | Height: | Size: 578 KiB |
BIN
docs/site-fixes-2026-10-11/k4-pricing-402.png
Normal file
|
After Width: | Height: | Size: 568 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-biz-320.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-biz-375.png
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-biz-390.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-biz-402.png
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-start-320.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-start-375.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-start-390.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
docs/site-fixes-2026-10-11/k4-signup-start-402.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
75
tests/e2e/tools/site-fixes-verify-4.mjs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Круг 4: проверка, что «сумма + ₸ + /мес» нигде не разрывается по строкам.
|
||||
// Ширины из прогона владельца в Safari: 320, 375, 390, 402.
|
||||
import { chromium } from '@playwright/test'
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const BASE = process.env.SITE_URL ?? 'https://test.food-market.kz'
|
||||
const OUT = resolve('/home/nns/food-market', 'docs/site-fixes-2026-10-11')
|
||||
mkdirSync(OUT, { recursive: true })
|
||||
const WIDTHS = [320, 375, 390, 402]
|
||||
const results = []
|
||||
const ok = (n, c, d = '') => { results.push({ n, c: !!c, d }); console.log(`${c ? '✓' : '✗'} ${n} ${d}`) }
|
||||
|
||||
// высота одной строки: text-xs ~16px, text-3xl ~36px, text-sm ~20px. Один ряд => ниже порога.
|
||||
const oneLine = (box, max) => box && box.height > 0 && box.height <= max
|
||||
|
||||
const b = await chromium.launch({ args: ['--ignore-certificate-errors'] })
|
||||
|
||||
for (const w of WIDTHS) {
|
||||
const ctx = await b.newContext({
|
||||
viewport: { width: w, height: 900 }, deviceScaleFactor: 2,
|
||||
ignoreHTTPSErrors: true, isMobile: true, hasTouch: true,
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||||
})
|
||||
const p = await ctx.newPage()
|
||||
|
||||
// нет горизонтальной прокрутки
|
||||
const noHScroll = async (label) => {
|
||||
const over = await p.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)
|
||||
ok(`${label}@${w}: нет горизонтальной прокрутки`, over <= 1, `overflow=${over}`)
|
||||
}
|
||||
|
||||
// ---- Главная ----
|
||||
await p.goto(`${BASE}/`, { waitUntil: 'networkidle' }); await p.waitForTimeout(400)
|
||||
// hero-строка: span с «5 000 ₸/мес»
|
||||
const hero = await p.locator('p:has-text("Подписка от") span').first().boundingBox().catch(() => null)
|
||||
ok(`home@${w}: hero «5 000 ₸/мес» одной строкой`, oneLine(hero, 24), hero ? `h=${Math.round(hero.height)}` : 'нет')
|
||||
// карточки тарифов: nowrap-спаны внутри цен
|
||||
const startCard = await p.locator('section:has(h2:has-text("Простые тарифы")) p.text-3xl').first().boundingBox().catch(() => null)
|
||||
ok(`home@${w}: карточка «Старт» цена одной строкой`, oneLine(startCard, 48), startCard ? `h=${Math.round(startCard.height)}` : 'нет')
|
||||
await p.screenshot({ path: resolve(OUT, `k4-home-${w}.png`), fullPage: true })
|
||||
await noHScroll('home')
|
||||
|
||||
// ---- /pricing ----
|
||||
await p.goto(`${BASE}/pricing`, { waitUntil: 'networkidle' }); await p.waitForTimeout(600)
|
||||
const prBuilder = await p.locator('#configurator .font-extrabold').first().boundingBox().catch(() => null)
|
||||
ok(`pricing@${w}: сумма конструктора одной строкой`, oneLine(prBuilder, 48), prBuilder ? `h=${Math.round(prBuilder.height)}` : 'нет')
|
||||
await p.screenshot({ path: resolve(OUT, `k4-pricing-${w}.png`), fullPage: true })
|
||||
await noHScroll('pricing')
|
||||
|
||||
// ---- /signup?plan=start ----
|
||||
await p.goto(`${BASE}/signup?plan=start`, { waitUntil: 'networkidle' }); await p.waitForTimeout(600)
|
||||
const tileStart = await p.locator('label:has-text("Старт") .whitespace-nowrap').first().boundingBox().catch(() => null)
|
||||
ok(`signup-start@${w}: плитка «Старт» цена одной строкой`, oneLine(tileStart, 24), tileStart ? `h=${Math.round(tileStart.height)}` : 'нет')
|
||||
await p.screenshot({ path: resolve(OUT, `k4-signup-start-${w}.png`), fullPage: true })
|
||||
await noHScroll('signup-start')
|
||||
|
||||
// ---- /signup после конфигуратора «Бизнес» (14 000 ₸/мес) ----
|
||||
await p.goto(`${BASE}/signup?plan=business&shops=3&cashes=1&warehouses=1&employees=5`, { waitUntil: 'networkidle' }); await p.waitForTimeout(700)
|
||||
const sumBiz = await p.locator('span.font-bold.whitespace-nowrap').first().boundingBox().catch(() => null)
|
||||
ok(`signup-biz@${w}: итог «14 000 ₸/мес» одной строкой`, oneLine(sumBiz, 24), sumBiz ? `h=${Math.round(sumBiz.height)}` : 'нет')
|
||||
const tileBiz = await p.locator('label:has-text("Бизнес") .whitespace-nowrap').first().boundingBox().catch(() => null)
|
||||
ok(`signup-biz@${w}: плитка «Бизнес» цена одной строкой`, oneLine(tileBiz, 24), tileBiz ? `h=${Math.round(tileBiz.height)}` : 'нет')
|
||||
// проверим, что текст действительно «14 000 ₸/мес»
|
||||
const bizTxt = await p.locator('span.font-bold.whitespace-nowrap').first().innerText().catch(() => '')
|
||||
ok(`signup-biz@${w}: сумма = 14 000 ₸/мес`, /14[\s ]000[\s ]₸.?\/.?мес/.test(bizTxt), JSON.stringify(bizTxt))
|
||||
await p.screenshot({ path: resolve(OUT, `k4-signup-biz-${w}.png`), fullPage: true })
|
||||
await noHScroll('signup-biz')
|
||||
|
||||
await ctx.close()
|
||||
}
|
||||
await b.close()
|
||||
const pass = results.filter(r => r.c).length
|
||||
console.log(`\n[site-verify-4] ${pass}/${results.length} passed → ${OUT}`)
|
||||
process.exit(pass === results.length ? 0 : 1)
|
||||