From abdb7c021c4d1bd38543bf9fb8c9ca7753bc292d Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Mon, 6 Jul 2026 23:59:48 +0300 Subject: [PATCH] added new start page --- react_front/src/App.tsx | 122 ++++++++ src/App.js | 13 +- src/components/landing/FinalCta.jsx | 10 + src/components/landing/Footer.jsx | 7 + src/components/landing/Hero.jsx | 50 +++ src/components/landing/Icon.jsx | 37 +++ src/components/landing/Navbar.jsx | 10 + src/components/landing/RadarBeacon.jsx | 27 ++ src/components/landing/StepsSection.jsx | 18 ++ src/config/landingConstants.js | 32 ++ src/index.css | 1 - src/{components => pages}/ChatListPage.jsx | 2 +- src/{components => pages}/ChatPage.jsx | 0 src/{components => pages}/MainFeed.jsx | 31 +- src/pages/NakhodkaLanding.jsx | 22 ++ src/{components => pages}/ReviewsPage.jsx | 10 +- src/styles/NakhodkaLanding.css | 337 +++++++++++++++++++++ 17 files changed, 705 insertions(+), 24 deletions(-) create mode 100644 react_front/src/App.tsx create mode 100644 src/components/landing/FinalCta.jsx create mode 100644 src/components/landing/Footer.jsx create mode 100644 src/components/landing/Hero.jsx create mode 100644 src/components/landing/Icon.jsx create mode 100644 src/components/landing/Navbar.jsx create mode 100644 src/components/landing/RadarBeacon.jsx create mode 100644 src/components/landing/StepsSection.jsx create mode 100644 src/config/landingConstants.js rename src/{components => pages}/ChatListPage.jsx (98%) rename src/{components => pages}/ChatPage.jsx (100%) rename src/{components => pages}/MainFeed.jsx (95%) create mode 100644 src/pages/NakhodkaLanding.jsx rename src/{components => pages}/ReviewsPage.jsx (97%) create mode 100644 src/styles/NakhodkaLanding.css diff --git a/react_front/src/App.tsx b/react_front/src/App.tsx new file mode 100644 index 0000000..a66b5ef --- /dev/null +++ b/react_front/src/App.tsx @@ -0,0 +1,122 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from './assets/vite.svg' +import heroImg from './assets/hero.png' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +
+
+ + React logo + Vite logo +
+
+

Get started

+

+ Edit src/App.tsx and save to test HMR +

+
+ +
+ +
+ +
+
+ +

Documentation

+

Your questions, answered

+ +
+
+ +

Connect with us

+

Join the Vite community

+ +
+
+ +
+
+ + ) +} + +export default App diff --git a/src/App.js b/src/App.js index e570c32..07906e8 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,14 @@ -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { AuthProvider } from './context/AuthContext'; import { ThemeProvider } from './context/ThemeContext'; import { useAuthContext } from './context/AuthContext'; import { useUnreadSocket } from './hooks/useUnreadSocket'; +import LandingPage from './pages/NakhodkaLanding'; import RegisterPage from './components/Authentication'; -import MainFeed from './components/MainFeed'; -import ChatPage from './components/ChatPage'; -import ChatListPage from './components/ChatListPage'; -import ReviewsPage from './components/ReviewsPage'; +import MainFeed from './pages/MainFeed'; +import ChatPage from './pages/ChatPage'; +import ChatListPage from './pages/ChatListPage'; +import ReviewsPage from './pages/ReviewsPage'; import './styles/chat.css'; function UnreadListener() { @@ -23,7 +24,7 @@ function App() { - } /> + } /> } /> } /> diff --git a/src/components/landing/FinalCta.jsx b/src/components/landing/FinalCta.jsx new file mode 100644 index 0000000..e30e87d --- /dev/null +++ b/src/components/landing/FinalCta.jsx @@ -0,0 +1,10 @@ +export default function FinalCta({ onClick }) { + return ( +
+

Кто-то прямо сейчас ищет то, что вы нашли

+ +
+ ); +} diff --git a/src/components/landing/Footer.jsx b/src/components/landing/Footer.jsx new file mode 100644 index 0000000..e894eee --- /dev/null +++ b/src/components/landing/Footer.jsx @@ -0,0 +1,7 @@ +export default function Footer() { + return ( +
+ © {new Date().getFullYear()} Находка. Возвращаем найденное. +
+ ); +} diff --git a/src/components/landing/Hero.jsx b/src/components/landing/Hero.jsx new file mode 100644 index 0000000..5526f74 --- /dev/null +++ b/src/components/landing/Hero.jsx @@ -0,0 +1,50 @@ +import { useState } from "react"; +import Navbar from "./Navbar"; +import RadarBeacon from "./RadarBeacon"; + +export default function Hero({ onCtaClick, onLoginClick }) { + const [hover, setHover] = useState(false); + + return ( +
+
+
+ + + + +
+ Сервис поиска пропавших питомцев и вещей +

+ Потерял — не значит +
+ потерял навсегда +

+

+ Разместите объявление о пропаже или находке — Находка сама сопоставит + похожие записи по фото, месту и описанию и покажет совпадения рядом с вами. +

+
+ +
+
+
+ ); +} diff --git a/src/components/landing/Icon.jsx b/src/components/landing/Icon.jsx new file mode 100644 index 0000000..db0cafe --- /dev/null +++ b/src/components/landing/Icon.jsx @@ -0,0 +1,37 @@ +export default function Icon({ type }) { + if (type === "paw") { + return ( + + + + + + + ); + } + + if (type === "bag") { + return ( + + + + + ); + } + + if (type === "key") { + return ( + + + + + ); + } + + return ( + + + + + ); +} diff --git a/src/components/landing/Navbar.jsx b/src/components/landing/Navbar.jsx new file mode 100644 index 0000000..94dec61 --- /dev/null +++ b/src/components/landing/Navbar.jsx @@ -0,0 +1,10 @@ +export default function Navbar({ onLoginClick }) { + return ( + + ); +} diff --git a/src/components/landing/RadarBeacon.jsx b/src/components/landing/RadarBeacon.jsx new file mode 100644 index 0000000..b626f2a --- /dev/null +++ b/src/components/landing/RadarBeacon.jsx @@ -0,0 +1,27 @@ +import Icon from "./Icon"; +import { markers } from "../../config/landingConstants"; + +export default function RadarBeacon() { + return ( +
+ + + +
+ + {markers.map((m) => ( +
+ +
+ ))} +
+ ); +} diff --git a/src/components/landing/StepsSection.jsx b/src/components/landing/StepsSection.jsx new file mode 100644 index 0000000..da8274f --- /dev/null +++ b/src/components/landing/StepsSection.jsx @@ -0,0 +1,18 @@ +import { steps } from "../../config/landingConstants"; + +export default function StepsSection() { + return ( +
+

Как это работает

+
+ {steps.map((s) => ( +
+ {s.n} +

{s.title}

+

{s.text}

+
+ ))} +
+
+ ); +} diff --git a/src/config/landingConstants.js b/src/config/landingConstants.js new file mode 100644 index 0000000..5d8320f --- /dev/null +++ b/src/config/landingConstants.js @@ -0,0 +1,32 @@ + + +export const markers = [ + { id: "paw", x: 14, y: 18, delay: "0.4s", icon: "paw" }, + { id: "key", x: 72, y: 22, delay: "1.1s", icon: "key" }, + { id: "phone", x: 60, y: 62, delay: "1.8s", icon: "phone" }, + { id: "bag", x: 26, y: 70, delay: "2.4s", icon: "bag" }, +]; + +export const steps = [ + { + n: "01", + title: "Разместите объявление", + text: "Опишите пропажу или находку: фото, место, приметы. Это занимает меньше минуты.", + }, + { + n: "02", + title: "Система ищет совпадения", + text: "Находка сама сравнивает похожие объявления по фото, месту и описанию — по всему городу.", + }, + { + n: "03", + title: "Свяжитесь и заберите", + text: "Как только есть совпадение — пишите в чат прямо в приложении и договоритесь о встрече.", + }, +]; + +export const stats = [ + { value: "12 480", label: "находок опубликовано" }, + { value: "9 200+", label: "воссоединений с хозяевами" }, + { value: "24/7", label: "поиск работает без перерыва" }, +]; diff --git a/src/index.css b/src/index.css index e8af213..4b33d4d 100644 --- a/src/index.css +++ b/src/index.css @@ -16,5 +16,4 @@ html, body, #root { height: 100%; margin: 0; padding: 0; - overflow: hidden; } \ No newline at end of file diff --git a/src/components/ChatListPage.jsx b/src/pages/ChatListPage.jsx similarity index 98% rename from src/components/ChatListPage.jsx rename to src/pages/ChatListPage.jsx index ec4a9e3..a0da74d 100644 --- a/src/components/ChatListPage.jsx +++ b/src/pages/ChatListPage.jsx @@ -6,7 +6,7 @@ import { useUnread } from "../context/UnreadContext"; import { getPostForChat } from "../stores/chatPostStore"; import { API_URL } from "../config/authConstants"; import { useDeleteChat } from "../hooks/useDeleteChat"; -import TrashIcon from "./icons/TrashIcon"; +import TrashIcon from "../components/icons/TrashIcon"; export default function ChatListPage() { const navigate = useNavigate(); diff --git a/src/components/ChatPage.jsx b/src/pages/ChatPage.jsx similarity index 100% rename from src/components/ChatPage.jsx rename to src/pages/ChatPage.jsx diff --git a/src/components/MainFeed.jsx b/src/pages/MainFeed.jsx similarity index 95% rename from src/components/MainFeed.jsx rename to src/pages/MainFeed.jsx index 39d587f..44fb896 100644 --- a/src/components/MainFeed.jsx +++ b/src/pages/MainFeed.jsx @@ -1,17 +1,17 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import "../styles/index.css"; import { API_URL } from "../config/authConstants"; -import Sidebar from "./Sidebar"; -import RightSidebar from "./RightSidebar"; -import PostOnFeed from "./PostOnFeed"; -import CreateUpdatePost from "./CreateUpdatePost"; -import ImageViewer from "./ImageViewer"; -import MenuIcon from "./icons/MenuIcon"; -import ProfileAvatar from "./profile/ProfileAvatar"; -import { ProfileModal } from "./profile"; -import AuthModal from "./AuthModal"; -import RulesModal from "./RulesModal"; +import Sidebar from "../components/Sidebar"; +import RightSidebar from "../components/RightSidebar"; +import PostOnFeed from "../components/PostOnFeed"; +import CreateUpdatePost from "../components/CreateUpdatePost"; +import ImageViewer from "../components/ImageViewer"; +import MenuIcon from "../components/icons/MenuIcon"; +import ProfileAvatar from "../components/profile/ProfileAvatar"; +import { ProfileModal } from "../components/profile"; +import AuthModal from "../components/AuthModal"; +import RulesModal from "../components/RulesModal"; import { useAuthContext } from "../context/AuthContext"; import { useDeletePost } from "../hooks/useDeletePost"; import { useFavorites } from "../hooks/useFavorites"; @@ -24,6 +24,7 @@ import { usePostStatus } from "../hooks/usePostStatus"; */ export default function MainFeed() { const navigate = useNavigate(); + const location = useLocation(); const { user, token, isAuthenticated } = useAuthContext(); const { deletePost } = useDeletePost(); const { toggleFavorite, loadFavorites } = useFavorites(); @@ -139,6 +140,14 @@ export default function MainFeed() { } }, [isAuthenticated, token, loadFavorites]); + // Открываем модалку аутентификации если пришли с лендинга + useEffect(() => { + if (location.state?.openAuth) { + setAuthModalOpen(true); + window.history.replaceState({}, document.title); + } + }, [location.state]); + // Первичная загрузка при монтировании компонента useEffect(() => { const timer = setTimeout(() => { diff --git a/src/pages/NakhodkaLanding.jsx b/src/pages/NakhodkaLanding.jsx new file mode 100644 index 0000000..b335142 --- /dev/null +++ b/src/pages/NakhodkaLanding.jsx @@ -0,0 +1,22 @@ +import { useNavigate } from "react-router-dom"; +import "../styles/NakhodkaLanding.css"; +import Hero from "../components/landing/Hero"; +import StepsSection from "../components/landing/StepsSection"; +import FinalCta from "../components/landing/FinalCta"; +import Footer from "../components/landing/Footer"; + +export default function NakhodkaLanding() { + const navigate = useNavigate(); + + const goToFeed = () => navigate("/feed"); + const goToFeedWithAuth = () => navigate("/feed", { state: { openAuth: true } }); + + return ( +
+ + + +
+ ); +} diff --git a/src/components/ReviewsPage.jsx b/src/pages/ReviewsPage.jsx similarity index 97% rename from src/components/ReviewsPage.jsx rename to src/pages/ReviewsPage.jsx index 2c5c0a7..f4ca240 100644 --- a/src/components/ReviewsPage.jsx +++ b/src/pages/ReviewsPage.jsx @@ -2,11 +2,11 @@ import { useState, useCallback, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { API_URL } from "../config/authConstants"; import { useAuthContext } from "../context/AuthContext"; -import MenuIcon from "./icons/MenuIcon"; -import Sidebar from "./Sidebar"; -import ProfileAvatar from "./profile/ProfileAvatar"; -import { ProfileModal } from "./profile"; -import AuthModal from "./AuthModal"; +import MenuIcon from "../components/icons/MenuIcon"; +import Sidebar from "../components/Sidebar"; +import ProfileAvatar from "../components/profile/ProfileAvatar"; +import { ProfileModal } from "../components/profile"; +import AuthModal from "../components/AuthModal"; import "../styles/reviews.css"; /* ── Star rating sub-component ── */ diff --git a/src/styles/NakhodkaLanding.css b/src/styles/NakhodkaLanding.css new file mode 100644 index 0000000..3db0f20 --- /dev/null +++ b/src/styles/NakhodkaLanding.css @@ -0,0 +1,337 @@ +@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap'); + +.nk-page { + font-family: 'Inter', sans-serif; + background: #0A1F1E; + color: #F4F7F6; + min-height: 100vh; +} + +@keyframes beaconPulse { + 0% { + transform: scale(0.6); + opacity: 0.55; + } + 70% { + transform: scale(1.9); + opacity: 0; + } + 100% { + transform: scale(1.9); + opacity: 0; + } +} + +@keyframes markerIn { + 0% { + opacity: 0; + transform: translateY(6px) scale(0.7); + } + 15% { + opacity: 1; + transform: translateY(0) scale(1); + } + 85% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +@keyframes drift { + 0% { + background-position: 0 0; + } + 100% { + background-position: 60px 60px; + } +} + +@keyframes riseIn { + 0% { + opacity: 0; + transform: translateY(16px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.nk-hero { + position: relative; + padding: 28px 6vw 0; + min-height: 620px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.nk-grid { + position: absolute; + inset: 0; + background-image: radial-gradient(rgba(244, 247, 246, 0.10) 1px, transparent 1px); + background-size: 26px 26px; + animation: drift 14s linear infinite; + opacity: 0.6; +} + +.nk-glow { + position: absolute; + top: -20%; + right: -10%; + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(245, 166, 35, 0.18) 0%, transparent 70%); + filter: blur(10px); +} + +.nk-nav { + position: relative; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; +} + +.nk-logo { + font-family: 'Fraunces', serif; + font-size: 22px; + font-weight: 600; + letter-spacing: 0.01em; +} + +.nk-nav-btn { + background: rgba(244, 247, 246, 0.08); + border: 1px solid rgba(244, 247, 246, 0.16); + color: #F4F7F6; + padding: 9px 20px; + border-radius: 999px; + font-size: 14px; + cursor: pointer; + backdrop-filter: blur(6px); +} + +.nk-radar { + position: absolute; + right: 4vw; + top: 90px; + width: 340px; + height: 340px; + display: flex; + align-items: center; + justify-content: center; + z-index: 3; +} + +.nk-ring { + position: absolute; + width: 140px; + height: 140px; + border-radius: 50%; + border: 1px solid rgba(245, 166, 35, 0.55); + animation: beaconPulse 3.9s ease-out infinite; + will-change: transform; + transform: translateZ(0); +} + +.nk-core { + width: 14px; + height: 14px; + border-radius: 50%; + background: #F5A623; + box-shadow: 0 0 22px 6px rgba(245, 166, 35, 0.55); +} + +.nk-marker { + position: absolute; + color: #0A1F1E; + background: #F5A623; + width: 34px; + height: 34px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + animation: markerIn 3.2s ease-in-out infinite; + box-shadow: 0 4px 14px rgba(245, 166, 35, 0.4); + will-change: transform; + transform: translateZ(0); +} + +.nk-hero-text { + position: relative; + z-index: 2; + max-width: 620px; + margin-top: 90px; + animation: riseIn 0.8s ease both; +} + +.nk-eyebrow { + display: inline-block; + font-size: 13px; + color: #8FADA8; + letter-spacing: 0.04em; + margin-bottom: 18px; + text-transform: uppercase; +} + +.nk-h1 { + font-family: 'Fraunces', serif; + font-weight: 600; + font-size: clamp(34px, 5.2vw, 58px); + line-height: 1.08; + margin: 0 0 22px; +} + +.nk-accent { + color: #F5A623; +} + +.nk-lead { + font-size: 17px; + line-height: 1.6; + color: #C7DAD6; + max-width: 500px; + margin-bottom: 34px; +} + +.nk-cta-row { + display: flex; + align-items: center; + gap: 18px; + flex-wrap: wrap; +} + +.nk-cta-primary { + background: #F5A623; + color: #0A1F1E; + border: none; + padding: 15px 30px; + border-radius: 999px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; +} + +.nk-cta-primary:hover { + transform: translateY(-2px); + box-shadow: 0 12px 28px rgba(245, 166, 35, 0.35); +} + +.nk-cta-hint { + font-size: 13px; + color: #7FA39D; +} + + +.nk-steps-section { + padding: 0 6vw 60px; +} + +.nk-h2 { + font-family: 'Fraunces', serif; + font-size: clamp(24px, 3vw, 34px); + font-weight: 600; + margin-bottom: 42px; + text-align: center; +} + +.nk-steps-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 22px; + max-width: 1000px; + margin: 0 auto; +} + +.nk-step-card { + background: rgba(244, 247, 246, 0.04); + border: 1px solid rgba(244, 247, 246, 0.10); + border-radius: 20px; + padding: 26px 24px; + transition: border-color 0.2s; +} + +.nk-step-card:hover { + border-color: rgba(244, 247, 246, 0.22); +} + +.nk-step-num { + font-family: 'Fraunces', serif; + font-size: 15px; + color: #F5A623; + font-weight: 600; +} + +.nk-step-title { + font-size: 18px; + font-weight: 600; + margin: 12px 0 8px; +} + +.nk-step-text { + font-size: 14.5px; + line-height: 1.55; + color: #9FBAB5; +} + +.nk-final-cta { + text-align: center; + padding: 70px 6vw 100px; + max-width: 700px; + margin: 0 auto; +} + +.nk-footer { + text-align: center; + padding: 24px 0 34px; + font-size: 13px; + color: #5F827D; +} + +@media (max-width: 768px) { + .nk-hero { + padding-bottom: 150px; + } + + .nk-radar { + width: 150px; + height: 150px; + right: 10vw; + top: auto; + bottom: 100px; + } + + .nk-ring { + width: 85px; + height: 85px; + } + + .nk-core { + width: 14px; + height: 14px; + } + + .nk-marker { + width: 30px; + height: 30px; + } + + .nk-marker svg { + width: 18px; + height: 18px; + } +} + +@media (prefers-reduced-motion: reduce) { + .nk-ring, .nk-marker { + animation-duration: 8s !important; + } + + .nk-grid { + animation: none !important; + } +}