diff --git a/MOBILE_OPTIMIZATION.md b/MOBILE_OPTIMIZATION.md new file mode 100644 index 0000000..94855b8 --- /dev/null +++ b/MOBILE_OPTIMIZATION.md @@ -0,0 +1,186 @@ +# Оптимизация UI для мобильных устройств - Aether Messenger + +Проект был полностью адаптирован для максимально нативного отображения на мобильных устройствах. Ниже перечислены все внесенные изменения. + +## 🔧 Основные улучшения + +### 1. **HTML метаданные** (`index.html`) +- ✅ Добавлена правильная viewport конфигурация (`viewport-fit=cover` для iPhone с notch) +- ✅ Установлена `theme-color` для браузерной полосы +- ✅ Включена поддержка PWA (`apple-mobile-web-app-capable`) +- ✅ Добавлена apple-touch-icon для иконки на домашнем экране +- ✅ Отключен автоматический зум при фокусе на input +- ✅ Добавлены все необходимые метаданные для мобильных браузеров + +### 2. **Tailwind CSS конфигурация** (`tailwind.config.js`) +- ✅ Добавлена поддержка safe-area-inset для notch на iPhone +- ✅ Новые утилиты для touch-friendly размеров (44x44 минимум по Apple HIG) +- ✅ Добавлены медиа-запросы для обнаружения touch устройств +- ✅ Расширенная палитра спейсинга и размеров + +### 3. **Глобальные стили** (`index.css`) +- ✅ Оптимизировано позиционирование root элемента (fixed для предотвращения скроллинга) +- ✅ Правильная обработка viewport height с поддержкой появления/исчезновения клавиатуры +- ✅ Отключены синие tap highlights на iOS/Android +- ✅ Улучшена поддержка прокрутки с `-webkit-overflow-scrolling: touch` +- ✅ Добавлена поддержка prefers-reduced-motion для доступности +- ✅ Оптимизирована работа с input полями (предотвращение зума на iOS) + +### 4. **ChatPage оптимизация** (`src/pages/ChatPage.tsx`) + +#### Отступы и размеры +- Уменьшены отступы на мобильных устройствах (p-2.5 вместо p-4) +- Оптимизированы размеры аватаров (w-10 вместо w-12 на мобиле) +- Правильные размеры иконок для мобилы (size-16 на мобиле, size-20 на десктопе) + +#### Сообщения +- Уменьшена максимальная ширина пузырей сообщений (max-w-xs вместо max-w-lg на мобиле) +- Оптимизирован размер шрифта (text-sm вместо text-[15px]) +- Добавлены правильные border-radius для мобилы + +#### Поле ввода +- Установлена `font-size: 16px` для предотвращения iOS зума +- Увеличена высота кнопки отправления (min-h-touch) +- Оптимизированы паддинги для удобного нажатия на мобиле + +#### Модальные окна +- Переход на полноэкранное отображение снизу на мобилах +- Улучшенная анимация (slide up вместо scale) +- Оптимизированы отступы и размеры шрифтов + +#### Сайдбар +- Использование w-screen вместо w-full для полноэкранного отображения +- Скрытие на мобилах при выборе чата +- Правильное использование flex-shrink-0 для предотвращения collapse + +### 5. **AuthPage оптимизация** (`src/pages/AuthPage.tsx`) +- Использование w-screen h-screen для полного заполнения экрана +- Уменьшены размеры логотипа на мобилах +- Оптимизированы кнопки с min-h-touch + +### 6. **ProfilePage оптимизация** (`src/pages/ProfilePage.tsx`) +- Полноэкранное отображение (w-screen h-screen) +- Оптимизирована структура для лучшей прокрутки +- Уменьшены размеры иконок на мобилах +- Правильные размеры кнопок для touch устройств + +### 7. **SettingsPage оптимизация** (`src/pages/SettingsPage.tsx`) +- Адаптивные размеры темных кнопок +- Оптимизированные опции тем для мобилы +- Улучшенная прокрутка содержимого + +### 8. **App.tsx улучшения** (`src/App.tsx`) +- Добавлена обработка viewport resize при появлении клавиатуры +- Попытка предотвращения "bounce" скроллинга на iOS +- Установка CSS переменной `--vh` для правильного заполнения 100vh на мобилах + +## 📱 Native-like особенности + +### Визуальные улучшения +- 🎨 Правильные border-radius для iOS (`rounded-*` классы) +- 🎯 Touch-friendly размеры кнопок (минимум 44x44px) +- 📏 Оптимизированные шрифты для малых экранов +- 🪟 Поддержка safe area inset (notch, bottom bar) + +### Поведение +- ⌨️ Правильная обработка появления/исчезновения клавиатуры +- 🖱️ Отключены неправильные ховер эффекты на мобилах +- ⚡ Оптимизирована производительность (GPU acceleration где необходимо) +- 🔄 Правильный скроллинг с инерцией на iOS + +### Доступность +- ♿ Поддержка `prefers-reduced-motion` +- 👁️ Оптимизированные размеры текста +- 🔊 Правильные focus состояния + +## 🚀 Рекомендации для дальнейшей оптимизации + +### 1. Изображения +- Используйте WebP формат с fallback на PNG/JPG +- Реализуйте lazy loading для аватаров +- Оптимизируйте размеры изображений через srcset + +### 2. Производительность +```javascript +// Рассмотрите использование React.memo для больших списков +const MessageBubble = React.memo(({ message, isMyMessage }) => { + // компонент +}); + +// Используйте useMemo для тяжелых вычислений +const sortedMessages = useMemo(() => { + return messages.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); +}, [messages]); +``` + +### 3. Network +- Реализуйте сжатие изображений на клиенте перед загрузкой +- Используйте Service Worker для кэширования +- Considere использование webp для аватаров + +### 4. Storage +- Реализуйте IndexedDB для кэширования сообщений +- Используйте localStorage для сохранения черновиков + +### 5. PWA +```json +// Добавьте manifest.json +{ + "name": "Aether Messenger", + "short_name": "Aether", + "description": "Простой и элегантный мессенджер", + "start_url": "/", + "display": "standalone", + "theme_color": "#6B705C", + "background_color": "#F5F5F1" +} +``` + +### 6. Тестирование +- ☑️ Тестируйте на реальных мобильных устройствах +- ☑️ Проверяйте на разных браузерах (Safari, Chrome, Samsung Internet) +- ☑️ Тестируйте с отключенным интернетом/медленной сетью +- ☑️ Используйте Chrome DevTools Mobile Emulation + +## 🔍 Проверка результатов + +### Используйте Chrome DevTools +1. Откройте DevTools (F12) +2. Переключитесь в Mobile режим (Ctrl+Shift+M) +3. Проверьте: + - ✅ Все элементы видны и правильно отцентрированы + - ✅ Нет горизонтального скроллинга + - ✅ Кнопки достаточно большие для нажатия + - ✅ Text читаемый без зума + +### Lighthouse аудит +1. Откройте Lighthouse в Chrome DevTools +2. Запустите аудит для Mobile +3. Проверьте очки для Performance, Accessibility, Best Practices + +## 📊 Тестовые устройства + +Рекомендуется тестировать на: +- iPhone 11/12/13 (Safari) +- Samsung Galaxy S21/22 (Chrome) +- Pixel 6/7 (Chrome) +- iPad (Safari) +- Планшеты 10-12 дюймов + +## 📝 Контрольный список перед релизом + +- [ ] Тестировано на iPhone с iOS 14+ +- [ ] Тестировано на Android 10+ +- [ ] Все кнопки имеют минимум 44x44px +- [ ] Нет горизонтального скроллинга +- [ ] Модалы открываются снизу на мобилах +- [ ] Клавиатура не прячет поле ввода +- [ ] Изображения оптимизированы +- [ ] Touch события работают без задержек +- [ ] LighthouseScore > 85 для Mobile + +--- + +**Версия:** 1.0 +**Дата последнего обновления:** 24 февраля 2026 +**Статус:** ✅ Завершено diff --git a/frontend/index.html b/frontend/index.html index c18315e..1cbec5c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,13 +3,21 @@ - + + + + + + + + + Aether — Messenger - +
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cb70f2b..9bf626f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,7 +17,7 @@ function PrivateRoute({ children }: { children: React.ReactNode }) { if (isLoading) { return ( -
+
); @@ -36,6 +36,30 @@ function App() { document.documentElement.setAttribute('data-theme', theme); }, [theme]); + useEffect(() => { + // Prevent default iOS behaviors + document.addEventListener('touchmove', (e) => { + if ((e.target as any).closest('.overflow-y-auto') === null && + (e.target as any).nodeName !== 'INPUT' && + (e.target as any).nodeName !== 'TEXTAREA') { + e.preventDefault(); + } + }, { passive: false }); + + // Handle viewport resize on mobile (keyboard appears) + const handleResize = () => { + const vh = window.innerHeight * 0.01; + document.documentElement.style.setProperty('--vh', `${vh}px`); + }; + + handleResize(); + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + useEffect(() => { const checkAuth = async () => { try { @@ -49,7 +73,7 @@ function App() { }; checkAuth(); - }, []); // Пустой массив зависимостей - выполнится только один раз + }, []); return ( diff --git a/frontend/src/index.css b/frontend/src/index.css index f5d6609..653ef7e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -41,9 +41,26 @@ --error-soft: rgba(216, 155, 142, 0.15); } +html { + position: fixed; + height: 100%; + width: 100%; + overflow: hidden; +} + +html, +body { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; +} + body { margin: 0; - min-height: 100vh; font-family: 'Inter', sans-serif; line-height: 1.5; font-weight: 400; @@ -52,5 +69,187 @@ body { color: var(--text-primary); background-color: var(--bg-primary); transition: background-color 0.3s ease, color 0.3s ease; + + /* Prevent zoom on double tap and long press */ + touch-action: manipulation; + + /* Mobile optimization */ + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; + + /* Notch support */ + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); +} + +#root { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Text selection for inputs */ +input, textarea, select { + -webkit-user-select: text; + user-select: text; +} + +/* Smooth scrolling */ +@supports (scroll-behavior: smooth) { + html { + scroll-behavior: smooth; + } +} + +/* Mobile touch optimizations */ +@media (hover: none) and (pointer: coarse) { + /* Disable hover states on touch devices */ + button:hover, + a:hover { + filter: none; + } + + /* Ensure minimum touch target size */ + button, + input[type="button"], + input[type="submit"], + [role="button"] { + min-height: 44px; + min-width: 44px; + } + + /* Prevent iOS default styling */ + input, + textarea, + select { + font-size: 16px; + border-radius: 8px; + padding: 12px; + } + + /* Disable blue tap highlight */ + -webkit-tap-highlight-color: transparent; + + /* Better focus states for touch */ + input:focus, + textarea:focus, + select:focus { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; + } +} + +/* Prevent text zoom when rotating */ +body { + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +/* Smooth transitions */ +* { + -webkit-tap-highlight-color: transparent; +} + +/* Modal scrolling fix on iOS */ +body.modal-open { + overflow: hidden; + position: fixed; +} + +/* Fix for iOS keyboard appearance */ +@supports (padding: max(0px)) { + body { + padding-bottom: max(env(safe-area-inset-bottom), 0px); + } +} + +/* Responsive font sizes */ +@media (max-width: 640px) { + html { + font-size: 14px; + } +} + +@media (min-width: 641px) { + html { + font-size: 16px; + } +} + +/* Better button interactivity on mobile */ +button { + -webkit-appearance: none; + appearance: none; + background: none; + border: none; +} + +/* Input styling for mobile */ +input[type="text"], +input[type="email"], +input[type="password"], +textarea { + -webkit-appearance: none; + appearance: none; + border-radius: 8px; +} + +/* Disable auto-zoom on iOS input focus */ +input[type="text"], +input[type="email"], +input[type="password"], +textarea, +select { + font-size: 16px !important; +} + +/* Smooth scrolling container */ +.overflow-y-auto { + -webkit-overflow-scrolling: touch; +} + +/* Better focus visible styling */ +*:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; +} + +/* Loading spinner optimization */ +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; + will-change: transform; +} + +/* Performance: Use GPU acceleration */ +.motion-reduce { + animation: none; + transition: none; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } } diff --git a/frontend/src/pages/AuthPage.tsx b/frontend/src/pages/AuthPage.tsx index 46ad6e5..193db84 100644 --- a/frontend/src/pages/AuthPage.tsx +++ b/frontend/src/pages/AuthPage.tsx @@ -8,7 +8,7 @@ export default function AuthPage() { const [isLogin, setIsLogin] = useState(true); return ( -
+
{/* Subtle texture background */}
-
+
{/* Logo */} -
-
+
+
Aether Logo
-
+
AETHER
{/* Switch */} -
+
{isLogin ? ( <> Ещё нет аккаунта?{' '}
{/* Action Button */} -
+
navigate('/profile')} whileTap={{ scale: 0.95 }} - className="w-full flex items-center justify-center gap-2 py-2 px-3 rounded-xl font-inter text-sm font-medium transition hover:opacity-80" + className="w-full flex items-center justify-center gap-2 py-2 px-3 rounded-xl md:rounded-2xl font-inter text-xs md:text-sm font-medium transition hover:opacity-80 min-h-touch" style={{ backgroundColor: 'var(--bg-input)', color: 'var(--accent-primary)' }} > - + Профиль
{/* New Chat Button */} -
+
setShowNewChatModal(true)} - className="w-full flex items-center justify-center gap-3 py-3 px-4 rounded-2xl font-inter font-semibold shadow-sm hover:shadow-md transition" + className="w-full flex items-center justify-center gap-2 md:gap-3 py-2.5 md:py-3 px-3 md:px-4 rounded-xl md:rounded-2xl font-inter font-semibold text-sm md:text-base shadow-sm hover:shadow-md transition min-h-touch" style={{ backgroundColor: 'var(--accent-primary)', color: 'white' }} > - + Новый чат
{/* Chats List */} -
-
+
+
{loading ? (
-

+

Загрузка чатов...

) : error ? (
-

{error}

+

{error}

) : chats.length === 0 ? (
-

+

Пока нет чатов

@@ -641,17 +641,17 @@ export default function ChatPage() { whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={() => handleChatClick(chat)} - className="p-4 rounded-2xl cursor-pointer transition-all relative overflow-hidden" + className="p-2.5 md:p-4 rounded-lg md:rounded-2xl cursor-pointer transition-all relative overflow-hidden min-h-touch" style={{ backgroundColor: selectedChat?.chat_id === chat.chat_id ? 'var(--accent-primary)' : 'var(--bg-input)', boxShadow: selectedChat?.chat_id === chat.chat_id ? '0 4px 12px rgba(0,0,0,0.1)' : 'none', }} > -

+
{/* Chat Avatar with Online Indicator */}
{/* Online indicator */}

{chat.display_name} @@ -696,14 +696,14 @@ export default function ChatPage() { {chat.last_message ? (

{chat.last_message}

) : (

Нет сообщений @@ -718,7 +718,7 @@ export default function ChatPage() {

{/* Footer Info */} -
+

Aether Chat v1.0

@@ -726,13 +726,13 @@ export default function ChatPage() {
{/* Main Chat Area */} -
{selectedChat ? ( <> {/* Chat Header */} -
@@ -740,21 +740,21 @@ export default function ChatPage() { whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} onClick={handleBackToChats} - className="p-2 rounded-full transition" + className="p-2 rounded-full transition min-h-touch min-w-touch" style={{ backgroundColor: 'var(--bg-input)', color: 'var(--text-primary)' }} title="Назад к чатам" > - + handleViewUserProfile(selectedChat.user_id)} - className="w-12 h-12 flex-shrink-0 flex items-center justify-center cursor-pointer" + className="w-10 h-10 md:w-12 md:h-12 flex-shrink-0 flex items-center justify-center cursor-pointer" style={{ backgroundImage: selectedChat.avatar_url ? `url(${selectedChat.avatar_url})` : undefined, backgroundSize: 'cover', @@ -771,7 +771,7 @@ export default function ChatPage() { className="flex-1 min-w-0 cursor-pointer" onClick={() => handleViewUserProfile(selectedChat.user_id)} > -

+

{selectedChat.display_name}

@@ -784,7 +784,7 @@ export default function ChatPage() {

{/* Date divider */} {showDateDivider && ( -
-
+
{/* Avatar for incoming messages */} {!isMyMessage && showAvatar && ( @@ -872,7 +872,7 @@ export default function ChatPage() { initial={{ scale: 0 }} animate={{ scale: 1 }} transition={{ delay: 0.1 }} - className="w-9 h-9 flex-shrink-0 flex items-center justify-center self-end shadow-md" + className="w-7 h-7 md:w-9 md:h-9 flex-shrink-0 flex items-center justify-center self-end shadow-md" style={{ backgroundImage: selectedChat.avatar_url ? `url(${selectedChat.avatar_url})` : undefined, backgroundSize: 'cover', @@ -888,20 +888,20 @@ export default function ChatPage() { )} {/* Message bubble */} -
+
{/* Sender name for incoming messages */} {!isMyMessage && showAvatar && ( - + {selectedChat.display_name} )} - {/* Edit and Delete buttons - показываем только для своих сообщений */} + {/* Edit and Delete buttons - только для своих сообщений на десктопе */} {isMyMessage && editingMessageId !== message.id && ( <> )} {/* Message content or edit input */} {editingMessageId === message.id ? ( -
+
setEditingMessageText(e.target.value)} onKeyDown={(e) => handleEditKeyPress(e, message.id)} autoFocus - className="flex-1 bg-transparent border-b-2 outline-none" + className="flex-1 bg-transparent border-b-2 outline-none text-sm md:text-base" style={{ borderColor: isMyMessage ? 'rgba(255,255,255,0.5)' : 'var(--accent-primary)', color: isMyMessage ? 'white' : 'var(--text-primary)' @@ -951,17 +951,17 @@ export default function ChatPage() { />
) : ( @@ -969,11 +969,11 @@ export default function ChatPage() { )} {/* Message metadata */} -
+
{/* Edited indicator */} {message.is_edited && ( - {/* Reactions placeholder - можно добавить позже */} + {/* Reactions placeholder */} {isLastInGroup && false && (
❤️ @@ -1076,7 +1076,7 @@ export default function ChatPage() { {/* Message Input */} -
@@ -1087,13 +1087,14 @@ export default function ChatPage() { value={messageText} onChange={(e) => setMessageText(e.target.value)} onKeyPress={handleKeyPress} - placeholder="Написать сообщение..." + placeholder="Сообщение..." disabled={sendingMessage} - className="flex-1 px-3 md:px-4 py-2.5 md:py-3 rounded-2xl font-inter text-sm outline-none transition" + className="flex-1 px-3 md:px-4 py-2 md:py-3 rounded-lg md:rounded-2xl font-inter text-sm md:text-base outline-none transition min-h-touch" style={{ backgroundColor: 'var(--bg-input)', color: 'var(--text-primary)', borderBottom: '2px solid transparent', + fontSize: '16px', // Prevent iOS zoom on input focus }} onFocus={(e) => e.target.style.borderBottomColor = 'var(--accent-primary)'} onBlur={(e) => e.target.style.borderBottomColor = 'transparent'} @@ -1102,13 +1103,13 @@ export default function ChatPage() { whileTap={{ scale: 0.95 }} onClick={handleSendMessage} disabled={!messageText.trim() || sendingMessage} - className="px-4 md:px-6 py-2.5 md:py-3 rounded-2xl font-inter font-semibold flex items-center gap-2 transition hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed" + className="px-3 md:px-6 py-2 md:py-3 rounded-lg md:rounded-2xl font-inter font-semibold flex items-center justify-center gap-1 md:gap-2 transition hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed min-h-touch min-w-touch" style={{ backgroundColor: 'var(--accent-primary)', color: 'white' }} > {sendingMessage ? ( -
+
) : ( - + )}
@@ -1145,38 +1146,38 @@ export default function ChatPage() { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" + className="fixed inset-0 bg-black/50 flex items-end md:items-center justify-center z-50 p-4" onClick={() => setViewingUser(null)} > e.stopPropagation()} > {/* Modal Header */} -
+
-

+

Профиль пользователя

{/* Modal Content */} -
+
{/* Avatar */} -
+
{!viewingUser.avatar_url && ( - + )}
{/* User Info */} -
+
-

+

{viewingUser.display_name}

-

+

@{viewingUser.username}

{viewingUser.description && ( -
-

+

+

{viewingUser.description}

)} {viewingUser.birth_day && ( -
+
🎂 {new Date(viewingUser.birth_day).toLocaleDateString('ru-RU', { @@ -1222,18 +1223,18 @@ export default function ChatPage() {
)} -
+
✉️ - {viewingUser.email} + {viewingUser.email}
{/* Actions */} -
+
setViewingUser(null)} - className="w-full py-3 px-4 rounded-2xl font-inter font-semibold transition hover:opacity-90" + className="w-full py-2.5 md:py-3 px-4 rounded-lg md:rounded-2xl font-inter font-semibold transition hover:opacity-90 min-h-touch" style={{ backgroundColor: 'var(--accent-primary)', color: 'white' }} > Закрыть @@ -1252,7 +1253,7 @@ export default function ChatPage() { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" + className="fixed inset-0 bg-black/50 flex items-end md:items-center justify-center z-50 p-4" onClick={() => { setShowNewChatModal(false); setSearchQuery(''); @@ -1260,17 +1261,17 @@ export default function ChatPage() { }} > e.stopPropagation()} - className="w-full max-w-md rounded-3xl shadow-2xl overflow-hidden" + className="w-full md:max-w-md rounded-t-3xl md:rounded-3xl shadow-2xl overflow-hidden" style={{ backgroundColor: 'var(--bg-card)' }} > {/* Modal Header */} -
-
-

+
+
+

Новый чат

@@ -1292,51 +1293,52 @@ export default function ChatPage() { onChange={(e) => handleSearchUsers(e.target.value)} placeholder="Поиск пользователей..." autoFocus - className="w-full px-4 py-3 rounded-2xl font-inter text-sm outline-none transition" + className="w-full px-3 md:px-4 py-2 md:py-3 rounded-lg md:rounded-2xl font-inter text-sm md:text-base outline-none transition" style={{ backgroundColor: 'var(--bg-input)', color: 'var(--text-primary)', borderBottom: '2px solid transparent', + fontSize: '16px', // Prevent iOS zoom }} />
{/* Search Results */} -
+
{searchLoading ? (
-

+

Поиск...

) : searchQuery.trim().length < 2 ? (
-

+

Введите имя пользователя для поиска

) : searchResults.length === 0 ? (
-

+

Пользователи не найдены

) : ( -
+
{searchResults.map((foundUser) => ( handleStartChatWithUser(foundUser)} - className="w-full flex items-center gap-3 p-3 rounded-2xl transition hover:shadow-sm" + className="w-full flex items-center gap-2 md:gap-3 p-2.5 md:p-3 rounded-lg md:rounded-2xl transition hover:shadow-sm min-h-touch" style={{ backgroundColor: 'var(--bg-input)' }} > {/* Avatar */}
{/* User Info */} -
-

+

+

{foundUser.display_name || foundUser.username}

-

+

@{foundUser.username}

diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index de79844..8e9b076 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -151,50 +151,51 @@ export default function ProfilePage() { if (!user) return null; return ( -
-
- {/* Header */} -
- -
+
+
+
+ {/* Header */} +
- +
+ + +
-
- {/* Verification Banner */} - {!user.is_verified && ( - - )} + {/* Verification Banner */} + {!user.is_verified && ( + + )} - {/* Profile Card */} - + {/* Profile Card */} + {/* Avatar Section */}
@@ -572,12 +573,13 @@ export default function ProfilePage() { onClick={handleDeleteAccount} whileTap={{ scale: 0.95 }} style={{ backgroundColor: 'var(--error-color)', color: 'white' }} - className="py-3 px-6 rounded-full font-inter font-semibold hover:shadow-lg transition-all" + className="py-2 md:py-3 px-4 md:px-6 rounded-lg md:rounded-full font-inter font-semibold text-sm md:text-base hover:shadow-lg transition-all min-h-touch" > Удалить аккаунт
+
); diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 21b0cb2..87257ce 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -8,117 +8,119 @@ export default function SettingsPage() { const { theme, setTheme } = useThemeStore(); return ( -
-
- {/* Header */} -
- +
+ + {/* Settings Card */} + - - Назад к чатам - -
+

+ Настройки +

- {/* Settings Card */} - -

- Настройки -

+ {/* Appearance Section */} +
+
+

+ Внешний вид +

- {/* Appearance Section */} -
-
-

- Внешний вид -

+ {/* Theme Selector */} +
+ - {/* Theme Selector */} -
- - -
- {/* Light Theme */} - setTheme('light')} - className="relative p-6 rounded-2xl border-2 transition-all" - style={{ - backgroundColor: theme === 'light' ? 'var(--accent-primary-soft)' : 'var(--bg-input)', - borderColor: theme === 'light' ? 'var(--accent-primary)' : 'transparent', - }} - > -
-
- +
+ {/* Light Theme */} + setTheme('light')} + className="relative p-3 md:p-6 rounded-lg md:rounded-2xl border-2 transition-all min-h-touch" + style={{ + backgroundColor: theme === 'light' ? 'var(--accent-primary-soft)' : 'var(--bg-input)', + borderColor: theme === 'light' ? 'var(--accent-primary)' : 'transparent', + }} + > +
+
+ +
+ + Светлая тема + + {theme === 'light' && ( + + + + + + )}
- - Светлая тема - - {theme === 'light' && ( - - - - - - )} -
- + - {/* Dark Theme */} - setTheme('dark')} - className="relative p-6 rounded-2xl border-2 transition-all" - style={{ - backgroundColor: theme === 'dark' ? 'var(--accent-primary-soft)' : 'var(--bg-input)', - borderColor: theme === 'dark' ? 'var(--accent-primary)' : 'transparent', - }} - > -
-
- + {/* Dark Theme */} + setTheme('dark')} + className="relative p-3 md:p-6 rounded-lg md:rounded-2xl border-2 transition-all min-h-touch" + style={{ + backgroundColor: theme === 'dark' ? 'var(--accent-primary-soft)' : 'var(--bg-input)', + borderColor: theme === 'dark' ? 'var(--accent-primary)' : 'transparent', + }} + > +
+
+ +
+ + Темная тема + + {theme === 'dark' && ( + + + + + + )}
- - Темная тема - - {theme === 'dark' && ( - - - - - - )} -
- + +
+ +

+ Выберите тему оформления +

- -

- Выберите тему, которая лучше всего подходит для ваших глаз -

-
- + +
); diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index b1708b5..4239aee 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -24,6 +24,29 @@ export default { 'soft': '0 15px 45px rgba(107, 112, 92, 0.1), 0 0 0 1px rgba(107, 112, 92, 0.05)', 'logo': '0 4px 12px rgba(107, 112, 92, 0.15)', }, + spacing: { + 'safe-start': 'max(1rem, env(safe-area-inset-left))', + 'safe-end': 'max(1rem, env(safe-area-inset-right))', + 'safe-top': 'max(1rem, env(safe-area-inset-top))', + 'safe-bottom': 'max(1rem, env(safe-area-inset-bottom))', + }, + minHeight: { + 'touch': '44px', + 'touch-lg': '48px', + }, + minWidth: { + 'touch': '44px', + 'touch-lg': '48px', + }, + screens: { + 'xs': '360px', + 'sm': '640px', + 'md': '768px', + 'lg': '1024px', + 'xl': '1280px', + '2xl': '1536px', + 'touch': { 'raw': '(hover: none) and (pointer: coarse)' }, + }, }, }, plugins: [],