Merge pull request #30 from SlimusMinus/add_start-page
added new start page
This commit is contained in:
122
react_front/src/App.tsx
Normal file
122
react_front/src/App.tsx
Normal file
@@ -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 (
|
||||||
|
<>
|
||||||
|
<section id="center">
|
||||||
|
<div className="hero">
|
||||||
|
<img src={heroImg} className="base" width="170" height="179" alt="" />
|
||||||
|
<img src={reactLogo} className="framework" alt="React logo" />
|
||||||
|
<img src={viteLogo} className="vite" alt="Vite logo" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>Get started</h1>
|
||||||
|
<p>
|
||||||
|
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="counter"
|
||||||
|
onClick={() => setCount((count) => count + 1)}
|
||||||
|
>
|
||||||
|
Count is {count}
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="ticks"></div>
|
||||||
|
|
||||||
|
<section id="next-steps">
|
||||||
|
<div id="docs">
|
||||||
|
<svg className="icon" role="presentation" aria-hidden="true">
|
||||||
|
<use href="/icons.svg#documentation-icon"></use>
|
||||||
|
</svg>
|
||||||
|
<h2>Documentation</h2>
|
||||||
|
<p>Your questions, answered</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://vite.dev/" target="_blank">
|
||||||
|
<img className="logo" src={viteLogo} alt="" />
|
||||||
|
Explore Vite
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://react.dev/" target="_blank">
|
||||||
|
<img className="button-icon" src={reactLogo} alt="" />
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="social">
|
||||||
|
<svg className="icon" role="presentation" aria-hidden="true">
|
||||||
|
<use href="/icons.svg#social-icon"></use>
|
||||||
|
</svg>
|
||||||
|
<h2>Connect with us</h2>
|
||||||
|
<p>Join the Vite community</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||||
|
<svg
|
||||||
|
className="button-icon"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use href="/icons.svg#github-icon"></use>
|
||||||
|
</svg>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://chat.vite.dev/" target="_blank">
|
||||||
|
<svg
|
||||||
|
className="button-icon"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use href="/icons.svg#discord-icon"></use>
|
||||||
|
</svg>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://x.com/vite_js" target="_blank">
|
||||||
|
<svg
|
||||||
|
className="button-icon"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use href="/icons.svg#x-icon"></use>
|
||||||
|
</svg>
|
||||||
|
X.com
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||||
|
<svg
|
||||||
|
className="button-icon"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use href="/icons.svg#bluesky-icon"></use>
|
||||||
|
</svg>
|
||||||
|
Bluesky
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="ticks"></div>
|
||||||
|
<section id="spacer"></section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
13
src/App.js
13
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 { AuthProvider } from './context/AuthContext';
|
||||||
import { ThemeProvider } from './context/ThemeContext';
|
import { ThemeProvider } from './context/ThemeContext';
|
||||||
import { useAuthContext } from './context/AuthContext';
|
import { useAuthContext } from './context/AuthContext';
|
||||||
import { useUnreadSocket } from './hooks/useUnreadSocket';
|
import { useUnreadSocket } from './hooks/useUnreadSocket';
|
||||||
|
import LandingPage from './pages/NakhodkaLanding';
|
||||||
import RegisterPage from './components/Authentication';
|
import RegisterPage from './components/Authentication';
|
||||||
import MainFeed from './components/MainFeed';
|
import MainFeed from './pages/MainFeed';
|
||||||
import ChatPage from './components/ChatPage';
|
import ChatPage from './pages/ChatPage';
|
||||||
import ChatListPage from './components/ChatListPage';
|
import ChatListPage from './pages/ChatListPage';
|
||||||
import ReviewsPage from './components/ReviewsPage';
|
import ReviewsPage from './pages/ReviewsPage';
|
||||||
import './styles/chat.css';
|
import './styles/chat.css';
|
||||||
|
|
||||||
function UnreadListener() {
|
function UnreadListener() {
|
||||||
@@ -23,7 +24,7 @@ function App() {
|
|||||||
<UnreadListener />
|
<UnreadListener />
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/feed" replace />} />
|
<Route path="/" element={<LandingPage />} />
|
||||||
|
|
||||||
<Route path="/register" element={<RegisterPage />} />
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
<Route path="/feed" element={<MainFeed />} />
|
<Route path="/feed" element={<MainFeed />} />
|
||||||
|
|||||||
10
src/components/landing/FinalCta.jsx
Normal file
10
src/components/landing/FinalCta.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default function FinalCta({ onClick }) {
|
||||||
|
return (
|
||||||
|
<section className="nk-final-cta">
|
||||||
|
<h2 className="nk-h2">Кто-то прямо сейчас ищет то, что вы нашли</h2>
|
||||||
|
<button className="nk-cta-primary" onClick={onClick}>
|
||||||
|
Открыть Находку
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
src/components/landing/Footer.jsx
Normal file
7
src/components/landing/Footer.jsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="nk-footer">
|
||||||
|
© {new Date().getFullYear()} Находка. Возвращаем найденное.
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
src/components/landing/Hero.jsx
Normal file
50
src/components/landing/Hero.jsx
Normal file
@@ -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 (
|
||||||
|
<section className="nk-hero">
|
||||||
|
<div className="nk-grid" />
|
||||||
|
<div className="nk-glow" />
|
||||||
|
|
||||||
|
<RadarBeacon />
|
||||||
|
<Navbar onLoginClick={onLoginClick || onCtaClick} />
|
||||||
|
|
||||||
|
<div className="nk-hero-text">
|
||||||
|
<span className="nk-eyebrow">Сервис поиска пропавших питомцев и вещей</span>
|
||||||
|
<h1 className="nk-h1">
|
||||||
|
Потерял — не значит
|
||||||
|
<br />
|
||||||
|
<span className="nk-accent">потерял навсегда</span>
|
||||||
|
</h1>
|
||||||
|
<p className="nk-lead">
|
||||||
|
Разместите объявление о пропаже или находке — Находка сама сопоставит
|
||||||
|
похожие записи по фото, месту и описанию и покажет совпадения рядом с вами.
|
||||||
|
</p>
|
||||||
|
<div className="nk-cta-row">
|
||||||
|
<button
|
||||||
|
className="nk-cta-primary"
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}
|
||||||
|
onClick={onCtaClick}
|
||||||
|
>
|
||||||
|
Начать поиск
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 8,
|
||||||
|
display: "inline-block",
|
||||||
|
transform: hover ? "translateX(3px)" : "none",
|
||||||
|
transition: "transform .2s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/components/landing/Icon.jsx
Normal file
37
src/components/landing/Icon.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
export default function Icon({ type }) {
|
||||||
|
if (type === "paw") {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
|
||||||
|
<circle cx="7" cy="7" r="2.1" />
|
||||||
|
<circle cx="12.3" cy="5.2" r="2.1" />
|
||||||
|
<circle cx="17.2" cy="7.4" r="2.1" />
|
||||||
|
<path d="M12 10.5c-3.4 0-6.2 2.4-6.2 5.4 0 1.7 1.4 2.9 3 2.5 1.2-.3 2-1 3.2-1s2 .7 3.2 1c1.6.4 3-.8 3-2.5 0-3-2.8-5.4-6.2-5.4z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "bag") {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M4 10h16l-1.4 10.5a2 2 0 0 1-2 1.5H7.4a2 2 0 0 1-2-1.5L4 10z" />
|
||||||
|
<path d="M8 10V6a4 4 0 0 1 8 0v4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "key") {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||||
|
<circle cx="8" cy="8" r="4.2" />
|
||||||
|
<path d="M11 11l9 9M16.5 16.5l2.5-2.5M19 19l2-2" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||||
|
<rect x="7" y="2.5" width="10" height="19" rx="2.2" />
|
||||||
|
<line x1="11" y1="18.5" x2="13" y2="18.5" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
src/components/landing/Navbar.jsx
Normal file
10
src/components/landing/Navbar.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default function Navbar({ onLoginClick }) {
|
||||||
|
return (
|
||||||
|
<nav className="nk-nav">
|
||||||
|
<span className="nk-logo">Находка</span>
|
||||||
|
<button className="nk-nav-btn" onClick={onLoginClick}>
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
src/components/landing/RadarBeacon.jsx
Normal file
27
src/components/landing/RadarBeacon.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import Icon from "./Icon";
|
||||||
|
import { markers } from "../../config/landingConstants";
|
||||||
|
|
||||||
|
export default function RadarBeacon() {
|
||||||
|
return (
|
||||||
|
<div className="nk-radar">
|
||||||
|
<span className="nk-ring" style={{ animationDelay: "0s" }} />
|
||||||
|
<span className="nk-ring" style={{ animationDelay: "1.3s" }} />
|
||||||
|
<span className="nk-ring" style={{ animationDelay: "2.6s" }} />
|
||||||
|
<div className="nk-core" />
|
||||||
|
|
||||||
|
{markers.map((m) => (
|
||||||
|
<div
|
||||||
|
key={m.id}
|
||||||
|
className="nk-marker"
|
||||||
|
style={{
|
||||||
|
left: `${m.x}%`,
|
||||||
|
top: `${m.y}%`,
|
||||||
|
animationDelay: m.delay,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type={m.icon} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/components/landing/StepsSection.jsx
Normal file
18
src/components/landing/StepsSection.jsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { steps } from "../../config/landingConstants";
|
||||||
|
|
||||||
|
export default function StepsSection() {
|
||||||
|
return (
|
||||||
|
<section className="nk-steps-section">
|
||||||
|
<h2 className="nk-h2">Как это работает</h2>
|
||||||
|
<div className="nk-steps-grid">
|
||||||
|
{steps.map((s) => (
|
||||||
|
<div className="nk-step-card" key={s.n}>
|
||||||
|
<span className="nk-step-num">{s.n}</span>
|
||||||
|
<h3 className="nk-step-title">{s.title}</h3>
|
||||||
|
<p className="nk-step-text">{s.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
src/config/landingConstants.js
Normal file
32
src/config/landingConstants.js
Normal file
@@ -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: "поиск работает без перерыва" },
|
||||||
|
];
|
||||||
@@ -16,5 +16,4 @@ html, body, #root {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import { useUnread } from "../context/UnreadContext";
|
|||||||
import { getPostForChat } from "../stores/chatPostStore";
|
import { getPostForChat } from "../stores/chatPostStore";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { API_URL } from "../config/authConstants";
|
||||||
import { useDeleteChat } from "../hooks/useDeleteChat";
|
import { useDeleteChat } from "../hooks/useDeleteChat";
|
||||||
import TrashIcon from "./icons/TrashIcon";
|
import TrashIcon from "../components/icons/TrashIcon";
|
||||||
|
|
||||||
export default function ChatListPage() {
|
export default function ChatListPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
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 "../styles/index.css";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { API_URL } from "../config/authConstants";
|
||||||
import Sidebar from "./Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
import RightSidebar from "./RightSidebar";
|
import RightSidebar from "../components/RightSidebar";
|
||||||
import PostOnFeed from "./PostOnFeed";
|
import PostOnFeed from "../components/PostOnFeed";
|
||||||
import CreateUpdatePost from "./CreateUpdatePost";
|
import CreateUpdatePost from "../components/CreateUpdatePost";
|
||||||
import ImageViewer from "./ImageViewer";
|
import ImageViewer from "../components/ImageViewer";
|
||||||
import MenuIcon from "./icons/MenuIcon";
|
import MenuIcon from "../components/icons/MenuIcon";
|
||||||
import ProfileAvatar from "./profile/ProfileAvatar";
|
import ProfileAvatar from "../components/profile/ProfileAvatar";
|
||||||
import { ProfileModal } from "./profile";
|
import { ProfileModal } from "../components/profile";
|
||||||
import AuthModal from "./AuthModal";
|
import AuthModal from "../components/AuthModal";
|
||||||
import RulesModal from "./RulesModal";
|
import RulesModal from "../components/RulesModal";
|
||||||
import { useAuthContext } from "../context/AuthContext";
|
import { useAuthContext } from "../context/AuthContext";
|
||||||
import { useDeletePost } from "../hooks/useDeletePost";
|
import { useDeletePost } from "../hooks/useDeletePost";
|
||||||
import { useFavorites } from "../hooks/useFavorites";
|
import { useFavorites } from "../hooks/useFavorites";
|
||||||
@@ -24,6 +24,7 @@ import { usePostStatus } from "../hooks/usePostStatus";
|
|||||||
*/
|
*/
|
||||||
export default function MainFeed() {
|
export default function MainFeed() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const { user, token, isAuthenticated } = useAuthContext();
|
const { user, token, isAuthenticated } = useAuthContext();
|
||||||
const { deletePost } = useDeletePost();
|
const { deletePost } = useDeletePost();
|
||||||
const { toggleFavorite, loadFavorites } = useFavorites();
|
const { toggleFavorite, loadFavorites } = useFavorites();
|
||||||
@@ -139,6 +140,14 @@ export default function MainFeed() {
|
|||||||
}
|
}
|
||||||
}, [isAuthenticated, token, loadFavorites]);
|
}, [isAuthenticated, token, loadFavorites]);
|
||||||
|
|
||||||
|
// Открываем модалку аутентификации если пришли с лендинга
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.state?.openAuth) {
|
||||||
|
setAuthModalOpen(true);
|
||||||
|
window.history.replaceState({}, document.title);
|
||||||
|
}
|
||||||
|
}, [location.state]);
|
||||||
|
|
||||||
// Первичная загрузка при монтировании компонента
|
// Первичная загрузка при монтировании компонента
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
22
src/pages/NakhodkaLanding.jsx
Normal file
22
src/pages/NakhodkaLanding.jsx
Normal file
@@ -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 (
|
||||||
|
<div className="nk-page">
|
||||||
|
<Hero onCtaClick={goToFeed} onLoginClick={goToFeedWithAuth} />
|
||||||
|
<StepsSection />
|
||||||
|
<FinalCta onClick={goToFeed} />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@ import { useState, useCallback, useEffect } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { API_URL } from "../config/authConstants";
|
||||||
import { useAuthContext } from "../context/AuthContext";
|
import { useAuthContext } from "../context/AuthContext";
|
||||||
import MenuIcon from "./icons/MenuIcon";
|
import MenuIcon from "../components/icons/MenuIcon";
|
||||||
import Sidebar from "./Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
import ProfileAvatar from "./profile/ProfileAvatar";
|
import ProfileAvatar from "../components/profile/ProfileAvatar";
|
||||||
import { ProfileModal } from "./profile";
|
import { ProfileModal } from "../components/profile";
|
||||||
import AuthModal from "./AuthModal";
|
import AuthModal from "../components/AuthModal";
|
||||||
import "../styles/reviews.css";
|
import "../styles/reviews.css";
|
||||||
|
|
||||||
/* ── Star rating sub-component ── */
|
/* ── Star rating sub-component ── */
|
||||||
337
src/styles/NakhodkaLanding.css
Normal file
337
src/styles/NakhodkaLanding.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user