Merge pull request #46 from SlimusMinus/fix-start_page

fixed start page and scroll in modal window
This commit is contained in:
SlimusMinus
2026-08-18 01:07:30 +03:00
committed by GitHub
17 changed files with 142 additions and 68 deletions

View File

@@ -15,7 +15,7 @@ export default function AuthModal({ onClose }) {
const { login: saveAuth } = useAuthContext(); const { login: saveAuth } = useAuthContext();
return ( return (
<div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}> <div className="f-modal-bg auth-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}>
<div className="f-modal auth-modal"> <div className="f-modal auth-modal">
<div className="f-modal-head"> <div className="f-modal-head">
<span className="f-modal-title"> <span className="f-modal-title">

View File

@@ -8,6 +8,7 @@ export default function FeedContent({
filteredAds, filteredAds,
activeNav, activeNav,
liked, liked,
likeCountAdj,
onLike, onLike,
onEdit, onEdit,
onDelete, onDelete,
@@ -58,6 +59,7 @@ export default function FeedContent({
key={ad.id} key={ad.id}
ad={ad} ad={ad}
liked={liked.has(ad.id)} liked={liked.has(ad.id)}
likeCountAdj={likeCountAdj[ad.id] || 0}
onLike={onLike} onLike={onLike}
onEdit={activeNav === "myads" ? onEdit : undefined} onEdit={activeNav === "myads" ? onEdit : undefined}
onDelete={activeNav === "myads" ? onDelete : undefined} onDelete={activeNav === "myads" ? onDelete : undefined}

View File

@@ -1,16 +1,23 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import GlobeIcon from "../icons/GlobeIcon"; import GlobeIcon from "../icons/GlobeIcon";
import MoonIcon from "../icons/MoonIcon";
import SunIcon from "../icons/SunIcon";
import { useLanguageToggle } from "../../hooks/useLanguageToggle"; import { useLanguageToggle } from "../../hooks/useLanguageToggle";
import { useTheme } from "../../context/ThemeContext";
export default function Navbar({ onLoginClick }) { export default function Navbar({ onLoginClick }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { language, toggleLanguage } = useLanguageToggle(); const { language, toggleLanguage } = useLanguageToggle();
const { theme, toggle: toggleTheme } = useTheme();
return ( return (
<nav className="nk-nav"> <nav className="nk-nav">
<span className="nk-logo">{t('app.name')}</span> <span className="nk-logo">{t('app.name')}</span>
<div className="nk-nav-right"> <div className="nk-nav-right">
<button className="nk-theme-btn" onClick={toggleTheme} title={theme === 'dark' ? t('sidebar.lightTheme') : t('sidebar.darkTheme')}>
{theme === 'dark' ? <SunIcon /> : <MoonIcon />}
</button>
<button className="nk-lang-btn" onClick={toggleLanguage}> <button className="nk-lang-btn" onClick={toggleLanguage}>
<GlobeIcon /> <GlobeIcon />
{language === 'ru' ? 'EN' : 'RU'} {language === 'ru' ? 'EN' : 'RU'}

View File

@@ -1,5 +1,5 @@
// src/hooks/useFeedActions.js // src/hooks/useFeedActions.js
import { useState, useCallback } from "react"; import { useState, useCallback, useRef } from "react";
import { useDeletePost } from "./useDeletePost"; import { useDeletePost } from "./useDeletePost";
import { useFavorites } from "./useFavorites"; import { useFavorites } from "./useFavorites";
import { usePostStatus } from "./usePostStatus"; import { usePostStatus } from "./usePostStatus";
@@ -22,12 +22,18 @@ import { usePostStatus } from "./usePostStatus";
* @param {Function} onAuthRequired — вызывается когда нужна авторизация * @param {Function} onAuthRequired — вызывается когда нужна авторизация
* @param {boolean} isAuthenticated * @param {boolean} isAuthenticated
*/ */
export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthenticated, t }) { export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthenticated, t, setLiked, setLikeCountAdj }) {
const { deletePost } = useDeletePost(); const { deletePost } = useDeletePost();
const { toggleFavorite } = useFavorites(); const { toggleFavorite } = useFavorites();
const { markAsFound } = usePostStatus(); const { markAsFound } = usePostStatus();
const [error, setError] = useState(""); const [error, setError] = useState("");
const likedRef = useRef(new Set());
const syncLikedRef = useCallback((prev) => {
likedRef.current = prev instanceof Set ? prev : new Set(prev);
return prev;
}, []);
const afterMutation = useCallback(() => { const afterMutation = useCallback(() => {
refresh(); refresh();
@@ -39,13 +45,35 @@ export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthent
onAuthRequired(); onAuthRequired();
return; return;
} }
if (setLiked) {
const wasCurrentlyLiked = likedRef.current.has(id);
const delta = wasCurrentlyLiked ? -1 : 1;
setLiked(prev => {
const next = new Set(prev);
if (next.has(id)) next.delete(id); else next.add(id);
likedRef.current = next;
return next;
});
if (setLikeCountAdj) {
setLikeCountAdj(prev => ({ ...prev, [id]: (prev[id] || 0) + delta }));
}
const result = await toggleFavorite(id); const result = await toggleFavorite(id);
if (result.success) { if (!result.success) {
afterMutation(); setLiked(prev => {
} else { const next = new Set(prev);
if (next.has(id)) next.delete(id); else next.add(id);
likedRef.current = next;
return next;
});
if (setLikeCountAdj) {
setLikeCountAdj(prev => ({ ...prev, [id]: (prev[id] || 0) - delta }));
}
setError(result.error); setError(result.error);
} }
}, [isAuthenticated, onAuthRequired, toggleFavorite, afterMutation]); }
}, [isAuthenticated, onAuthRequired, toggleFavorite, setLiked, setLikeCountAdj]);
const handleDelete = useCallback(async (postId) => { const handleDelete = useCallback(async (postId) => {
if (!window.confirm(t('feed.confirmDelete'))) return; if (!window.confirm(t('feed.confirmDelete'))) return;
@@ -75,6 +103,7 @@ export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthent
handleMarkFound, handleMarkFound,
handleNewAd: afterMutation, handleNewAd: afterMutation,
handleEditSave: afterMutation, handleEditSave: afterMutation,
syncLiked: syncLikedRef,
error, error,
clearError, clearError,
}; };

View File

@@ -208,8 +208,8 @@
}, },
"landing": { "landing": {
"eyebrow": "Service for finding lost pets and items", "eyebrow": "Service for finding lost pets and items",
"heroTitle": "Lost doesn't mean", "heroTitle": "We believe that everything lost can be found.",
"heroAccent": "lost forever", "heroAccent": "You are not alone — we are searching together.",
"heroLead": "Post about your loss or find — Nakhodka will automatically match similar listings by photo, location, and description, showing matches near you.", "heroLead": "Post about your loss or find — Nakhodka will automatically match similar listings by photo, location, and description, showing matches near you.",
"startSearch": "Start search", "startSearch": "Start search",
"howItWorks": "How it works", "howItWorks": "How it works",

View File

@@ -209,8 +209,8 @@
}, },
"landing": { "landing": {
"eyebrow": "Сервис поиска пропавших питомцев и вещей", "eyebrow": "Сервис поиска пропавших питомцев и вещей",
"heroTitle": "Потерял — не значит", "heroTitle": "Верим, что всё потерянное можно найти.",
"heroAccent": "потерял навсегда", "heroAccent": "Ты не один — мы ищем вместе",
"heroLead": "Разместите объявление о пропаже или находке — Находка сама сопоставит похожие записи по фото, месту и описанию и покажет совпадения рядом с вами.", "heroLead": "Разместите объявление о пропаже или находке — Находка сама сопоставит похожие записи по фото, месту и описанию и покажет совпадения рядом с вами.",
"startSearch": "Начать поиск", "startSearch": "Начать поиск",
"howItWorks": "Как это работает", "howItWorks": "Как это работает",

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import CloseIcon from "../components/icons/CloseIcon"; import CloseIcon from "../components/icons/CloseIcon";
import RulesModal from "../components/RulesModal"; import RulesModal from "./RulesModal";
import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants"; import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants";
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePostMutation } from "../hooks/usePostMutation"; import { usePostMutation } from "../hooks/usePostMutation";

View File

@@ -10,7 +10,7 @@ import ImageViewer from "../components/ImageViewer";
import CityFilter from "../components/CityFilter"; import CityFilter from "../components/CityFilter";
import { ProfileModal } from "../components/profile"; import { ProfileModal } from "../components/profile";
import AuthModal from "../components/AuthModal"; import AuthModal from "../components/AuthModal";
import RulesModal from "../components/RulesModal"; import RulesModal from "./RulesModal";
import FeedTopBar from "../components/feed/FeedTopBar"; import FeedTopBar from "../components/feed/FeedTopBar";
import FeedSearchBar from "../components/feed/FeedSearchBar"; import FeedSearchBar from "../components/feed/FeedSearchBar";
import TypeFilterBar from "../components/feed/TypeFilterBar"; import TypeFilterBar from "../components/feed/TypeFilterBar";
@@ -45,6 +45,7 @@ export default function MainFeed() {
const [selectedCities, setSelectedCities] = useState([]); const [selectedCities, setSelectedCities] = useState([]);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [liked, setLiked] = useState(new Set()); const [liked, setLiked] = useState(new Set());
const [likeCountAdj, setLikeCountAdj] = useState({});
const feedRef = useRef(null); const feedRef = useRef(null);
@@ -85,15 +86,23 @@ export default function MainFeed() {
onAuthRequired: () => modals.open("auth"), onAuthRequired: () => modals.open("auth"),
isAuthenticated, isAuthenticated,
t, t,
setLiked,
setLikeCountAdj,
}); });
// Загружаем лайки пользователя при авторизации // Загружаем лайки пользователя при авторизации
useEffect(() => { useEffect(() => {
if (isAuthenticated && token) { if (isAuthenticated && token) {
loadFavorites().then(favoriteIds => setLiked(new Set(favoriteIds))); loadFavorites().then(favoriteIds => {
const s = new Set(favoriteIds);
actions.syncLiked(s);
setLiked(s);
});
} else { } else {
actions.syncLiked(new Set());
setLiked(new Set()); setLiked(new Set());
} }
setLikeCountAdj({});
}, [isAuthenticated, token, loadFavorites]); }, [isAuthenticated, token, loadFavorites]);
// Открываем модалку аутентификации если пришли с лендинга // Открываем модалку аутентификации если пришли с лендинга
@@ -102,7 +111,7 @@ export default function MainFeed() {
modals.open("auth"); modals.open("auth");
window.history.replaceState({}, document.title); window.history.replaceState({}, document.title);
} }
}, [location.state, modals]); }, [location.state]); // eslint-disable-line react-hooks/exhaustive-deps
// Первичная загрузка ленты (дебаунс поиска), только когда нет фильтра по городам // Первичная загрузка ленты (дебаунс поиска), только когда нет фильтра по городам
useEffect(() => { useEffect(() => {
@@ -229,6 +238,7 @@ export default function MainFeed() {
filteredAds={filteredAds} filteredAds={filteredAds}
activeNav={activeNav} activeNav={activeNav}
liked={liked} liked={liked}
likeCountAdj={likeCountAdj}
onLike={actions.toggleLike} onLike={actions.toggleLike}
onEdit={handleEdit} onEdit={handleEdit}
onDelete={actions.handleDelete} onDelete={actions.handleDelete}

View File

@@ -11,25 +11,18 @@ import DateCalendarPopup from "../components/DateCalendarPopup";
import Toast from "../components/Toast"; import Toast from "../components/Toast";
import {useTranslation} from 'react-i18next'; import {useTranslation} from 'react-i18next';
export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMarkFound, isOwner, onImageClick, onAuthRequired }) { export default function PostOnFeed({ ad, liked, likeCountAdj, onLike, onEdit, onDelete, onMarkFound, isOwner, onImageClick, onAuthRequired }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [showCalendar, setShowCalendar] = useState(false); const [showCalendar, setShowCalendar] = useState(false);
const [titleExpanded, setTitleExpanded] = useState(false);
const [descExpanded, setDescExpanded] = useState(false); const [descExpanded, setDescExpanded] = useState(false);
const [titleTruncated, setTitleTruncated] = useState(false);
const [descTruncated, setDescTruncated] = useState(false); const [descTruncated, setDescTruncated] = useState(false);
const dateAnchorRef = useRef(null); const dateAnchorRef = useRef(null);
const titleRef = useRef(null);
const descRef = useRef(null); const descRef = useRef(null);
const { isAuthenticated } = useAuthContext(); const { isAuthenticated } = useAuthContext();
const { contactSeller, error: contactError, clearError } = useContactSeller(ad); const { contactSeller, error: contactError, clearError } = useContactSeller(ad);
// Определяем, обрезан ли title/description (тремя точками), и реагируем на ресайз
useEffect(() => { useEffect(() => {
const check = () => { const check = () => {
if (!titleExpanded && titleRef.current) {
setTitleTruncated(titleRef.current.scrollWidth > titleRef.current.clientWidth + 1);
}
if (!descExpanded && descRef.current) { if (!descExpanded && descRef.current) {
setDescTruncated(descRef.current.scrollHeight > descRef.current.clientHeight + 1); setDescTruncated(descRef.current.scrollHeight > descRef.current.clientHeight + 1);
} }
@@ -37,7 +30,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
check(); check();
window.addEventListener("resize", check); window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check); return () => window.removeEventListener("resize", check);
}, [titleExpanded, descExpanded]); }, [descExpanded]);
const handleContact = (e) => { const handleContact = (e) => {
e.stopPropagation(); e.stopPropagation();
@@ -68,9 +61,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
<div className="f-card-head"> <div className="f-card-head">
<div className="f-card-author"> <div className="f-card-author">
<div <div
ref={titleRef} className="f-card-author-name"
className={`f-card-author-name${titleTruncated ? " f-truncatable" : ""}${titleExpanded ? " f-expanded" : ""}`}
onClick={titleTruncated ? () => setTitleExpanded(v => !v) : undefined}
> >
{ad.title} {ad.title}
</div> </div>
@@ -100,7 +91,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
}}> }}>
<HeartIcon filled={liked}/> <HeartIcon filled={liked}/>
</button> </button>
<span className="f-card-like-count">{ad.likeCount || 0}</span> <span className="f-card-like-count">{(ad.likeCount || 0) + likeCountAdj}</span>
</div> </div>
<div className="f-card-actions-right"> <div className="f-card-actions-right">
{onEdit && isOwner && !isClosed && ( {onEdit && isOwner && !isClosed && (

View File

@@ -6,7 +6,7 @@ import Sidebar from "../components/Sidebar";
import AppTopBar from "../components/layout/AppTopBar"; import AppTopBar from "../components/layout/AppTopBar";
import { ProfileModal } from "../components/profile"; import { ProfileModal } from "../components/profile";
import AuthModal from "../components/AuthModal"; import AuthModal from "../components/AuthModal";
import RulesModal from "../components/RulesModal"; import RulesModal from "./RulesModal";
import ReviewCard from "../components/reviews/ReviewCard"; import ReviewCard from "../components/reviews/ReviewCard";
import ReviewForm from "../components/reviews/ReviewForm"; import ReviewForm from "../components/reviews/ReviewForm";
import RatingSummary from "../components/reviews/RatingSummary"; import RatingSummary from "../components/reviews/RatingSummary";

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import CloseIcon from "./icons/CloseIcon"; import CloseIcon from "../components/icons/CloseIcon";
import "../styles/rules.css"; import "../styles/rules.css";
const RULES = [ const RULES = [
@@ -147,6 +148,13 @@ const RULES = [
export default function RulesModal({ onClose }) { export default function RulesModal({ onClose }) {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = prev; };
}, []);
return ( return (
<div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}> <div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}>
<div className="f-modal rules-modal"> <div className="f-modal rules-modal">

View File

@@ -2,8 +2,8 @@
.nk-page { .nk-page {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
background: linear-gradient(180deg, #ffffff 0%, #fafafa 100%); background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-app) 100%);
color: #262626; color: var(--text-primary);
min-height: 100vh; min-height: 100vh;
} }
@@ -71,10 +71,10 @@
.nk-grid { .nk-grid {
position: absolute; position: absolute;
inset: 0; inset: 0;
background-image: radial-gradient(rgba(38, 38, 38, 0.10) 1px, transparent 1px); background-image: radial-gradient(var(--text-primary) 1px, transparent 1px);
background-size: 26px 26px; background-size: 26px 26px;
animation: drift 14s linear infinite; animation: drift 14s linear infinite;
opacity: 0.5; opacity: 0.1;
} }
.nk-glow { .nk-glow {
@@ -100,7 +100,7 @@
font-size: 22px; font-size: 22px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.01em; letter-spacing: 0.01em;
color: #262626; color: var(--text-primary);
} }
.nk-nav-right { .nk-nav-right {
@@ -109,10 +109,11 @@
gap: 10px; gap: 10px;
} }
.nk-lang-btn { .nk-lang-btn,
background: #ffffff; .nk-theme-btn {
border: 1px solid #dbdbdb; background: var(--bg-card);
color: #262626; border: 1px solid var(--border);
color: var(--text-primary);
padding: 9px 12px; padding: 9px 12px;
border-radius: 999px; border-radius: 999px;
font-size: 12px; font-size: 12px;
@@ -124,8 +125,13 @@
transition: background 0.2s; transition: background 0.2s;
} }
.nk-lang-btn:hover { .nk-theme-btn {
background: #f0f0f0; padding: 9px;
}
.nk-lang-btn:hover,
.nk-theme-btn:hover {
background: var(--hover-bg);
} }
.nk-nav-btn { .nk-nav-btn {
@@ -194,16 +200,17 @@
position: relative; position: relative;
z-index: 2; z-index: 2;
max-width: 620px; max-width: 620px;
margin-top: 90px; margin-top: 40px;
animation: riseIn 0.8s ease both; animation: riseIn 0.8s ease both;
} }
.nk-eyebrow { .nk-eyebrow {
display: inline-block; display: inline-block;
font-size: 13px; font-size: 20px;
color: #737373; text-align: center;
color: var(--muted);
letter-spacing: 0.04em; letter-spacing: 0.04em;
margin-bottom: 18px; margin-bottom: 30px;
text-transform: uppercase; text-transform: uppercase;
font-weight: 600; font-weight: 600;
} }
@@ -211,10 +218,10 @@
.nk-h1 { .nk-h1 {
font-family: 'Fraunces', serif; font-family: 'Fraunces', serif;
font-weight: 600; font-weight: 600;
font-size: clamp(34px, 5.2vw, 58px); font-size: clamp(30px, 6.76vw, 75px);
line-height: 1.08; line-height: 1.08;
margin: 0 0 22px; margin: 0 0 22px;
color: #262626; color: var(--text-primary);
} }
.nk-accent { .nk-accent {
@@ -227,7 +234,7 @@
.nk-lead { .nk-lead {
font-size: 17px; font-size: 17px;
line-height: 1.6; line-height: 1.6;
color: #737373; color: var(--muted);
max-width: 500px; max-width: 500px;
margin-bottom: 34px; margin-bottom: 34px;
} }
@@ -259,11 +266,12 @@
.nk-cta-hint { .nk-cta-hint {
font-size: 13px; font-size: 13px;
color: #737373; color: var(--muted);
} }
.nk-steps-section { .nk-steps-section {
padding: 0 6vw 60px; padding: 0 6vw 60px;
margin-top: -60px;
} }
.nk-h2 { .nk-h2 {
@@ -272,7 +280,7 @@
font-weight: 600; font-weight: 600;
margin-bottom: 42px; margin-bottom: 42px;
text-align: center; text-align: center;
color: #262626; color: var(--text-primary);
} }
.nk-steps-grid { .nk-steps-grid {
@@ -284,22 +292,22 @@
} }
.nk-step-card { .nk-step-card {
background: #ffffff; background: var(--bg-card);
border: 1px solid #dbdbdb; border: 1px solid var(--border);
border-radius: 20px; border-radius: 20px;
padding: 26px 24px; padding: 26px 24px;
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
} }
.nk-step-card:hover { .nk-step-card:hover {
border-color: rgba(0, 149, 246, 0.4); border-color: rgba(var(--teal-rgb), 0.4);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
} }
.nk-step-num { .nk-step-num {
font-family: 'Fraunces', serif; font-family: 'Fraunces', serif;
font-size: 15px; font-size: 15px;
color: #0095f6; color: var(--teal);
font-weight: 600; font-weight: 600;
} }
@@ -307,13 +315,13 @@
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
margin: 12px 0 8px; margin: 12px 0 8px;
color: #262626; color: var(--text-primary);
} }
.nk-step-text { .nk-step-text {
font-size: 14.5px; font-size: 14.5px;
line-height: 1.55; line-height: 1.55;
color: #737373; color: var(--muted);
} }
.nk-final-cta { .nk-final-cta {
@@ -327,7 +335,7 @@
text-align: center; text-align: center;
padding: 24px 0 34px; padding: 24px 0 34px;
font-size: 13px; font-size: 13px;
color: #b0b0b0; color: var(--muted2);
} }
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@@ -31,13 +31,7 @@
.f-card-author { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; align-items: flex-start; } .f-card-author { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
.f-card-author-name { .f-card-author-name {
color: var(--text-primary); font-weight: 800; font-size: 1.1rem; color: var(--text-primary); font-weight: 800; font-size: 1.1rem;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; word-break: break-word;
}
.f-card-author-name.f-truncatable { cursor: pointer; }
.f-card-author-name.f-expanded {
white-space: normal;
overflow: visible;
text-overflow: clip;
} }
.f-card-author-loc { .f-card-author-loc {
color: var(--muted); font-size: .78rem; color: var(--muted); font-size: .78rem;
@@ -366,3 +360,10 @@
position: relative; position: relative;
} }
.f-date-pill:hover { background: var(--hover-bg); } .f-date-pill:hover { background: var(--hover-bg); }
@media (max-width: 600px) {
.f-card-contact {
padding: 20px 56px;
font-size: 1.5rem;
}
}

View File

@@ -70,6 +70,7 @@
.chat-messages { .chat-messages {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain;
padding: 20px; padding: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -194,6 +195,7 @@
.chat-list { .chat-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain;
padding: 8px; padding: 8px;
max-width: 560px; max-width: 560px;
width: 100%; width: 100%;
@@ -352,4 +354,8 @@
.chat-bubble { .chat-bubble {
max-width: 85%; max-width: 85%;
} }
.f-card-contact {
padding: 10px 28px;
font-size: 0.9rem;
}
} }

View File

@@ -306,7 +306,7 @@
.f-wrap { display: flex; flex: 1; overflow: hidden; min-height: 0; } .f-wrap { display: flex; flex: 1; overflow: hidden; min-height: 0; }
.f-feed { .f-feed {
flex: 1; overflow-y: scroll; min-height: 0; flex: 1; overflow-y: scroll; overscroll-behavior: contain; min-height: 0;
padding: 20px 16px; display: flex; flex-direction: column; gap: 16px; padding: 20px 16px; display: flex; flex-direction: column; gap: 16px;
width: 100%; max-width: 660px; margin: 0 auto; width: 100%; max-width: 660px; margin: 0 auto;
} }

View File

@@ -274,12 +274,23 @@
align-items: flex-end; align-items: flex-end;
} }
.f-modal { .f-modal-bg.auth-modal-bg {
align-items: center;
}
.f-modal:not(.auth-modal) {
width: 100%; width: 100%;
max-height: 92vh; max-height: 92vh;
border-radius: 20px 20px 0 0; border-radius: 20px 20px 0 0;
padding: 20px 16px 32px; /* 32px снизу — отступ от home indicator */ padding: 20px 16px 32px; /* 32px снизу — отступ от home indicator */
} }
.auth-modal {
width: 100%;
max-height: 80vh;
border-radius: 20px;
padding: 20px 16px 32px;
}
} }
/* ── Rules Checkbox ── */ /* ── Rules Checkbox ── */

View File

@@ -12,6 +12,7 @@
padding: 20px; padding: 20px;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain;
max-width: 900px; max-width: 900px;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;