Initial commit: Сисадмингрупп website
React + Vite + Tailwind, RU/EN, Hero/Services/About/Contact sections
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.DS_Store
|
||||
*.local
|
||||
14
index.html
Normal file
14
index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Сисадмингрупп — IT-аутсорсинг, кибербезопасность и техническая поддержка в Пушкино" />
|
||||
<title>Сисадмингрупп — IT-решения для бизнеса</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
2234
package-lock.json
generated
Normal file
2234
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "sag24-website",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/manrope": "^5.2.8",
|
||||
"lucide-react": "^0.263.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.24",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"vite": "^4.4.0"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
5
public/favicon.svg
Normal file
5
public/favicon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<rect width="32" height="32" rx="8" fill="#1d4ed8"/>
|
||||
<path d="M8 10h16M8 16h16M8 22h10" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<circle cx="24" cy="22" r="3" fill="#60a5fa"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 280 B |
17
src/App.jsx
Normal file
17
src/App.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import { LanguageProvider } from './contexts/LanguageContext.jsx'
|
||||
import Navigation from './components/Navigation.jsx'
|
||||
import Footer from './components/Footer.jsx'
|
||||
import Home from './pages/Home.jsx'
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<LanguageProvider>
|
||||
<Navigation />
|
||||
<main>
|
||||
<Home />
|
||||
</main>
|
||||
<Footer />
|
||||
</LanguageProvider>
|
||||
)
|
||||
}
|
||||
20
src/components/Footer.jsx
Normal file
20
src/components/Footer.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { useLanguage } from '../contexts/LanguageContext.jsx'
|
||||
|
||||
export default function Footer() {
|
||||
const { t } = useLanguage()
|
||||
const year = new Date().getFullYear()
|
||||
|
||||
return (
|
||||
<footer className="bg-slate-900 text-slate-400 py-10">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<span className="text-white font-semibold">Сисадмингрупп</span>
|
||||
<span className="text-sm">{year} © {t('footer.rights')}</span>
|
||||
<div className="flex gap-6 text-sm">
|
||||
<a href="tel:+74953637476" className="hover:text-white transition-colors">+7 495 363-74-76</a>
|
||||
<a href="mailto:info@sag24.ru" className="hover:text-white transition-colors">info@sag24.ru</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
70
src/components/Navigation.jsx
Normal file
70
src/components/Navigation.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Menu, X } from 'lucide-react'
|
||||
import { useLanguage } from '../contexts/LanguageContext.jsx'
|
||||
|
||||
export default function Navigation() {
|
||||
const { t, lang, toggle } = useLanguage()
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 40)
|
||||
window.addEventListener('scroll', onScroll)
|
||||
return () => window.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
const links = [
|
||||
{ label: t('nav.services'), href: '#services' },
|
||||
{ label: t('nav.about'), href: '#about' },
|
||||
{ label: t('nav.contact'), href: '#contact' },
|
||||
]
|
||||
|
||||
return (
|
||||
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${scrolled ? 'bg-white/95 backdrop-blur shadow-sm' : 'bg-transparent'}`}>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 h-16 flex items-center justify-between">
|
||||
<a href="#" className="text-xl font-bold text-slate-900 tracking-tight">
|
||||
Сисадмингрупп
|
||||
</a>
|
||||
|
||||
{/* Desktop nav */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{links.map(l => (
|
||||
<a key={l.href} href={l.href} className="text-sm font-medium text-slate-600 hover:text-blue-700 transition-colors">
|
||||
{l.label}
|
||||
</a>
|
||||
))}
|
||||
<button
|
||||
onClick={toggle}
|
||||
className="text-sm font-semibold text-slate-500 hover:text-blue-700 transition-colors border border-slate-200 rounded px-2 py-0.5"
|
||||
>
|
||||
{lang === 'ru' ? 'EN' : 'RU'}
|
||||
</button>
|
||||
<a href="#contact" className="px-4 py-2 bg-blue-700 hover:bg-blue-800 text-white text-sm font-semibold rounded transition-colors">
|
||||
{t('nav.contact')}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
{/* Mobile */}
|
||||
<div className="md:hidden flex items-center gap-3">
|
||||
<button onClick={toggle} className="text-sm font-semibold text-slate-500 border border-slate-200 rounded px-2 py-0.5">
|
||||
{lang === 'ru' ? 'EN' : 'RU'}
|
||||
</button>
|
||||
<button onClick={() => setMenuOpen(!menuOpen)} className="p-1 text-slate-700">
|
||||
{menuOpen ? <X size={22} /> : <Menu size={22} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu */}
|
||||
{menuOpen && (
|
||||
<div className="md:hidden bg-white border-t border-slate-100 px-4 py-4 flex flex-col gap-4">
|
||||
{links.map(l => (
|
||||
<a key={l.href} href={l.href} onClick={() => setMenuOpen(false)} className="text-sm font-medium text-slate-700 hover:text-blue-700">
|
||||
{l.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
18
src/components/useReveal.js
Normal file
18
src/components/useReveal.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export function useReveal() {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => { if (entry.isIntersecting) el.classList.add('visible') },
|
||||
{ threshold: 0.15 }
|
||||
)
|
||||
observer.observe(el)
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
return ref
|
||||
}
|
||||
37
src/contexts/LanguageContext.jsx
Normal file
37
src/contexts/LanguageContext.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { createContext, useContext, useState } from 'react'
|
||||
import { ru, en } from '../translations/index.js'
|
||||
|
||||
const translations = { ru, en }
|
||||
|
||||
const LanguageContext = createContext(null)
|
||||
|
||||
export function LanguageProvider({ children }) {
|
||||
const [lang, setLang] = useState(() => {
|
||||
return localStorage.getItem('lang') || 'ru'
|
||||
})
|
||||
|
||||
const toggle = () => {
|
||||
const next = lang === 'ru' ? 'en' : 'ru'
|
||||
setLang(next)
|
||||
localStorage.setItem('lang', next)
|
||||
}
|
||||
|
||||
const t = (key) => {
|
||||
const keys = key.split('.')
|
||||
let value = translations[lang]
|
||||
for (const k of keys) {
|
||||
value = value?.[k]
|
||||
}
|
||||
return value ?? key
|
||||
}
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={{ lang, toggle, t }}>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useLanguage() {
|
||||
return useContext(LanguageContext)
|
||||
}
|
||||
31
src/index.css
Normal file
31
src/index.css
Normal file
@@ -0,0 +1,31 @@
|
||||
@import '@fontsource/manrope/400.css';
|
||||
@import '@fontsource/manrope/500.css';
|
||||
@import '@fontsource/manrope/600.css';
|
||||
@import '@fontsource/manrope/700.css';
|
||||
@import '@fontsource/manrope/800.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
font-family: 'Manrope', sans-serif;
|
||||
@apply bg-white text-slate-900;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.section-reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||
}
|
||||
.section-reveal.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
10
src/main.jsx
Normal file
10
src/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
195
src/pages/Home.jsx
Normal file
195
src/pages/Home.jsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import React, { useState } from 'react'
|
||||
import { ChevronRight, Server, Shield, Headphones, Phone, Mail, MapPin, CheckCircle2 } from 'lucide-react'
|
||||
import { useLanguage } from '../contexts/LanguageContext.jsx'
|
||||
import { useReveal } from '../components/useReveal.js'
|
||||
|
||||
const serviceIcons = [Server, Shield, Headphones]
|
||||
|
||||
function ServiceCard({ icon: Icon, title, description, points }) {
|
||||
const ref = useReveal()
|
||||
return (
|
||||
<div ref={ref} className="section-reveal bg-white border border-slate-200 rounded-xl p-8 hover:shadow-lg hover:border-blue-200 transition-all duration-300 flex flex-col gap-4">
|
||||
<div className="w-12 h-12 bg-blue-50 rounded-lg flex items-center justify-center">
|
||||
<Icon size={24} className="text-blue-700" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-slate-900">{title}</h3>
|
||||
<p className="text-slate-600 text-sm leading-relaxed">{description}</p>
|
||||
<ul className="flex flex-col gap-2 mt-auto pt-4 border-t border-slate-100">
|
||||
{points.map((p, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-slate-700">
|
||||
<CheckCircle2 size={16} className="text-blue-600 flex-shrink-0" />
|
||||
{p}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const { t } = useLanguage()
|
||||
const [formState, setFormState] = useState({ name: '', company: '', message: '' })
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
|
||||
const aboutRef = useReveal()
|
||||
const contactRef = useReveal()
|
||||
|
||||
const services = t('services.items')
|
||||
const stats = [t('about.stat1'), t('about.stat2'), t('about.stat3')]
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
setSubmitted(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Hero */}
|
||||
<section className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-blue-950 to-slate-900 relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-blue-500 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-blue-700 rounded-full blur-3xl" />
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 py-32 text-center relative z-10">
|
||||
<div className="inline-flex items-center gap-2 bg-blue-500/10 border border-blue-500/20 rounded-full px-4 py-1.5 mb-8">
|
||||
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse" />
|
||||
<span className="text-blue-300 text-sm font-medium">IT-аутсорсинг · Безопасность · Поддержка</span>
|
||||
</div>
|
||||
<h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-white leading-tight mb-6">
|
||||
{t('hero.title')}
|
||||
</h1>
|
||||
<p className="text-lg sm:text-xl text-slate-300 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
{t('hero.subtitle')}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a href="#contact" className="px-8 py-4 bg-blue-600 hover:bg-blue-500 text-white font-semibold rounded-lg transition-all duration-300 flex items-center justify-center gap-2 group">
|
||||
{t('hero.cta')}
|
||||
<ChevronRight size={20} className="group-hover:translate-x-1 transition-transform" />
|
||||
</a>
|
||||
<a href="#services" className="px-8 py-4 border border-white/20 hover:border-white/40 text-white font-semibold rounded-lg transition-all duration-300">
|
||||
{t('hero.ctaSecondary')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Services */}
|
||||
<section id="services" className="py-24 bg-slate-50">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold text-slate-900 mb-4">{t('services.title')}</h2>
|
||||
<p className="text-slate-500 text-lg max-w-2xl mx-auto">{t('services.subtitle')}</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{services.map((svc, i) => (
|
||||
<ServiceCard key={i} icon={serviceIcons[i]} {...svc} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About */}
|
||||
<section id="about" className="py-24 bg-white">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div ref={aboutRef} className="section-reveal grid md:grid-cols-2 gap-16 items-center">
|
||||
<div>
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-6">{t('about.title')}</h2>
|
||||
<p className="text-slate-600 text-lg leading-relaxed mb-4">{t('about.text1')}</p>
|
||||
<p className="text-slate-600 leading-relaxed">{t('about.text2')}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{stats.map((s, i) => (
|
||||
<div key={i} className="bg-slate-50 rounded-xl p-6 text-center border border-slate-100">
|
||||
<div className="text-3xl font-bold text-blue-700 mb-1">{s.value}</div>
|
||||
<div className="text-sm text-slate-500">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact */}
|
||||
<section id="contact" className="py-24 bg-slate-900">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div ref={contactRef} className="section-reveal grid md:grid-cols-2 gap-16">
|
||||
<div>
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-white mb-4">{t('contact.title')}</h2>
|
||||
<p className="text-slate-400 text-lg mb-10">{t('contact.subtitle')}</p>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<Phone size={20} className="text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider mb-0.5">{t('contact.phone')}</div>
|
||||
<a href="tel:+74953637476" className="text-white font-medium hover:text-blue-400 transition-colors">+7 495 363-74-76</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<Mail size={20} className="text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider mb-0.5">{t('contact.email')}</div>
|
||||
<a href="mailto:info@sag24.ru" className="text-white font-medium hover:text-blue-400 transition-colors">info@sag24.ru</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<MapPin size={20} className="text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider mb-0.5">{t('contact.address')}</div>
|
||||
<span className="text-white font-medium">{t('contact.addressValue')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-8">
|
||||
{submitted ? (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4 py-8">
|
||||
<CheckCircle2 size={48} className="text-green-400" />
|
||||
<p className="text-white text-center text-lg">{t('contact.formSuccess')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder={t('contact.formName')}
|
||||
value={formState.name}
|
||||
onChange={e => setFormState({ ...formState, name: e.target.value })}
|
||||
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('contact.formCompany')}
|
||||
value={formState.company}
|
||||
onChange={e => setFormState({ ...formState, company: e.target.value })}
|
||||
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm"
|
||||
/>
|
||||
<textarea
|
||||
required
|
||||
rows={5}
|
||||
placeholder={t('contact.formMessage')}
|
||||
value={formState.message}
|
||||
onChange={e => setFormState({ ...formState, message: e.target.value })}
|
||||
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm resize-none"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-3 bg-blue-600 hover:bg-blue-500 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
{t('contact.formSubmit')}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
58
src/translations/en.js
Normal file
58
src/translations/en.js
Normal file
@@ -0,0 +1,58 @@
|
||||
export const en = {
|
||||
nav: {
|
||||
services: 'Services',
|
||||
about: 'About',
|
||||
contact: 'Contact',
|
||||
},
|
||||
hero: {
|
||||
title: 'IT Solutions for Your Business',
|
||||
subtitle: 'We take full responsibility for your IT infrastructure — from workstation support to data protection',
|
||||
cta: 'Discuss a Project',
|
||||
ctaSecondary: 'Learn More',
|
||||
},
|
||||
services: {
|
||||
title: 'Our Services',
|
||||
subtitle: 'End-to-end IT infrastructure management',
|
||||
items: [
|
||||
{
|
||||
title: 'IT Outsourcing',
|
||||
description: 'Full management of your IT infrastructure: servers, networks, workstations. Fast response, results-driven.',
|
||||
points: ['24/7 support', 'Remote and on-site service', 'Fixed monthly cost'],
|
||||
},
|
||||
{
|
||||
title: 'Cybersecurity',
|
||||
description: 'Corporate data protection, security audits, firewall and antivirus configuration.',
|
||||
points: ['Vulnerability audit & analysis', 'Firewall & VPN setup', 'Data loss prevention'],
|
||||
},
|
||||
{
|
||||
title: 'Technical Support',
|
||||
description: 'Fast resolution of employee technical issues. Help desk, ticket management, user training.',
|
||||
points: ['Help desk & ticketing', 'User training', 'SLA from 15 minutes'],
|
||||
},
|
||||
],
|
||||
},
|
||||
about: {
|
||||
title: 'About Us',
|
||||
text1: 'SysadminGroup is an IT company from Pushkino with years of experience serving small and medium businesses. We handle all IT infrastructure so you can focus on your business.',
|
||||
text2: 'Our team consists of certified specialists in networking, information security, and system administration.',
|
||||
stat1: { value: '10+', label: 'years on market' },
|
||||
stat2: { value: '150+', label: 'clients' },
|
||||
stat3: { value: '15 min', label: 'response time' },
|
||||
},
|
||||
contact: {
|
||||
title: 'Contact Us',
|
||||
subtitle: 'Tell us about your challenge — we will propose a solution',
|
||||
phone: 'Phone',
|
||||
email: 'Email',
|
||||
address: 'Address',
|
||||
addressValue: 'Pushkino, Moscow Region',
|
||||
formName: 'Your name',
|
||||
formCompany: 'Company',
|
||||
formMessage: 'Describe your task',
|
||||
formSubmit: 'Send Request',
|
||||
formSuccess: 'Request sent! We will contact you shortly.',
|
||||
},
|
||||
footer: {
|
||||
rights: 'All rights reserved.',
|
||||
},
|
||||
}
|
||||
2
src/translations/index.js
Normal file
2
src/translations/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ru } from './ru.js'
|
||||
export { en } from './en.js'
|
||||
58
src/translations/ru.js
Normal file
58
src/translations/ru.js
Normal file
@@ -0,0 +1,58 @@
|
||||
export const ru = {
|
||||
nav: {
|
||||
services: 'Услуги',
|
||||
about: 'О нас',
|
||||
contact: 'Контакты',
|
||||
},
|
||||
hero: {
|
||||
title: 'IT-решения для вашего бизнеса',
|
||||
subtitle: 'Берём на себя всю IT-инфраструктуру — от поддержки рабочих мест до защиты данных',
|
||||
cta: 'Обсудить проект',
|
||||
ctaSecondary: 'Узнать больше',
|
||||
},
|
||||
services: {
|
||||
title: 'Наши услуги',
|
||||
subtitle: 'Комплексное обслуживание IT-инфраструктуры под ключ',
|
||||
items: [
|
||||
{
|
||||
title: 'IT-аутсорсинг',
|
||||
description: 'Полное обслуживание вашей IT-инфраструктуры: серверы, сети, рабочие станции. Реагируем быстро, работаем на результат.',
|
||||
points: ['Поддержка 24/7', 'Удалённое и выездное обслуживание', 'Фиксированная стоимость'],
|
||||
},
|
||||
{
|
||||
title: 'Кибербезопасность',
|
||||
description: 'Защита корпоративных данных, аудит безопасности, настройка межсетевых экранов и антивирусной защиты.',
|
||||
points: ['Аудит и анализ уязвимостей', 'Настройка Firewall и VPN', 'Защита от утечек данных'],
|
||||
},
|
||||
{
|
||||
title: 'Техническая поддержка',
|
||||
description: 'Оперативное решение технических проблем сотрудников. Help desk, управление заявками, обучение пользователей.',
|
||||
points: ['Help desk и тикет-система', 'Обучение пользователей', 'SLA от 15 минут'],
|
||||
},
|
||||
],
|
||||
},
|
||||
about: {
|
||||
title: 'О компании',
|
||||
text1: 'Сисадмингрупп — IT-компания из Пушкино с многолетним опытом обслуживания малого и среднего бизнеса. Мы берём на себя всю IT-инфраструктуру, чтобы вы могли сосредоточиться на бизнесе.',
|
||||
text2: 'Наша команда — сертифицированные специалисты с опытом в сетевых технологиях, информационной безопасности и системном администрировании.',
|
||||
stat1: { value: '10+', label: 'лет на рынке' },
|
||||
stat2: { value: '150+', label: 'клиентов' },
|
||||
stat3: { value: '15 мин', label: 'время реакции' },
|
||||
},
|
||||
contact: {
|
||||
title: 'Свяжитесь с нами',
|
||||
subtitle: 'Расскажите о задаче — предложим решение',
|
||||
phone: 'Телефон',
|
||||
email: 'Email',
|
||||
address: 'Адрес',
|
||||
addressValue: 'Пушкино, Московская область',
|
||||
formName: 'Ваше имя',
|
||||
formCompany: 'Компания',
|
||||
formMessage: 'Опишите задачу',
|
||||
formSubmit: 'Отправить заявку',
|
||||
formSuccess: 'Заявка отправлена! Мы свяжемся с вами в ближайшее время.',
|
||||
},
|
||||
footer: {
|
||||
rights: 'Все права защищены.',
|
||||
},
|
||||
}
|
||||
22
tailwind.config.js
Normal file
22
tailwind.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,jsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Manrope', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
900: '#1e3a8a',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
9
vite.config.js
Normal file
9
vite.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user