diff --git a/public/css/app.css b/public/css/app.css index cc990a3..9b112d3 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -512,3 +512,312 @@ 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); } diff --git a/public/index.html b/public/index.html index ee98281..1b54461 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ - + @@ -34,15 +34,15 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/public/js/app.js b/public/js/app.js index 1c1104a..de4ea41 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -471,17 +471,18 @@ const App = { } }, - _toast(msg) { + _toast(msg, kind) { let t = document.getElementById('b-toast'); if (!t) { t = document.createElement('div'); t.id = 'b-toast'; - t.style.cssText = '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 #0008;opacity:0;transition:.28s;z-index:99;max-width:90%;border:1px solid var(--border);'; + t.className = 'toast-b'; document.body.appendChild(t); } + t.className = 'toast-b' + (kind ? ' ' + kind : ''); t.textContent = msg; - requestAnimationFrame(() => { t.style.opacity = '1'; t.style.transform = 'translateX(-50%) translateY(0)'; }); - setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateX(-50%) translateY(80px)'; }, 2200); + requestAnimationFrame(() => t.classList.add('show')); + setTimeout(() => t.classList.remove('show'), 2200); }, showEmailRegister() { this.showEmailLogin(); this._authSwitchMode && this._authSwitchMode('up'); }, diff --git a/public/js/views/chat.js b/public/js/views/chat.js index 49d8a08..4fbaef0 100644 --- a/public/js/views/chat.js +++ b/public/js/views/chat.js @@ -11,20 +11,26 @@ const ChatView = { this.otherName = params.otherName; root.innerHTML = `
-
- -
${escapeHtml(this.otherName || '')}
- - + +
+ +
${escapeHtml(((this.otherName || '?')[0] || '?').toUpperCase())}
+
${escapeHtml(this.otherName || '')}
+ +
-
-