fixed start page and scroll in modal window
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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'}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
const result = await toggleFavorite(id);
|
if (setLiked) {
|
||||||
if (result.success) {
|
const wasCurrentlyLiked = likedRef.current.has(id);
|
||||||
afterMutation();
|
const delta = wasCurrentlyLiked ? -1 : 1;
|
||||||
} else {
|
|
||||||
setError(result.error);
|
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);
|
||||||
|
if (!result.success) {
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -209,8 +209,8 @@
|
|||||||
},
|
},
|
||||||
"landing": {
|
"landing": {
|
||||||
"eyebrow": "Сервис поиска пропавших питомцев и вещей",
|
"eyebrow": "Сервис поиска пропавших питомцев и вещей",
|
||||||
"heroTitle": "Потерял — не значит",
|
"heroTitle": "Верим, что всё потерянное можно найти.",
|
||||||
"heroAccent": "потерял навсегда",
|
"heroAccent": "Ты не один — мы ищем вместе",
|
||||||
"heroLead": "Разместите объявление о пропаже или находке — Находка сама сопоставит похожие записи по фото, месту и описанию и покажет совпадения рядом с вами.",
|
"heroLead": "Разместите объявление о пропаже или находке — Находка сама сопоставит похожие записи по фото, месту и описанию и покажет совпадения рядом с вами.",
|
||||||
"startSearch": "Начать поиск",
|
"startSearch": "Начать поиск",
|
||||||
"howItWorks": "Как это работает",
|
"howItWorks": "Как это работает",
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ── */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user