buhapp-telegram/public/css/app.css
root c5f8ac67ad UX overhaul: единый стиль BuhApp на всех экранах
ДИЗАЙН-СИСТЕМА в app.css:
- --primary #FF6B35, --success #3dd68c, --danger #ff5c5c
- --bg #0f0f12, --bg-elev #1a1a22, --bg-input #121218
- Компоненты: .card-b, .row-b, .field-b, .btn-b, .switch-b, .chip, .avatar-b, .bubble-b, .composer-b, .toast-b, .stars-b, .pulse-b, .empty-b

ПЕРЕПИСАНЫ на единый стиль:

Settings:
- Карточка с аватаром пользователя
- Card-row для юр.документов (📄 / 🔒 / ⚠️)
- 'Выйти' — danger btn-b с state machine (idle→load→ok)

Profile:
- Карточка аватара + кнопка 'Загрузить фото' через multipart /api/v1/me/photo
- Поля: field-b (ctrl + hint)
- Чипы в chip-list, новый стиль с .on
- Счётчик bio символов
- 'Сохранить' со state machine + toast

Review (создание):
- Звёзды в stars-b с center hint
- textarea с counter
- switch-b 'Анонимно' (вместо checkbox)
- Submit со state machine + toast 'Спасибо за отзыв!'

UserReviews:
- Средний рейтинг + звёзды
- Список карточек с отзывами

Chats:
- Список с avatar-b + last message + время + badge непрочитанных
- Пустое состояние с эмодзи
- formatChatTime() — 'сейчас' / '5м' / '3ч' / '2д'

Chat (один):
- Header с аватаром и кнопками 👤 ⭐
- composer-b: 📎 + input + ➤ (sticky bottom)
- bubble-b .in / .out с .ticks.read
- Send со state + toast
- sendPhoto() для фоток в чате

App.js _toast(msg, kind):
- Единый toast-b с классами ok / err

Всё в одном стиле. CSS variables, padding 16, radius 14, no disabled кнопок.
2026-08-21 11:42:16 +00:00

824 lines
20 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; }
}
/* Pulse animation для важных кнопок (позвать на действие) */
.btn-pulse {
display: inline-block;
animation: btn-pulse 2s ease-in-out infinite;
}
@keyframes btn-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.2); }
}
/* Read-marks для чата */
.bubble .meta { display: flex; align-items: center; gap: 4px; }
.bubble .read-tick { font-size: 12px; opacity: 0.85; }
.bubble .read-tick.read { color: var(--secondary); font-weight: 700; }
/* edit marker */
.bubble .edited { font-style: italic; opacity: 0.7; }
/* Auth UX (от Grok) */
.btn.ok { background: var(--success) !important; color: #fff !important; }
.btn.load { opacity: .85; pointer-events: none; }
.btn.err { background: var(--danger) !important; }
input.bad { border-color: var(--danger) !important; }
input.good { border-color: var(--success) !important; }
.hint.ok { color: var(--success); }
.hint.err { color: var(--danger); }
.hint.mute { color: var(--text-dim); }
/* ====================================
BuhApp Design System (от Grok)
Единый стиль для всех экранов
==================================== */
:root {
--primary: #FF6B35;
--primary-soft: rgba(255, 107, 53, 0.15);
--success: #3dd68c;
--danger: #ff5c5c;
--warn: #ffb800;
--bg: #0f0f12;
--bg-elev: #1a1a22;
--bg-input: #121218;
--border: #2a2a36;
--border-strong: #3a3a48;
--text: #f2f2f5;
--text-dim: #8b8b9a;
--text-mute: #5a5a6a;
--r-card: 14px;
--r-field: 10px;
--r-btn: 12px;
--safe: env(safe-area-inset-bottom, 0px);
}
/* Card */
.card-b {
background: var(--bg-elev);
border: 1.5px solid var(--border);
border-radius: var(--r-card);
padding: 16px;
}
/* Stack of card-rows (для Settings и Chats) */
.row-list { display: flex; flex-direction: column; gap: 12px; }
/* Row внутри card */
.row-b {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: var(--bg-input);
border-radius: 12px;
border: 1px solid var(--border);
min-height: 52px;
cursor: pointer;
transition: background 0.1s, border-color 0.1s;
}
.row-b:active { background: var(--bg-elev); }
.row-b .title { flex: 1; font-size: 15px; font-weight: 500; }
.row-b .value { font-size: 13px; color: var(--text-dim); }
.row-b .chev { color: var(--text-mute); font-size: 16px; }
/* Field */
.field-b { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field-b label { font-size: 12px; font-weight: 600; color: var(--text-dim); letter-spacing: 0.02em; }
.field-b .ctrl {
width: 100%;
background: var(--bg-input);
border: 1.5px solid var(--border);
border-radius: var(--r-field);
color: var(--text);
font: 15px/1.2 inherit;
padding: 13px 14px;
outline: 0;
-webkit-appearance: none;
}
.field-b .ctrl:focus { border-color: var(--primary); }
.field-b .ctrl.bad { border-color: var(--danger); }
.field-b .ctrl.good { border-color: var(--success); }
.field-b .hint { min-height: 18px; font-size: 12px; }
.field-b .hint.ok { color: var(--success); }
.field-b .hint.err { color: var(--danger); }
.field-b .hint.mute { color: var(--text-dim); }
/* Button */
.btn-b {
width: 100%;
border: 0;
border-radius: var(--r-btn);
padding: 15px;
font: 700 16px/1 inherit;
cursor: pointer;
background: var(--primary);
color: #fff;
transition: opacity 0.15s, background 0.15s, transform 0.1s;
}
.btn-b:active { transform: scale(0.98); }
.btn-b.load { opacity: 0.85; pointer-events: none; }
.btn-b.ok { background: var(--success); }
.btn-b.err { background: var(--danger); }
.btn-b.ghost {
background: transparent;
border: 1.5px solid var(--border-strong);
color: var(--text);
}
.btn-b.ghost:active { border-color: var(--primary); }
.btn-b.danger { background: var(--danger); }
.btn-b.tiny {
width: auto;
padding: 8px 14px;
font-size: 13px;
border-radius: 10px;
}
/* Switch (track + thumb) */
.switch-b {
position: relative;
width: 44px;
height: 26px;
background: var(--border);
border-radius: 13px;
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s;
}
.switch-b.on { background: var(--primary); }
.switch-b::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: #fff;
border-radius: 50%;
transition: left 0.15s;
}
.switch-b.on::after { left: 21px; }
/* Chip (tag) */
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
background: var(--bg-input);
border: 1.5px solid var(--border);
border-radius: 999px;
font-size: 13px;
cursor: pointer;
transition: background 0.1s, border-color 0.1s;
}
.chip:active { transform: scale(0.96); }
.chip.on {
background: var(--primary-soft);
border-color: var(--primary);
color: var(--primary);
}
.chip .x { font-size: 11px; opacity: 0.7; }
/* List of chips (drink/activity/purpose) */
.chip-list { display: flex; flex-wrap: wrap; gap: 8px; }
/* Toast (bottom) — общий для всего приложения */
.toast-b {
position: fixed;
left: 50%;
bottom: 88px;
transform: translateX(-50%) translateY(80px);
background: #222;
color: #fff;
padding: 12px 18px;
border-radius: 12px;
font-size: 14px;
font-weight: 600;
box-shadow: 0 8px 28px rgba(0,0,0,0.5);
opacity: 0;
transition: opacity 0.28s, transform 0.28s;
z-index: 999;
max-width: 90%;
border: 1px solid var(--border-strong);
pointer-events: none;
}
.toast-b.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast-b.err { background: var(--danger); }
.toast-b.ok { background: var(--success); }
/* Empty state */
.empty-b {
text-align: center;
padding: 32px 16px;
color: var(--text-dim);
}
.empty-b .em { font-size: 48px; margin-bottom: 12px; }
/* Avatar circle */
.avatar-b {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--primary-soft);
color: var(--primary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 20px;
flex-shrink: 0;
}
.avatar-b.sm { width: 32px; height: 32px; font-size: 14px; }
.avatar-b.lg { width: 88px; height: 88px; font-size: 36px; }
/* Chat bubble */
.bubble-b {
max-width: 75%;
padding: 10px 14px;
border-radius: 14px;
word-wrap: break-word;
font-size: 15px;
line-height: 1.35;
}
.bubble-b.in {
background: var(--bg-elev);
border: 1px solid var(--border);
align-self: flex-start;
}
.bubble-b.out {
background: var(--primary-soft);
border: 1px solid var(--primary);
align-self: flex-end;
color: var(--text);
}
.bubble-b .meta {
display: flex;
align-items: center;
gap: 6px;
justify-content: flex-end;
margin-top: 4px;
font-size: 11px;
color: var(--text-mute);
}
.bubble-b.out .meta { color: var(--text-dim); }
.bubble-b .ticks.read { color: var(--primary); }
.bubble-b.err {
border-color: var(--danger);
background: rgba(255, 92, 92, 0.1);
}
/* Composer (чат) */
.composer-b {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px calc(8px + var(--safe));
background: var(--bg-elev);
border-top: 1px solid var(--border);
}
.composer-b input {
flex: 1;
background: var(--bg-input);
border: 1.5px solid var(--border);
border-radius: 20px;
padding: 10px 14px;
color: var(--text);
font: 15px/1.2 inherit;
outline: 0;
-webkit-appearance: none;
}
.composer-b input:focus { border-color: var(--primary); }
.composer-b .send-b {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--primary);
color: #fff;
border: 0;
cursor: pointer;
font-size: 18px;
flex-shrink: 0;
}
.composer-b .send-b.load { opacity: 0.6; pointer-events: none; }
.composer-b .attach-b {
width: 40px;
height: 40px;
border-radius: 50%;
background: transparent;
color: var(--text-dim);
border: 0;
cursor: pointer;
font-size: 20px;
flex-shrink: 0;
}
.composer-b .attach-b:active { color: var(--primary); }
/* Pulse animation (для важных idle-кнопок) */
@keyframes pulse-b {
0%, 100% { box-shadow: 0 0 0 0 var(--primary-soft); }
50% { box-shadow: 0 0 0 12px transparent; }
}
.pulse-b { animation: pulse-b 2s ease-in-out infinite; }
/* Star rating */
.stars-b {
display: flex;
justify-content: center;
gap: 8px;
margin: 12px 0;
}
.stars-b .star {
font-size: 36px;
color: var(--border-strong);
cursor: pointer;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
.stars-b .star.on { color: var(--warn); }