add auth window

This commit is contained in:
SlimusMinus
2026-04-29 23:24:53 +03:00
parent bfbe5f9873
commit eb180a7da8
22 changed files with 460 additions and 19 deletions

49
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "react_front",
"version": "0.1.0",
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@pbe/react-yandex-maps": "^1.2.5",
"@tailwindcss/vite": "^4.2.4",
"@tanstack/react-query": "^5.100.5",
@@ -23,12 +24,17 @@
"postcss": "^8.5.12",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.74.0",
"react-router-dom": "^7.14.2",
"react-scripts": "5.0.1",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.4",
"web-vitals": "^2.1.4",
"zod": "^4.3.6",
"zustand": "^5.0.12"
},
"devDependencies": {
"@types/node": "^25.6.0"
}
},
"node_modules/@adobe/css-tools": {
@@ -2500,6 +2506,18 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@hookform/resolvers": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
"integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
"license": "MIT",
"dependencies": {
"@standard-schema/utils": "^0.3.0"
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -3469,6 +3487,12 @@
"@sinonjs/commons": "^1.7.0"
}
},
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -14833,6 +14857,22 @@
"integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==",
"license": "MIT"
},
"node_modules/react-hook-form": {
"version": "7.74.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.74.0.tgz",
"integrity": "sha512-yR6wHr99p9wFv686jhRWVSFhUvDvNbdUf2dKlbno8/VKOCuoNobDGC6S+M2dua9A9Yo8vpcrp8assIYbsZCQ9g==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -18543,6 +18583,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",

View File

@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@pbe/react-yandex-maps": "^1.2.5",
"@tailwindcss/vite": "^4.2.4",
"@tanstack/react-query": "^5.100.5",
@@ -18,11 +19,13 @@
"postcss": "^8.5.12",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.74.0",
"react-router-dom": "^7.14.2",
"react-scripts": "5.0.1",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.4",
"web-vitals": "^2.1.4",
"zod": "^4.3.6",
"zustand": "^5.0.12"
},
"scripts": {
@@ -48,5 +51,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/node": "^25.6.0"
}
}

View File

@@ -1,24 +1,20 @@
import logo from './logo.svg';
import './App.css';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import LoginPage from './pages/LoginPage';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<BrowserRouter>
<Routes>
{/* Главная → сразу редирект на логин */}
<Route path="/" element={<Navigate to="/login" replace />} />
{/* Страница авторизации */}
<Route path="/login" element={<LoginPage />} />
{/* Сюда добавишь остальные страницы позже */}
{/* <Route path="/dashboard" element={<Dashboard />} /> */}
</Routes>
</BrowserRouter>
);
}

1
src/assets/bag.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg fill="none" height="26" viewBox="0 0 28 26" width="28" xmlns="http://www.w3.org/2000/svg"><path d="M23.3 25H4.70001C2.80001 25 1.20001 23.4 1.20001 21.5V9.29999C1.20001 7.39999 2.80001 5.79999 4.70001 5.79999H23.3C25.2 5.79999 26.8 7.39999 26.8 9.29999V21.5C26.8 23.4 25.2 25 23.3 25Z" stroke="#4F4F4F" stroke-miterlimit="10" stroke-width="2"/><path d="M20.4 5.79999H7.60001V3C7.60001 1.9 8.50001 1 9.60001 1H18.4C19.5 1 20.4 1.9 20.4 3V5.79999Z" stroke="#4F4F4F" stroke-miterlimit="10" stroke-width="2"/><path d="M2 13.8H26.8" stroke="#4F4F4F" stroke-miterlimit="10" stroke-width="2"/><path d="M6.79999 12.2V16.2" stroke="#4F4F4F" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><path d="M21.2 12.2V16.2" stroke="#4F4F4F" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 836 B

BIN
src/assets/card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/dog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/glasses.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/assets/headphones.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
src/assets/keys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/parcel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
src/assets/wallet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
src/assets/watch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,85 @@
import { s } from '../styles/loginStyles';
import keysIcon from '../assets/passwords_keys_6032.png';
export default function MagnifierLogo() {
return (
<svg viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg" style={s.logoSvg}>
<defs>
<radialGradient id="glassGrad" cx="38%" cy="35%" r="60%">
<stop offset="0%" stopColor="#7ffff0"/>
<stop offset="45%" stopColor="#2de0c8"/>
<stop offset="100%" stopColor="#0a7a68"/>
</radialGradient>
<linearGradient id="rimGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#e8eef5"/>
<stop offset="40%" stopColor="#b0bec5"/>
<stop offset="100%" stopColor="#607d8b"/>
</linearGradient>
<linearGradient id="handleGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#4a4a4a"/>
<stop offset="25%" stopColor="#2a2a2a"/>
<stop offset="50%" stopColor="#111111"/>
<stop offset="75%" stopColor="#1e1e1e"/>
<stop offset="100%" stopColor="#000000"/>
</linearGradient>
<radialGradient id="innerGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="#ffffff" stopOpacity="0.25"/>
<stop offset="100%" stopColor="#ffffff" stopOpacity="0"/>
</radialGradient>
<linearGradient id="shimmer2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#ffffff" stopOpacity="0.7"/>
<stop offset="100%" stopColor="#ffffff" stopOpacity="0"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="1" dy="2" stdDeviation="3" floodColor="#000"
floodOpacity="0.5"/>
</filter>
<clipPath id="lensClip">
<circle cx="35" cy="35" r="23"/>
</clipPath>
</defs>
{/* Ручка */}
<g filter="url(#shadow)">
<rect x="60" y="56" width="11" height="28" rx="5.5"
fill="url(#handleGrad)" transform="rotate(-45 62.5 71)"/>
<rect x="61" y="56" width="3" height="22" rx="1.5"
fill="white" opacity="0.12" transform="rotate(-45 62.5 71)" />
<g transform="rotate(-45 62.5 71)" opacity="0.08">
<line x1="60" y1="62" x2="71" y2="62" stroke="white" strokeWidth="1"/>
<line x1="60" y1="66" x2="71" y2="66" stroke="white" strokeWidth="1"/>
<line x1="60" y1="70" x2="71" y2="70" stroke="white" strokeWidth="1"/>
<line x1="60" y1="74" x2="71" y2="74" stroke="white" strokeWidth="1"/>
</g>
</g>
{/* Металлический обод */}
<circle cx="35" cy="35" r="28" fill="url(#rimGrad)" filter="url(#shadow)" />
<circle cx="35" cy="35" r="26" fill="none" stroke="#ecf0f1" strokeWidth="1" opacity="0.6" />
<circle cx="35" cy="35" r="28" fill="none" stroke="#455a64" strokeWidth="1.5" opacity="0.5" />
{/* Стеклянная линза */}
<circle cx="35" cy="35" r="23" fill="url(#glassGrad)" />
<circle cx="35" cy="35" r="23" fill="url(#innerGlow)" />
{/* Иконка внутри линзы */}
<image
href={keysIcon}
x="20"
y="20"
width="29"
height="29"
clipPath="url(#lensClip)"
opacity="0.6"
/>
{/* Блики на стекле */}
<ellipse cx="27" cy="25" rx="9" ry="6"
fill="url(#shimmer2)" opacity="0.55" transform="rotate(-20 27 25)" />
<ellipse cx="23" cy="29" rx="3" ry="2"
fill="white" opacity="0.3" transform="rotate(-20 23 29)" />
<path d="M 14 22 Q 20 10 35 8" stroke="white" strokeWidth="1.5"
strokeLinecap="round" fill="none" opacity="0.45" />
</svg>
);
}

26
src/config/constants.js Normal file
View File

@@ -0,0 +1,26 @@
import bagIcon from '../assets/bag.svg';
import catIcon from '../assets/cat.png';
import dogIcon from '../assets/dog.png';
import phoneIcon from '../assets/phone.png';
import walletIcon from '../assets/wallet.png';
import parcelIcon from '../assets/parcel.png';
import cardIcon from '../assets/card.png';
import headphonesIcon from '../assets/headphones.png';
import watchIcon from '../assets/watch.png';
import glassesIcon from '../assets/glasses.png';
export const API_URL = process.env.REACT_APP_API_URL || "http://localhost:8080";
export const FLOAT_ICONS = [
{ icon: bagIcon, top: "5%", left: "5%", delay: "0s" },
{ icon: catIcon, top: "5%", right: "5%", delay: "1s" },
{ icon: dogIcon, top: "25%", left: "25%", delay: "0.4s" },
{ icon: phoneIcon, top: "25%", right: "17%", delay: "1.4s" },
{ icon: walletIcon, top: "45%", left: "5%", delay: "2s" },
{ icon: parcelIcon, top: "45%", right: "25%", delay: "0.7s" },
{ icon: cardIcon, top: "65%", left: "10%", delay: "1.7s" },
{ icon: headphonesIcon, top: "65%", right: "2%", delay: "2.4s" },
{ icon: watchIcon, bottom: "15%", left: "20%", delay: "3s" },
{ icon: glassesIcon, bottom: "5%", right: "20%", delay: "3.4s" },
];

54
src/hooks/useAuth.js Normal file
View File

@@ -0,0 +1,54 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { API_URL } from "../config/constants";
export function useAuth() {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const login = async (email, password) => {
if (!email || !password) { setError("Введи email и пароль"); return; }
setLoading(true); setError("");
try {
const res = await fetch(`${API_URL}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.message || "Неверный email или пароль");
}
const { token, user } = await res.json();
localStorage.setItem("token", token);
localStorage.setItem("user", JSON.stringify(user));
navigate("/dashboard");
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const loginGoogle = () => { /* TODO: @react-oauth/google */ };
const loginYandex = () => {
const clientId = process.env.REACT_APP_YANDEX_CLIENT_ID;
const redirect = encodeURIComponent(`${window.location.origin}/auth/yandex`);
window.location.href =
`https://oauth.yandex.ru/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirect}`;
};
const loginVK = () => {
const appId = process.env.REACT_APP_VK_APP_ID;
const redirect = encodeURIComponent(`${window.location.origin}/auth/vk`);
window.location.href =
`https://oauth.vk.com/authorize?client_id=${appId}&redirect_uri=${redirect}&response_type=code&scope=email`;
};
return { login, loginGoogle, loginYandex, loginVK, loading, error };
}

82
src/pages/LoginPage.jsx Normal file
View File

@@ -0,0 +1,82 @@
import { useState } from "react";
import MagnifierLogo from "../components/MagnifierLogo";
import { useAuth } from "../hooks/useAuth";
import { FLOAT_ICONS } from "../config/constants";
import { s } from "../styles/loginStyles";
import "../styles/global.css";
export default function LoginPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPass, setShowPass] = useState(false);
const { login, loginGoogle, loginYandex, loginVK, loading, error } = useAuth();
return (
<div style={s.page}>
<div style={s.bgGlow} />
{FLOAT_ICONS.map(({ icon, delay, size, ...pos }, i) => (
<img key={i} src={icon} alt="" className="fi" style={{ ...pos, animationDelay: delay, width: size || "7rem", height: size || "7rem", filter: "grayscale(100%) " }} />
))}
<div style={s.card} className="card-in">
<button style={s.dotsBtn}>⋮</button>
<div style={s.logoWrap}>
<MagnifierLogo />
</div>
<h1 style={s.title}>Находка</h1>
<form onSubmit={(e) => { e.preventDefault(); login(email, password); }} noValidate>
<div style={s.inputWrap}>
<input
type="email" placeholder="Введите email"
value={email} onChange={(e) => setEmail(e.target.value)}
style={s.input} className="li"
autoComplete="off" name="naidishchey-email-x7k"
readOnly onFocus={(e) => e.target.removeAttribute("readOnly")}
/>
</div>
<div style={{ ...s.inputWrap, position: "relative" }}>
<input
type={showPass ? "text" : "password"} placeholder="Введите пароль"
value={password} onChange={(e) => setPassword(e.target.value)}
style={{ ...s.input, paddingRight: "44px" }} className="li"
autoComplete="off" name="naidishchey-pass-x7k"
readOnly onFocus={(e) => e.target.removeAttribute("readOnly")}
/>
<button type="button" style={s.eyeBtn} onClick={() => setShowPass(v => !v)}>
{showPass ? "🙈" : "👁"}
</button>
</div>
{error && <p style={s.errMsg}>{error}</p>}
<button type="submit" style={s.btnLogin} className="btn-l" disabled={loading}>
{loading ? "Входим..." : "Войти"}
</button>
</form>
<button style={s.forgotBtn}>Забыли пароль?</button>
<p style={s.regRow}>
Нет аккаунта? <a href="/register" style={s.link}>Зарегистрироваться</a>
</p>
<div style={s.divider} className="div-line">
<span style={s.divText}>или</span>
</div>
<div style={s.socials}>
<button onClick={loginGoogle} style={s.socBtn} className="soc">Google</button>
<button onClick={loginYandex} style={{ ...s.socBtn, color: "#ffcc00" }} className="soc">Яндекс</button>
<button onClick={loginVK} style={{ ...s.socBtn, color: "#5b9bd5" }} className="soc">ВК</button>
</div>
</div>
</div>
);
}

54
src/styles/global.css Normal file
View File

@@ -0,0 +1,54 @@
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&display=swap');
.fi {
position: fixed;
opacity: 0.15;
filter: grayscale(1) brightness(2);
pointer-events: none;
user-select: none;
animation: float 7s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0) rotate(-6deg); }
50% { transform: translateY(-18px) rotate(6deg); }
}
.card-in {
animation: cardIn .7s cubic-bezier(.34,1.56,.64,1) both;
}
@keyframes cardIn {
from { opacity: 0; transform: scale(.86) translateY(40px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes logoPulse {
0%, 100% { filter: drop-shadow(0 0 18px rgba(45,224,200,.45)); }
50% { filter: drop-shadow(0 0 38px rgba(45,224,200,.8)); }
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.li:focus {
border-color: #2de0c8 !important;
background: rgba(45,224,200,.07) !important;
box-shadow: 0 0 0 3px rgba(45,224,200,.12) !important;
}
.li::placeholder { color: rgba(255,255,255,.32); }
.btn-l:hover { opacity: .88; transform: translateY(-2px); transition: all .15s; }
.btn-l:active { transform: translateY(0); }
.soc:hover {
background: rgba(255,255,255,.1) !important;
border-color: rgba(255,255,255,.22) !important;
transform: translateY(-1px);
transition: all .2s;
}
.div-line::before, .div-line::after {
content: ''; flex: 1;
height: 1px; background: rgba(255,255,255,.08);
}

88
src/styles/loginStyles.js Normal file
View File

@@ -0,0 +1,88 @@
export const s = {
page: {
fontFamily: "'Nunito', sans-serif",
minHeight: "100vh",
display: "flex", alignItems: "center", justifyContent: "center",
background: "radial-gradient(ellipse at 30% 40%, #1a2340 0%, #0d1018 60%)",
overflow: "hidden", position: "relative",
},
bgGlow: {
position: "fixed", top: "40%", right: 0,
width: "500px", height: "400px",
background: "radial-gradient(ellipse, #0f2a22 0%, transparent 70%)",
pointerEvents: "none", opacity: 0.7,
},
card: {
position: "relative", zIndex: 10, width: "360px",
background: "linear-gradient(155deg, #1d2436 0%, #131720 100%)",
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: "30px", padding: "40px 38px 34px",
boxShadow: "0 40px 100px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.03)",
},
dotsBtn: {
position: "absolute", top: "18px", right: "20px",
background: "none", border: "none",
color: "rgba(255,255,255,0.25)", fontSize: "1.3rem",
cursor: "pointer", letterSpacing: "3px",
},
logoWrap: { textAlign: "center", marginBottom: "2px" },
logoSvg: {
width: "130px", height: "130px",
animation: "logoPulse 3s ease-in-out infinite",
},
title: {
textAlign: "center", color: "#fff",
fontSize: "2rem", fontWeight: 900,
letterSpacing: "-0.5px", margin: "14px 0 26px",
},
inputWrap: { marginBottom: "13px" },
input: {
width: "100%", boxSizing: "border-box",
background: "rgba(255,255,255,0.055)",
border: "1.5px solid rgba(255,255,255,0.09)",
borderRadius: "14px", padding: "14px 18px",
color: "#fff", fontFamily: "'Nunito', sans-serif",
fontSize: "0.93rem", outline: "none",
},
eyeBtn: {
position: "absolute", right: "13px", top: "50%",
transform: "translateY(-50%)",
background: "none", border: "none", cursor: "pointer",
color: "rgba(255,255,255,0.35)", fontSize: "1.05rem", lineHeight: 1,
},
errMsg: {
color: "#ff6b6b", fontSize: "0.83rem",
textAlign: "center", margin: "6px 0 10px",
},
btnLogin: {
width: "100%",
background: "linear-gradient(90deg, #1fc9b0, #2de0c8, #1fc9b0)",
backgroundSize: "200% 100%",
border: "none", borderRadius: "14px", padding: "15px",
color: "#082320", fontFamily: "'Nunito', sans-serif",
fontSize: "1.05rem", fontWeight: 900, cursor: "pointer", marginTop: "6px",
boxShadow: "0 6px 24px rgba(45,224,200,0.35)",
animation: "shimmer 3s linear infinite",
},
forgotBtn: {
display: "block", width: "100%", textAlign: "center",
color: "#2de0c8", fontSize: "0.87rem", marginTop: "14px",
background: "none", border: "none", cursor: "pointer",
fontFamily: "'Nunito', sans-serif",
},
regRow: {
textAlign: "center", color: "rgba(255,255,255,0.4)",
fontSize: "0.87rem", marginTop: "9px",
},
link: { color: "#2de0c8", textDecoration: "none" },
divider: { display: "flex", alignItems: "center", gap: "10px", margin: "18px 0 14px" },
divText: { color: "rgba(255,255,255,0.25)", fontSize: "0.78rem" },
socials: { display: "flex", gap: "10px" },
socBtn: {
flex: 1, padding: "11px 0", borderRadius: "12px",
border: "1.5px solid rgba(255,255,255,0.09)",
background: "rgba(255,255,255,0.04)",
cursor: "pointer", fontWeight: 800,
fontFamily: "'Nunito', sans-serif", fontSize: "0.88rem", color: "#fff",
},
};