buhapp-telegram/public/css/app.css
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

485 lines
11 KiB
CSS
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.

:root {
--primary: #FF6B35;
--primary-dark: #E64A19;
--secondary: #1E88E5;
--bg: #FFFFFF;
--bg-elev: #F5F5F7;
--bg-dark: #1C1C1E;
--text: #111114;
--text-dim: #6B6B73;
--text-inverse: #FFFFFF;
--border: #E0E0E5;
--danger: #E53935;
--success: #4CAF50;
--warning: #FFB300;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1C1C1E;
--bg-elev: #2C2C2E;
--text: #FFFFFF;
--text-dim: #98989D;
--border: #38383A;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; overflow: hidden; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro Display', Roboto, sans-serif;
font-size: 15px;
color: var(--text);
background: var(--bg);
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
-webkit-user-select: none;
user-select: none;
display: flex;
flex-direction: column;
}
/* Mobile emulator: на десктопе делаем "телефон в рамке" */
body.mobile-mode {
background: #1a1a1f;
align-items: center;
justify-content: center;
padding: 16px;
}
#mobile-frame {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
body.mobile-mode #mobile-frame {
width: 390px;
max-width: 100vw;
height: calc(100vh - 32px);
background: var(--bg);
border-radius: 16px;
box-shadow: 0 0 40px rgba(0,0,0,0.4);
flex: none;
}
@media (max-width: 600px) {
body.mobile-mode { padding: 0; }
body.mobile-mode #mobile-frame {
width: 100vw;
height: 100vh;
border-radius: 0;
}
}
#app {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
position: relative;
}
#tabbar {
height: 60px;
padding-bottom: env(safe-area-inset-bottom);
background: var(--bg-elev);
border-top: 1px solid var(--border);
display: flex;
flex-shrink: 0;
z-index: 100;
}
body.mobile-mode #tabbar {
border-radius: 0 0 16px 16px;
}
@media (max-width: 600px) {
body.mobile-mode #tabbar { border-radius: 0; }
}
.tab {
flex: 1;
background: none;
border: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
color: var(--text-dim);
font-size: 11px;
cursor: pointer;
position: relative;
min-height: 0;
}
.tab svg { fill: currentColor; }
.tab.active { color: var(--primary); }
.tab .badge {
position: absolute;
top: 4px;
right: calc(50% - 18px);
background: var(--primary);
color: var(--text-inverse);
font-size: 10px;
font-weight: 700;
border-radius: 999px;
min-width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
}
.view { padding: 16px; }
.view h1 { font-size: 28px; font-weight: 700; margin-bottom: 12px; }
.view h2 { font-size: 18px; font-weight: 600; margin: 12px 0 8px; }
.view .sub { color: var(--text-dim); margin-bottom: 16px; }
.card {
background: var(--bg-elev);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
}
.card.clickable { cursor: pointer; }
.btn {
display: block;
width: 100%;
background: var(--primary);
color: var(--text-inverse);
border: none;
border-radius: 10px;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
margin-top: 8px;
cursor: pointer;
}
.btn.ghost {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}
.btn.danger { background: var(--danger); }
.btn:disabled { opacity: 0.4; }
.input {
width: 100%;
background: var(--bg-elev);
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
padding: 12px;
font-size: 15px;
margin-bottom: 12px;
font-family: inherit;
}
label { font-size: 13px; color: var(--text-dim); margin-bottom: 4px; display: block; }
.chip {
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
background: var(--bg-elev);
border: 1px solid var(--border);
margin: 0 6px 6px 0;
font-size: 13px;
cursor: pointer;
}
.chip.active { background: var(--primary); color: var(--text-inverse); border-color: var(--primary); }
.list-row {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid var(--border);
cursor: pointer;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--primary);
color: var(--text-inverse);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
margin-right: 12px;
flex-shrink: 0;
overflow: hidden;
background-size: cover;
}
.list-row .body { flex: 1; min-width: 0; }
.list-row .name { font-weight: 600; }
.list-row .last { color: var(--text-dim); font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bubble {
max-width: 75%;
padding: 8px 12px;
border-radius: 16px;
margin-bottom: 6px;
word-wrap: break-word;
}
.bubble.mine { background: var(--primary); color: var(--text-inverse); align-self: flex-end; border-bottom-right-radius: 4px; }
.bubble.theirs { background: var(--bg-elev); color: var(--text); align-self: flex-start; border-bottom-left-radius: 4px; }
.bubble .meta { font-size: 11px; opacity: 0.7; margin-top: 2px; display: flex; gap: 6px; }
.bubble .edited { font-style: italic; }
.chat-input {
display: flex;
padding: 8px;
background: var(--bg);
border-top: 1px solid var(--border);
flex-shrink: 0;
}
.chat-input input { flex: 1; margin: 0 8px 0 0; }
.chat-input button { width: 40px; padding: 0; margin: 0; }
/* Карта — контейнер с фиксированной высотой */
#map { height: 400px; width: 100%; border-radius: 12px; overflow: hidden; margin-bottom: 12px; position: relative; z-index: 1; }
.stars { display: flex; gap: 4px; font-size: 32px; }
.stars span { cursor: pointer; color: var(--border); }
.stars span.on { color: var(--warning); }
.row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
/* Consent overlay — внутри mobile-frame */
#consent-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: var(--bg);
z-index: 200;
padding: 20px;
overflow-y: auto;
border-radius: inherit;
}
.empty { color: var(--text-dim); text-align: center; padding: 32px 16px; }
.toast {
position: absolute;
top: 16px; left: 16px; right: 16px;
background: var(--bg-dark);
color: var(--text-inverse);
padding: 12px;
border-radius: 10px;
z-index: 1000;
text-align: center;
font-size: 14px;
}
.loader { text-align: center; padding: 32px; color: var(--text-dim); }
.consent-line { display: flex; align-items: center; padding: 8px 0; gap: 8px; }
.consent-line input[type="checkbox"] { width: 20px; height: 20px; }
.rating-big { font-size: 36px; font-weight: 700; }
.privacy-note { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
/* Кнопка переключения — только на десктопе */
#mode-toggle {
position: fixed;
top: 16px;
right: 16px;
z-index: 9999;
background: var(--primary);
border: none;
color: var(--text-inverse);
border-radius: 999px;
padding: 8px 14px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 2px 12px rgba(0,0,0,0.4);
}
@media (max-width: 768px) {
#mode-toggle { display: none !important; }
}
/* Leaflet контейнер — без overflow:visible чтобы не вылезал */
.leaflet-container {
position: relative !important;
z-index: 1;
}
/* ===== Onboarding (welcome flow) — dark Telegram-style ===== */
.onboarding-overlay {
position: absolute;
inset: 0;
background: #1C1C1E;
color: #fff;
z-index: 300;
display: flex;
flex-direction: column;
overflow: hidden;
animation: ob-fade-in 260ms ease-out both;
border-radius: inherit;
background: radial-gradient(1200px 600px at 20% 0%, #2A2A2E 0%, #1C1C1E 60%, #141416 100%);
}
.onboarding-overlay.leaving { animation: ob-fade-out 240ms ease-in both; }
@keyframes ob-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ob-fade-out { from { opacity: 1; } to { opacity: 0; } }
.onboarding-stage {
flex: 1;
display: flex;
flex-direction: column;
padding: 8px 28px 24px;
position: relative;
}
.onboarding-top {
height: 44px; /* место под status-bar / notch */
flex-shrink: 0;
}
.onboarding-slides {
flex: 1;
position: relative;
min-height: 0;
}
.onboarding-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(40px);
transition: opacity 320ms ease, transform 360ms cubic-bezier(.2,.7,.2,1);
pointer-events: none;
will-change: opacity, transform;
}
.onboarding-slide.from-left { transform: translateX(-40px); }
.onboarding-slide.from-right { transform: translateX(40px); }
.onboarding-slide.is-active {
opacity: 1;
transform: translateX(0);
pointer-events: auto;
}
.onboarding-slide.to-left { opacity: 0; transform: translateX(-40px); }
.onboarding-slide.to-right { opacity: 0; transform: translateX(40px); }
.onboarding-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: ob-float 6s ease-in-out infinite;
}
.onboarding-icon svg { width: 100%; height: 100%; display: block; }
@keyframes ob-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.onboarding-title {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.4px;
margin-bottom: 12px;
max-width: 320px;
color: #fff;
line-height: 1.2;
}
.onboarding-body {
font-size: 15px;
line-height: 1.45;
color: rgba(255,255,255,0.75);
max-width: 300px;
}
.onboarding-bottom {
display: flex;
flex-direction: column;
gap: 0;
flex-shrink: 0;
padding-bottom: env(safe-area-inset-bottom);
}
.onboarding-dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 18px;
margin-bottom: 18px;
}
.onboarding-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;
}
.onboarding-dot.is-active {
width: 22px;
border-radius: 4px;
background: var(--primary);
}
.onboarding-actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.onboarding-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, opacity 200ms ease;
}
.onboarding-btn:active { transform: scale(0.98); }
.onboarding-btn-primary {
background: var(--primary);
color: #fff;
box-shadow: 0 6px 16px rgba(255,107,53,0.35);
}
.onboarding-btn-primary:hover { background: var(--primary-dark); }
.onboarding-btn-skip {
background: transparent;
color: rgba(255,255,255,0.65);
}
.onboarding-btn-skip:hover { color: #fff; }
.onboarding-caption {
margin-top: 14px;
color: rgba(255,255,255,0.5);
font-size: 12px;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.onboarding-overlay,
.onboarding-slide,
.onboarding-icon,
.onboarding-dot { animation: none !important; transition: none !important; }
.onboarding-slide { transform: none; opacity: 1; pointer-events: auto; }
}