buhapp-telegram/public/onboarding/preview.html
BuhApp Dev 149f31d25d Add user onboarding flow (3-slide welcome) for new signups
- New OnboardingView module (public/js/views/onboarding.js):
  • Reads /onboarding/slides.json (designer schema with inline SVG icons,
    per-slide cta_primary/cta_skip labels) — falls back to 3 emoji slides
    if the JSON is unavailable.
  • Renders a Telegram-style dark overlay inside #mobile-frame with a
    220px floating SVG icon, title, body, dot indicator (expandable
    active dot), primary CTA, and a ghost Skip button.
  • Prev/Next via primary CTA; dot click jumps to slide; arrow keys and
    horizontal swipe also navigate.
  • Haptic feedback via TG_APP on slide change + finish.
  • markDone() writes buhapp.onboarded='1' to localStorage so the flow
    never repeats. isDone()/reset() exposed for QA.

- App integration (public/js/app.js):
  • New App.maybeOnboard() helper — awaited after every successful auth
    path: Telegram login, restored session, email login, email register,
    and consent accept. Skips silently if already onboarded.

- UI assets (public/onboarding/):
  • slides.json with 3 designer slides (Map / Chat / Review).
  • Inline-SVG icons for map, chat, and review (icon-*.svg kept for
    designer reference, preview.html too).

- Styles (public/css/app.css): new .onboarding-* dark-theme rules.
- Index (public/index.html): loads onboarding.js before app.js.
2026-08-20 20:20:54 +00:00

492 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#FF6B35">
<title>BuhApp · Онбординг (превью)</title>
<style>
:root {
--primary: #FF6B35;
--primary-soft: #FFE2D3;
--primary-dark: #E25A28;
--bg-dark: #1C1C1E;
--bg-elev: #F5F5F7;
--text: #111114;
--text-dim: #6B6B73;
--border: #E0E0E5;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: radial-gradient(1200px 600px at 20% 0%, #2A2A2E 0%, #1C1C1E 60%, #141416 100%);
color: #fff;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.preview-header {
width: 100%; max-width: 980px; margin-bottom: 24px;
display: flex; align-items: center; justify-content: space-between;
color: rgba(255,255,255,0.65); font-size: 13px;
}
.preview-header b { color: #fff; font-weight: 600; }
.badge-tg {
display: inline-flex; align-items: center; gap: 6px;
background: rgba(255,255,255,0.08); padding: 4px 10px; border-radius: 999px;
font-size: 12px;
}
.badge-tg::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: #2AABEE; }
.device {
width: 390px; max-width: 100%; height: 760px;
background: var(--bg-dark);
border-radius: 44px;
box-shadow:
0 0 0 10px #0d0d0f,
0 0 0 11px #2a2a2e,
0 30px 80px rgba(0,0,0,0.55),
0 10px 30px rgba(255,107,53,0.12);
overflow: hidden;
position: relative;
display: flex; flex-direction: column;
}
.status-bar {
height: 44px;
display: flex; align-items: center; justify-content: space-between;
padding: 0 28px;
font-size: 14px; font-weight: 600; color: #fff; background: transparent;
}
.status-bar .right { display: flex; align-items: center; gap: 6px; }
.notch {
position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
width: 110px; height: 28px; background: #0a0a0c; border-radius: 20px;
}
.onboarding {
flex: 1;
display: flex; flex-direction: column;
padding: 12px 28px 24px; position: relative;
}
.slides { flex: 1; position: relative; }
.slide {
position: absolute; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
text-align: center;
padding: 8px 0 0;
opacity: 0;
transform: translateX(20px);
transition: opacity 320ms ease, transform 360ms cubic-bezier(.2,.7,.2,1);
pointer-events: none;
}
.slide.is-active {
opacity: 1; transform: translateX(0); pointer-events: auto;
}
.slide__icon {
width: 220px; height: 220px;
display: flex; align-items: center; justify-content: center;
margin-bottom: 28px;
filter: drop-shadow(0 16px 36px rgba(0,0,0,0.35));
animation: float 6s ease-in-out infinite;
}
.slide:nth-of-type(2) .slide__icon.is-anim { animation-delay: -2s; }
.slide:nth-of-type(3) .slide__icon.is-anim { animation-delay: -4s; }
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.slide__title {
font-size: 26px; font-weight: 700; letter-spacing: -0.4px;
margin-bottom: 12px; max-width: 320px;
}
.slide__body {
font-size: 15px; line-height: 1.45;
color: rgba(255,255,255,0.75); max-width: 300px;
}
.dots {
display: flex; justify-content: center; gap: 8px;
margin-top: 18px; margin-bottom: 18px;
}
.dot {
width: 8px; height: 8px; border-radius: 50%;
background: rgba(255,255,255,0.25);
border: 0; padding: 0; cursor: pointer;
transition: width 240ms ease, background 240ms ease;
}
.dot.is-active { width: 22px; border-radius: 4px; background: var(--primary); }
.actions { display: flex; flex-direction: column; gap: 10px; }
.btn {
appearance: none; border: 0; cursor: pointer;
font-family: inherit; font-size: 16px; font-weight: 600;
border-radius: 14px; padding: 16px 20px;
transition: transform 120ms ease, background 200ms ease;
}
.btn:active { transform: scale(0.98); }
.btn--primary {
background: var(--primary); color: #fff;
box-shadow: 0 6px 16px rgba(255,107,53,0.35);
}
.btn--primary:hover { background: var(--primary-dark); }
.btn--ghost { background: transparent; color: rgba(255,255,255,0.65); }
.btn--ghost:hover { color: #fff; }
.caption {
margin-top: 18px; color: rgba(255,255,255,0.5); font-size: 12px; text-align: center;
}
@media (max-width: 460px) {
.device { border-radius: 0; box-shadow: none; height: 100vh; width: 100%; }
.preview-header { display: none; }
.notch { display: none; }
}
</style>
</head>
<body>
<header class="preview-header">
<span><b>BuhApp · Онбординг</b> — превью в стиле Telegram WebApp</span>
<span class="badge-tg">mobile · iOS-like</span>
</header>
<div class="device" role="application" aria-label="Онбординг BuhApp">
<div class="notch"></div>
<div class="status-bar">
<span>9:41</span>
<span class="right">
<svg width="16" height="10" viewBox="0 0 16 10"><path d="M1 9h2V6H1v3zm4 0h2V4H5v5zm4 0h2V2H9v7zm4-9h2v10h-2V0z" fill="#fff"/></svg>
<svg width="14" height="10" viewBox="0 0 14 10"><path d="M7 2c2 0 4 1 5 2.5L14 3C12 1 9.5 0 7 0S2 1 0 3l2 1.5C3 3 5 2 7 2z" fill="#fff"/></svg>
<svg width="22" height="10" viewBox="0 0 22 10"><rect x="0" y="1" width="18" height="8" rx="2" fill="none" stroke="#fff" stroke-width="1"/><rect x="2" y="3" width="12" height="4" rx="1" fill="#fff"/><rect x="19" y="3" width="2" height="4" rx="0.5" fill="#fff"/></svg>
</span>
</div>
<main class="onboarding">
<div class="slides">
<section class="slide is-active" data-index="0" aria-hidden="false"><div class="slide__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="240" height="240" role="img" aria-label="Карта с геолокацией">
<defs>
<!-- Градиент фона карточки -->
<linearGradient id="mapBg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#FF8A5C"/>
<stop offset="100%" stop-color="#FF6B35"/>
</linearGradient>
<!-- Тень под пин-маркером -->
<radialGradient id="pinShadow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#000" stop-opacity="0.35"/>
<stop offset="70%" stop-color="#000" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#000" stop-opacity="0"/>
</radialGradient>
<!-- Свечение вокруг пина -->
<radialGradient id="pinGlow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#FF6B35" stop-opacity="0.45"/>
<stop offset="100%" stop-color="#FF6B35" stop-opacity="0"/>
</radialGradient>
<!-- Мягкий блик на "карте" -->
<linearGradient id="mapSheen" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.18"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- Внешний круг-подложка (бренд) -->
<circle cx="120" cy="120" r="112" fill="url(#mapBg)"/>
<circle cx="120" cy="120" r="112" fill="url(#mapSheen)"/>
<!-- Декоративные точки вокруг -->
<circle cx="46" cy="62" r="4" fill="#FFFFFF" opacity="0.45"/>
<circle cx="196" cy="58" r="3" fill="#FFFFFF" opacity="0.55"/>
<circle cx="58" cy="188" r="3" fill="#FFFFFF" opacity="0.5"/>
<circle cx="200" cy="184" r="4" fill="#FFFFFF" opacity="0.4"/>
<circle cx="34" cy="124" r="2.5" fill="#FFFFFF" opacity="0.5"/>
<circle cx="210" cy="130" r="2.5" fill="#FFFFFF" opacity="0.5"/>
<!-- "Карта" — белая карточка со скруглёнными краями -->
<g transform="translate(36 44)">
<rect x="0" y="0" width="168" height="152" rx="20" ry="20" fill="#FFFFFF"/>
<!-- Сетка улиц (приглушённая) -->
<g stroke="#FFE2D3" stroke-width="2" stroke-linecap="round">
<line x1="0" y1="32" x2="168" y2="32"/>
<line x1="0" y1="62" x2="168" y2="62"/>
<line x1="0" y1="96" x2="168" y2="96"/>
<line x1="0" y1="126" x2="168" y2="126"/>
<line x1="40" y1="0" x2="40" y2="152"/>
<line x1="84" y1="0" x2="84" y2="152"/>
<line x1="128" y1="0" x2="128" y2="152"/>
</g>
<!-- Главная дорога — оранжевая -->
<path d="M0 78 Q 60 70 84 80 T 168 76" stroke="#FFB58A" stroke-width="6" fill="none" stroke-linecap="round"/>
<!-- Река/парк -->
<path d="M0 110 Q 40 100 70 112 T 168 108" stroke="#FFD8C2" stroke-width="8" fill="none" stroke-linecap="round" opacity="0.7"/>
<!-- Маленькие здания -->
<rect x="14" y="14" width="14" height="10" rx="2" fill="#F5F5F7"/>
<rect x="50" y="42" width="16" height="12" rx="2" fill="#F5F5F7"/>
<rect x="100" y="16" width="12" height="9" rx="2" fill="#F5F5F7"/>
<rect x="138" y="42" width="18" height="12" rx="2" fill="#F5F5F7"/>
<rect x="14" y="130" width="14" height="12" rx="2" fill="#F5F5F7"/>
<rect x="100" y="132" width="14" height="10" rx="2" fill="#F5F5F7"/>
<rect x="140" y="128" width="16" height="14" rx="2" fill="#F5F5F7"/>
</g>
<!-- Свечение вокруг пина -->
<circle cx="120" cy="118" r="58" fill="url(#pinGlow)"/>
<!-- Тень под пином -->
<ellipse cx="120" cy="186" rx="14" ry="4" fill="url(#pinShadow)"/>
<!-- Пин-маркер (большой, по центру) -->
<g transform="translate(120 118)">
<!-- Внешняя капля -->
<path d="M0 -42 C 16 -42 24 -28 24 -16 C 24 -2 12 12 0 28 C -12 12 -24 -2 -24 -16 C -24 -28 -16 -42 0 -42 Z"
fill="#1C1C1E"/>
<!-- Внутренний круг -->
<circle cx="0" cy="-16" r="11" fill="#FF6B35"/>
<!-- Белая точка по центру -->
<circle cx="0" cy="-16" r="4" fill="#FFFFFF"/>
</g>
<!-- Маленькие "компании рядом" — аватары-пины -->
<g transform="translate(64 156)">
<circle cx="0" cy="0" r="14" fill="#1C1C1E"/>
<circle cx="0" cy="0" r="11" fill="#FFD8C2"/>
<circle cx="0" cy="-2" r="3.5" fill="#1C1C1E"/>
<path d="M-7 7 Q 0 1 7 7 L 7 9 L -7 9 Z" fill="#1C1C1E"/>
</g>
<g transform="translate(176 154)">
<circle cx="0" cy="0" r="14" fill="#1C1C1E"/>
<circle cx="0" cy="0" r="11" fill="#FFFFFF"/>
<circle cx="0" cy="-2" r="3.5" fill="#FF6B35"/>
<path d="M-7 7 Q 0 1 7 7 L 7 9 L -7 9 Z" fill="#FF6B35"/>
</g>
<!-- Радиус-дуга "рядом" -->
<path d="M 64 156 A 60 60 0 0 1 176 154" stroke="#FFFFFF" stroke-width="2" stroke-dasharray="3 4" fill="none" opacity="0.55"/>
</svg>
</div><h2 class="slide__title">Найди компанию рядом</h2><p class="slide__body">Открой карту — увидишь, кто из BuhApp находится поблизости. Геолокация работает прямо в Telegram, без отдельных приложений.</p></section>
<section class="slide " data-index="1" aria-hidden="true"><div class="slide__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="240" height="240" role="img" aria-label="Чат с сообщениями">
<defs>
<linearGradient id="chatBg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#FF8A5C"/>
<stop offset="100%" stop-color="#FF6B35"/>
</linearGradient>
<linearGradient id="bubbleMine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#F5F5F7"/>
</linearGradient>
<linearGradient id="bubbleSheen" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.25"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="circleSheen" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.18"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- Внешний круг -->
<circle cx="120" cy="120" r="112" fill="url(#chatBg)"/>
<circle cx="120" cy="120" r="112" fill="url(#circleSheen)"/>
<!-- Декор — точки -->
<circle cx="40" cy="58" r="4" fill="#FFFFFF" opacity="0.45"/>
<circle cx="200" cy="60" r="3" fill="#FFFFFF" opacity="0.55"/>
<circle cx="36" cy="180" r="3" fill="#FFFFFF" opacity="0.5"/>
<circle cx="206" cy="182" r="4" fill="#FFFFFF" opacity="0.4"/>
<circle cx="208" cy="118" r="2.5" fill="#FFFFFF" opacity="0.5"/>
<circle cx="32" cy="120" r="2.5" fill="#FFFFFF" opacity="0.5"/>
<!-- Третий пузырь (ответ собеседника, сверху, тёмный) -->
<g transform="translate(40 50)">
<path d="M14 0 L 92 0 Q 106 0 106 14 L 106 38 Q 106 52 92 52 L 28 52 L 14 64 L 18 52 Q 14 52 14 38 Z"
fill="#1C1C1E"/>
<!-- Строки текста -->
<rect x="24" y="14" width="62" height="6" rx="3" fill="#FFFFFF" opacity="0.85"/>
<rect x="24" y="26" width="46" height="6" rx="3" fill="#FFFFFF" opacity="0.55"/>
<!-- Хвостик-таймстамп -->
<circle cx="96" cy="46" r="3" fill="#FF6B35"/>
</g>
<!-- Второй пузырь (ответ собеседника, ниже) -->
<g transform="translate(40 124)">
<path d="M14 0 L 100 0 Q 114 0 114 14 L 114 32 Q 114 46 100 46 L 26 46 L 14 58 L 18 46 Q 14 46 14 32 Z"
fill="#1C1C1E"/>
<rect x="24" y="14" width="68" height="6" rx="3" fill="#FFFFFF" opacity="0.85"/>
<rect x="24" y="26" width="38" height="6" rx="3" fill="#FFFFFF" opacity="0.55"/>
</g>
<!-- Первый пузырь (наш, справа, белый, крупный) -->
<g transform="translate(112 60)">
<path d="M92 0 L 14 0 Q 0 0 0 14 L 0 42 Q 0 56 14 56 L 78 56 L 92 70 L 88 56 Q 92 56 92 42 Z"
fill="url(#bubbleMine)"/>
<path d="M92 0 L 14 0 Q 0 0 0 14 L 0 42 Q 0 56 14 56 L 78 56 L 92 70 L 88 56 Q 92 56 92 42 Z"
fill="url(#bubbleSheen)"/>
<!-- Строки текста -->
<rect x="14" y="14" width="64" height="6" rx="3" fill="#1C1C1E" opacity="0.85"/>
<rect x="14" y="26" width="48" height="6" rx="3" fill="#1C1C1E" opacity="0.55"/>
<!-- Статус "прочитано" — две галочки оранжевые -->
<g transform="translate(72 38)">
<path d="M0 4 L 4 8 L 12 0" stroke="#FF6B35" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 4 L 10 8 L 18 0" stroke="#FF6B35" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
<!-- Маленький индикатор "набирает..." -->
<g transform="translate(54 196)">
<circle cx="0" cy="0" r="4" fill="#FFFFFF" opacity="0.9"/>
<circle cx="12" cy="0" r="4" fill="#FFFFFF" opacity="0.6"/>
<circle cx="24" cy="0" r="4" fill="#FFFFFF" opacity="0.35"/>
</g>
</svg>
</div><h2 class="slide__title">Напиши первым</h2><p class="slide__body">В чате можно задать вопрос, договориться о встрече или обменяться контактами. Сообщения доходят мгновенно — как в обычном мессенджере.</p></section>
<section class="slide " data-index="2" aria-hidden="true"><div class="slide__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="240" height="240" role="img" aria-label="Отзыв со звёздами">
<defs>
<linearGradient id="revBg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#FF8A5C"/>
<stop offset="100%" stop-color="#FF6B35"/>
</linearGradient>
<linearGradient id="starFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFE9D6"/>
<stop offset="100%" stop-color="#FFCB9C"/>
</linearGradient>
<linearGradient id="starFillOrange" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#FFF1E6"/>
</linearGradient>
<linearGradient id="cardSheen" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.18"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- Внешний круг -->
<circle cx="120" cy="120" r="112" fill="url(#revBg)"/>
<circle cx="120" cy="120" r="112" fill="url(#cardSheen)"/>
<!-- Декор — точки -->
<circle cx="44" cy="56" r="4" fill="#FFFFFF" opacity="0.45"/>
<circle cx="198" cy="58" r="3" fill="#FFFFFF" opacity="0.55"/>
<circle cx="36" cy="186" r="3" fill="#FFFFFF" opacity="0.5"/>
<circle cx="204" cy="184" r="4" fill="#FFFFFF" opacity="0.4"/>
<circle cx="32" cy="120" r="2.5" fill="#FFFFFF" opacity="0.5"/>
<circle cx="210" cy="120" r="2.5" fill="#FFFFFF" opacity="0.5"/>
<!-- Звёзды (центральная композиция) -->
<!-- Звезда — пятиконечная, выровненная по центру -->
<g transform="translate(120 102)">
<!-- Большая звезда по центру -->
<g transform="translate(0 -38) scale(1.45)">
<path d="M0 -16 L 4.7 -4.9 L 16.8 -3.1 L 7.6 4.7 L 10.4 16.6 L 0 10.4 L -10.4 16.6 L -7.6 4.7 L -16.8 -3.1 L -4.7 -4.9 Z"
fill="url(#starFill)" stroke="#1C1C1E" stroke-width="2.2" stroke-linejoin="round"/>
</g>
<!-- Левая -->
<g transform="translate(-44 -30) scale(1.1)">
<path d="M0 -16 L 4.7 -4.9 L 16.8 -3.1 L 7.6 4.7 L 10.4 16.6 L 0 10.4 L -10.4 16.6 L -7.6 4.7 L -16.8 -3.1 L -4.7 -4.9 Z"
fill="url(#starFill)" stroke="#1C1C1E" stroke-width="2" stroke-linejoin="round"/>
</g>
<!-- Правая -->
<g transform="translate(44 -30) scale(1.1)">
<path d="M0 -16 L 4.7 -4.9 L 16.8 -3.1 L 7.6 4.7 L 10.4 16.6 L 0 10.4 L -10.4 16.6 L -7.6 4.7 L -16.8 -3.1 L -4.7 -4.9 Z"
fill="url(#starFill)" stroke="#1C1C1E" stroke-width="2" stroke-linejoin="round"/>
</g>
<!-- Левая нижняя -->
<g transform="translate(-22 4) scale(0.95)">
<path d="M0 -16 L 4.7 -4.9 L 16.8 -3.1 L 7.6 4.7 L 10.4 16.6 L 0 10.4 L -10.4 16.6 L -7.6 4.7 L -16.8 -3.1 L -4.7 -4.9 Z"
fill="url(#starFill)" stroke="#1C1C1E" stroke-width="2" stroke-linejoin="round"/>
</g>
<!-- Правая нижняя -->
<g transform="translate(22 4) scale(0.95)">
<path d="M0 -16 L 4.7 -4.9 L 16.8 -3.1 L 7.6 4.7 L 10.4 16.6 L 0 10.4 L -10.4 16.6 L -7.6 4.7 L -16.8 -3.1 L -4.7 -4.9 Z"
fill="url(#starFill)" stroke="#1C1C1E" stroke-width="2" stroke-linejoin="round"/>
</g>
</g>
<!-- Карточка отзыва внизу -->
<g transform="translate(40 140)">
<!-- Тень -->
<rect x="2" y="3" width="160" height="62" rx="14" fill="#000" opacity="0.18"/>
<!-- Тело карточки -->
<rect x="0" y="0" width="160" height="62" rx="14" fill="#FFFFFF"/>
<!-- Аватар -->
<circle cx="18" cy="20" r="12" fill="#FF6B35"/>
<circle cx="18" cy="17" r="4" fill="#FFFFFF"/>
<path d="M10 28 Q 18 22 26 28 L 26 32 L 10 32 Z" fill="#FFFFFF"/>
<!-- Имя -->
<rect x="36" y="13" width="68" height="6" rx="3" fill="#1C1C1E" opacity="0.85"/>
<!-- Строки отзыва -->
<rect x="12" y="42" width="136" height="5" rx="2.5" fill="#1C1C1E" opacity="0.35"/>
<rect x="12" y="51" width="96" height="5" rx="2.5" fill="#1C1C1E" opacity="0.35"/>
<!-- Маленькая звёздочка справа сверху -->
<g transform="translate(140 12) scale(0.45)">
<path d="M0 -16 L 4.7 -4.9 L 16.8 -3.1 L 7.6 4.7 L 10.4 16.6 L 0 10.4 L -10.4 16.6 L -7.6 4.7 L -16.8 -3.1 L -4.7 -4.9 Z"
fill="#FF6B35"/>
</g>
</g>
</svg>
</div><h2 class="slide__title">Оставь отзыв</h2><p class="slide__body">Поставь от 1 до 5 звёзд и напиши, что понравилось. Твои отзывы помогают другим выбрать надёжную компанию.</p></section>
</div>
<nav class="dots" aria-label="Прогресс">
<button class="dot is-active" data-go="0" aria-label="Слайд 1"></button>
<button class="dot " data-go="1" aria-label="Слайд 2"></button>
<button class="dot " data-go="2" aria-label="Слайд 3"></button>
</nav>
<div class="actions">
<button class="btn btn--primary" id="nextBtn" type="button">Показать на карте</button>
<button class="btn btn--ghost" id="skipBtn" type="button">Пропустить</button>
</div>
</main>
</div>
<p class="caption">Нажми «Далее» или точки снизу, чтобы переключать слайды.</p>
<script>
(function () {
var slides = Array.from(document.querySelectorAll('.slide'));
var dots = Array.from(document.querySelectorAll('.dot'));
var nextBtn = document.getElementById('nextBtn');
var skipBtn = document.getElementById('skipBtn');
var labels = {"0": "Показать на карте", "1": "Открыть чаты", "2": "Готово"};
var idx = 0;
function go(to) {
if (to === idx || to < 0 || to >= slides.length) return;
slides[idx].classList.remove('is-active');
dots[idx].classList.remove('is-active');
idx = to;
slides[idx].classList.add('is-active');
dots[idx].classList.add('is-active');
nextBtn.textContent = labels[idx];
if (idx === slides.length - 1) skipBtn.style.display = 'none';
else skipBtn.style.display = '';
}
nextBtn.addEventListener('click', function () { go(Math.min(idx + 1, slides.length - 1)); });
skipBtn.addEventListener('click', function () { go(slides.length - 1); });
dots.forEach(function (d, i) { d.addEventListener('click', function () { go(i); }); });
// touch swipe
var startX = null;
var slidesEl = document.querySelector('.slides');
slidesEl.addEventListener('touchstart', function (e) { startX = e.touches[0].clientX; }, {passive:true});
slidesEl.addEventListener('touchend', function (e) {
if (startX === null) return;
var dx = e.changedTouches[0].clientX - startX;
if (Math.abs(dx) > 40) go(dx < 0 ? Math.min(idx + 1, slides.length - 1) : Math.max(idx - 1, 0));
startX = null;
});
// Активируем анимацию для активного слайда
function setAnimClass() {
document.querySelectorAll('.slide__icon').forEach(function (el) { el.classList.remove('is-anim'); });
var active = document.querySelector('.slide.is-active .slide__icon');
if (active) active.classList.add('is-anim');
}
// Наблюдатель за сменой активного слайда
var obs = new MutationObserver(setAnimClass);
slides.forEach(function (s) { obs.observe(s, {attributes: true, attributeFilter: ['class']}); });
setAnimClass();
})();
</script>
</body>
</html>