fix(site): мобильные и UX-правки по тесту (Android/iOS), типографика KB/блога
- Шапка: десктоп-меню с xl (чтобы на ~1050px не ломалось в 2 строки); CTA без переноса; в мобильной панели добавлены «Интеграции», «Статус», «Что нового»; Android «Назад» закрывает панель (pushState/popstate), Esc/тап/крестик остаются. - Конструктор «Бизнес»: ползунки удобнее на тач — крупная ручка, touch-action pan-y (вертикальный скролл не двигает значение), кнопки −/+; бейдж «Популярный» (как на главной) и «90 дней бесплатно». - Кнопка «Настроить тариф» на главной → /pricing#configurator со скроллом к конструктору; на /pricing убран ложный пункт «Импорт из других систем» у «Старт». - Типографика markdown (h2/h3, списки, таблицы, ссылки) вынесена в global.css — /kb/* больше не плоский текст; эмодзи-фолбэк; увеличенная зона нажатия у инлайн-ссылок; снят дублирующий inline-стиль в blog/[slug]. - Футер: «Status» → «Статус». Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5f2188a7f9
commit
6f229ebecc
|
|
@ -10,16 +10,24 @@ interface SliderProps {
|
|||
label: string; value: number; min: number; max: number; onChange: (v: number) => void; suffix?: string
|
||||
}
|
||||
function Slider({ label, value, min, max, onChange, suffix }: SliderProps) {
|
||||
const dec = () => onChange(Math.max(min, value - 1))
|
||||
const inc = () => onChange(Math.min(max, value + 1))
|
||||
const btn = 'w-9 h-9 shrink-0 rounded-md border border-slate-300 text-xl leading-none text-slate-700 flex items-center justify-center hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-transparent'
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-baseline justify-between">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<label className="text-sm font-medium">{label}</label>
|
||||
<span className="text-2xl font-bold tabular-nums">{value}{suffix && <span className="text-sm text-slate-500">{suffix}</span>}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button" onClick={dec} disabled={value <= min} aria-label={`Уменьшить: ${label}`} className={btn}>−</button>
|
||||
<span className="text-2xl font-bold tabular-nums w-10 text-center">{value}{suffix && <span className="text-sm text-slate-500">{suffix}</span>}</span>
|
||||
<button type="button" onClick={inc} disabled={value >= max} aria-label={`Увеличить: ${label}`} className={btn}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="range" min={min} max={max} value={value}
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
className="w-full mt-2 accent-[#00B207]"
|
||||
className="fm-range w-full mt-2 accent-[#00B207]"
|
||||
style={{ touchAction: 'pan-y' }}
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-slate-400 mt-1"><span>{min}</span><span>{max}</span></div>
|
||||
</div>
|
||||
|
|
@ -38,10 +46,11 @@ export default function BusinessTariffBuilder() {
|
|||
const signupHref = `/signup?plan=business&shops=${shops}&cashes=${cashes}&warehouses=${warehouses}&employees=${employees}`
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border-2 border-brand bg-white p-6">
|
||||
<div className="relative rounded-xl border-2 border-brand bg-white p-6">
|
||||
<span className="absolute -top-3 left-1/2 -translate-x-1/2 bg-brand text-white text-xs px-2 py-0.5 rounded-full">Популярный</span>
|
||||
<div className="flex items-baseline justify-between mb-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">Конструктор «Бизнес»</h3>
|
||||
<h3 className="text-lg font-bold">Конструктор «Бизнес»</h3>
|
||||
<p className="text-xs text-slate-500 mt-0.5">База 10 000 ₸ + ваши параметры</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
|
|
@ -63,6 +72,7 @@ export default function BusinessTariffBuilder() {
|
|||
<a href={signupHref} className="block text-center mt-6 px-4 py-3 bg-brand text-white rounded-md font-semibold hover:bg-[#009305]">
|
||||
Выбрать конфигурацию · {fmt.format(total)} ₸/мес
|
||||
</a>
|
||||
<p className="text-xs text-center text-slate-500 mt-2">90 дней бесплатно, без карты</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import Logo from './Logo.astro'
|
|||
<ul class="space-y-1.5">
|
||||
<li><a href="/kb" class="block py-2 text-slate-700 hover:text-brand">База знаний</a></li>
|
||||
<li><a href="/blog" class="block py-2 text-slate-700 hover:text-brand">Блог</a></li>
|
||||
<li><a href="/status" class="block py-2 text-slate-700 hover:text-brand">Status</a></li>
|
||||
<li><a href="/status" class="block py-2 text-slate-700 hover:text-brand">Статус</a></li>
|
||||
<li><a href="/changelog" class="block py-2 text-slate-700 hover:text-brand">Changelog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,27 +6,33 @@ const nav = [
|
|||
{ href: '/pos', label: 'Касса' },
|
||||
{ href: '/for-grocery', label: 'Для продуктовых' },
|
||||
{ href: '/import', label: 'Импорт' },
|
||||
{ href: '/integrations', label: 'Интеграции' },
|
||||
{ href: '/pricing', label: 'Тарифы' },
|
||||
{ href: '/kb', label: 'База знаний' },
|
||||
{ href: '/blog', label: 'Блог' },
|
||||
]
|
||||
// Вторичные ссылки — внизу мобильной панели (в десктоп-навигацию не выносим).
|
||||
const secondary = [
|
||||
{ href: '/status', label: 'Статус' },
|
||||
{ href: '/changelog', label: 'Что нового' },
|
||||
]
|
||||
---
|
||||
<header class="sticky top-0 z-40 bg-white/95 backdrop-blur border-b border-slate-200">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between gap-4">
|
||||
<a href="/" class="flex items-center gap-2"><Logo /></a>
|
||||
|
||||
<nav class="hidden lg:flex items-center gap-1 text-sm">
|
||||
<nav class="hidden xl:flex items-center gap-1 text-sm">
|
||||
{nav.map((i) => <a href={i.href} class="px-3 py-2 hover:text-brand transition">{i.label}</a>)}
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a href={`${APP_URL}/login`} class="px-3 min-h-[44px] flex items-center text-sm text-slate-700 hover:text-brand">Войти</a>
|
||||
<a href="/signup" class="px-3 min-h-[44px] flex items-center text-sm font-medium bg-brand text-white rounded-md hover:bg-brand-hover">Начать бесплатно</a>
|
||||
<!-- Гамбургер: только на планшете/мобильном (десктоп-меню скрыто < lg) -->
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<a href={`${APP_URL}/login`} class="hidden sm:flex px-3 min-h-[44px] items-center text-sm text-slate-700 hover:text-brand whitespace-nowrap">Войти</a>
|
||||
<a href="/signup" class="px-3 min-h-[44px] flex items-center text-sm font-medium bg-brand text-white rounded-md hover:bg-brand-hover whitespace-nowrap">Начать бесплатно</a>
|
||||
<!-- Гамбургер: планшет/мобильный и средний десктоп (полное меню — от xl) -->
|
||||
<button
|
||||
id="mobile-menu-btn"
|
||||
type="button"
|
||||
class="lg:hidden inline-flex items-center justify-center w-11 h-11 -mr-1 rounded-md text-slate-700 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-brand"
|
||||
class="xl:hidden inline-flex items-center justify-center w-11 h-11 -mr-1 rounded-md text-slate-700 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-brand"
|
||||
aria-label="Меню"
|
||||
aria-controls="mobile-menu"
|
||||
aria-expanded="false"
|
||||
|
|
@ -45,6 +51,9 @@ const nav = [
|
|||
<a href={`${APP_URL}/login`} class="flex items-center justify-center min-h-[44px] px-3 rounded-md text-sm font-medium border border-slate-300 text-slate-700 hover:bg-slate-50">Войти</a>
|
||||
<a href="/signup" class="flex items-center justify-center min-h-[44px] px-3 rounded-md text-sm font-medium bg-brand text-white hover:bg-brand-hover">Начать бесплатно</a>
|
||||
</div>
|
||||
<div class="mt-2 pt-2 border-t border-slate-100 flex flex-wrap gap-x-4">
|
||||
{secondary.map((i) => <a href={i.href} class="flex items-center min-h-[44px] px-3 rounded-md text-sm text-slate-500 hover:text-brand">{i.label}</a>)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
@ -55,16 +64,32 @@ const nav = [
|
|||
const iconOpen = document.getElementById('mobile-menu-icon-open')
|
||||
const iconClose = document.getElementById('mobile-menu-icon-close')
|
||||
if (btn && menu) {
|
||||
const setOpen = (open: boolean) => {
|
||||
// Пока панель открыта — в истории висит наша запись, чтобы системная кнопка
|
||||
// «Назад» (Android) закрывала меню, а не уводила со страницы.
|
||||
let pushed = false
|
||||
const setOpen = (open: boolean, fromPop = false) => {
|
||||
menu.classList.toggle('hidden', !open)
|
||||
btn.setAttribute('aria-expanded', String(open))
|
||||
iconOpen?.classList.toggle('hidden', open)
|
||||
iconClose?.classList.toggle('hidden', !open)
|
||||
if (open) {
|
||||
if (!pushed) { history.pushState({ menu: true }, ''); pushed = true }
|
||||
} else if (pushed && !fromPop) {
|
||||
// закрыли сами (Esc/крестик/тап/resize) — снимаем запись из истории
|
||||
pushed = false
|
||||
history.back()
|
||||
} else {
|
||||
pushed = false
|
||||
}
|
||||
}
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(menu.classList.contains('hidden'))
|
||||
})
|
||||
// системная кнопка «Назад» / popstate — закрыть панель
|
||||
window.addEventListener('popstate', () => {
|
||||
if (!menu.classList.contains('hidden')) setOpen(false, true)
|
||||
})
|
||||
// закрытие по тапу вне меню
|
||||
document.addEventListener('click', (e) => {
|
||||
if (menu.classList.contains('hidden')) return
|
||||
|
|
@ -76,6 +101,6 @@ const nav = [
|
|||
if (e.key === 'Escape' && !menu.classList.contains('hidden')) { setOpen(false); (btn as HTMLButtonElement).focus() }
|
||||
})
|
||||
// при переходе к десктопной ширине — сбросить состояние
|
||||
window.matchMedia('(min-width: 1024px)').addEventListener('change', (m) => { if (m.matches) setOpen(false) })
|
||||
window.matchMedia('(min-width: 1280px)').addEventListener('change', (m) => { if (m.matches && !menu.classList.contains('hidden')) setOpen(false) })
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -24,19 +24,3 @@ const fmt = post.data.date.toLocaleDateString('ru-KZ', { day: '2-digit', month:
|
|||
<div class="prose-md mt-8"><Content /></div>
|
||||
</article>
|
||||
</BaseLayout>
|
||||
<style is:global>
|
||||
.prose-md { font-size: 16px; line-height: 1.75; color: #1e293b; }
|
||||
.prose-md h2 { font-size: 1.5rem; font-weight: 700; margin-top: 2rem; margin-bottom: 0.75rem; }
|
||||
.prose-md h3 { font-size: 1.125rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.5rem; }
|
||||
.prose-md p { margin: 1rem 0; }
|
||||
.prose-md ul, .prose-md ol { margin: 1rem 0 1rem 1.5rem; }
|
||||
.prose-md li { margin: 0.35rem 0; }
|
||||
.prose-md a { color: #00B207; text-decoration: underline; }
|
||||
.prose-md blockquote { border-left: 3px solid #00B207; background: #E8F5E9; padding: 0.75rem 1rem; margin: 1.25rem 0; font-style: italic; color: #1e293b; }
|
||||
.prose-md code { background: #f1f5f9; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; }
|
||||
.prose-md hr { margin: 2rem 0; border: 0; border-top: 1px solid #e2e8f0; }
|
||||
.prose-md strong { font-weight: 600; }
|
||||
.prose-md table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.95rem; }
|
||||
.prose-md th, .prose-md td { border: 1px solid #e2e8f0; padding: 0.5rem 0.75rem; text-align: left; }
|
||||
.prose-md th { background: #f8fafc; font-weight: 600; }
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -22,13 +22,12 @@ import BusinessTariffBuilder from '@/components/BusinessTariffBuilder.tsx'
|
|||
<li>✓ 2 сотрудника</li>
|
||||
<li>✓ Без лимита товаров</li>
|
||||
<li>✓ Касса с поддержкой торговых весов</li>
|
||||
<li>✓ Импорт из других систем</li>
|
||||
<li>✓ Перенос каталога из CSV и МойСклад</li>
|
||||
<li>✓ Перенос каталога из файлов Excel/CSV</li>
|
||||
</ul>
|
||||
<a href="/signup?plan=start" class="block text-center mt-6 px-4 py-2.5 bg-brand text-white rounded-md font-semibold">Начать</a>
|
||||
</div>
|
||||
|
||||
<div class="lg:row-span-2">
|
||||
<div id="configurator" class="lg:row-span-2 scroll-mt-20">
|
||||
<BusinessTariffBuilder client:load />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,3 +10,36 @@
|
|||
|
||||
html { scroll-behavior: smooth; }
|
||||
body { background: #fff; color: #0f172a; }
|
||||
|
||||
/* Эмодзи-фолбэк: чтобы иконки-эмодзи не рисовались квадратами там, где нет цветного
|
||||
emoji-шрифта (отдельные окружения/симуляторы). */
|
||||
:root { --emoji: 'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji','Twemoji Mozilla',sans-serif; }
|
||||
body { font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji', sans-serif; }
|
||||
|
||||
/* Типографика markdown-контента (База знаний и блог) — ОДИН источник на весь сайт
|
||||
(раньше стили были только на странице блога → /kb/* рисовались плоским текстом). */
|
||||
.prose-md { font-size: 16px; line-height: 1.75; color: #1e293b; }
|
||||
.prose-md h2 { font-size: 1.5rem; font-weight: 700; margin-top: 2rem; margin-bottom: 0.75rem; }
|
||||
.prose-md h3 { font-size: 1.125rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.5rem; }
|
||||
.prose-md p { margin: 1rem 0; }
|
||||
.prose-md ul { list-style: disc; margin: 1rem 0 1rem 1.5rem; }
|
||||
.prose-md ol { list-style: decimal; margin: 1rem 0 1rem 1.5rem; }
|
||||
.prose-md li { margin: 0.35rem 0; }
|
||||
.prose-md a { color: #00B207; text-decoration: underline; }
|
||||
.prose-md blockquote { border-left: 3px solid #00B207; background: #E8F5E9; padding: 0.75rem 1rem; margin: 1.25rem 0; font-style: italic; color: #1e293b; }
|
||||
.prose-md code { background: #f1f5f9; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; }
|
||||
.prose-md hr { margin: 2rem 0; border: 0; border-top: 1px solid #e2e8f0; }
|
||||
.prose-md strong { font-weight: 600; }
|
||||
.prose-md table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.95rem; }
|
||||
.prose-md th, .prose-md td { border: 1px solid #e2e8f0; padding: 0.5rem 0.75rem; text-align: left; }
|
||||
.prose-md th { background: #f8fafc; font-weight: 600; }
|
||||
/* Увеличенная зона нажатия для инлайн-ссылок (мобильный) без сдвига вёрстки. */
|
||||
.prose-md a { padding-block: 3px; margin-block: -3px; }
|
||||
|
||||
/* Ползунки конструктора «Бизнес»: крупная ручка (удобно пальцем) + вертикальный
|
||||
скролл страницы не двигает значение (touch-action задан в компоненте). */
|
||||
.fm-range { height: 2.25rem; background: transparent; cursor: pointer; }
|
||||
.fm-range::-webkit-slider-runnable-track { height: 6px; border-radius: 9999px; background: #e2e8f0; }
|
||||
.fm-range::-moz-range-track { height: 6px; border-radius: 9999px; background: #e2e8f0; }
|
||||
.fm-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 28px; height: 28px; margin-top: -11px; border-radius: 9999px; background: #00B207; border: 3px solid #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
|
||||
.fm-range::-moz-range-thumb { width: 28px; height: 28px; border-radius: 9999px; background: #00B207; border: 3px solid #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue