fill most content + improve ux

This commit is contained in:
Salam-vsem
2025-05-13 17:00:04 +03:00
parent 0fe2520261
commit f59c0085a3
41 changed files with 307 additions and 169 deletions

View File

@@ -0,0 +1,4 @@
<svg width="50" height="16" viewBox="0 0 50 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="49.4934" height="4.23718" transform="matrix(0.975813 -0.218607 0.297462 0.954734 0 11.2129)" fill="#FBFBFB"/>
<rect width="49.4934" height="4.23718" transform="matrix(0.975813 0.218607 -0.297462 0.954734 1.52344 0.462891)" fill="#FBFBFB"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@@ -1,22 +1,28 @@
---
import type { coachesSchema } from '@/content.config'
import { useTranslations } from '@/i18n/utils'
import { Image } from 'astro:assets'
import type { z } from 'astro:content'
type Props = {
name: string
description?: string
image: ImageMetadata
imageAlt: string
}
type Props = z.infer<ReturnType<typeof coachesSchema>> & { lang?: string }
const { name, description, image, imageAlt } = Astro.props
const { lang, name, description, image, imageAlt, link } = Astro.props
const t = useTranslations(lang)
---
<div class="flex h-full w-full flex-col items-center justify-start gap-4">
<div class="relative h-[390px] w-[300px]">
<div class="flex h-full w-full flex-col items-center justify-start gap-5">
<a class="relative h-[390px] w-full max-w-[300px]" href={link} target="_blank">
<Image src={image} class="absolute h-full w-full object-cover" alt={imageAlt} />
</div>
</a>
<div class="inline-flex w-full max-w-[300px] flex-col gap-1">
<h3 class="text-xl font-bold uppercase text-text-light">{name}</h3>
{description && <p>{description}</p>}
{
link && (
<a href={link} target="_blank" class="text-brand hover:text-brand/80 mt-2">
{t('coaches.link')}
</a>
)
}
</div>
</div>

View File

@@ -0,0 +1,29 @@
---
import { useTranslations } from '@/i18n/utils'
import { Image } from 'astro:assets'
import logoSrc from '@/assets/logo.png'
type Props = {
lang?: string
}
const { lang } = Astro.props
const t = useTranslations(lang)
---
<footer id="contacts" class="flex flex-col items-center gap-6 text-center">
<script
is:inline
type="text/javascript"
charset="utf-8"
async
src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A069b17526dc83cbf87c488f13ae089eb3af0f8f74af57e1be82e183434693562&width=100%25&height=400&lang=ru_RU&scroll=true"
></script>
<div class="flex flex-col items-center gap-4 px-6 py-8 text-center">
<Image src={logoSrc} alt="logo" class="h-32 w-auto md:h-48" />
<p class="text-light text-sm">
© {new Date().getFullYear()}
{t('footer.copyright')}
</p>
</div>
</footer>

29
src/components/Hero.astro Normal file
View File

@@ -0,0 +1,29 @@
---
import type { z } from 'astro:content'
import Section from './Section.astro'
import { heroSchema } from '@/content.config'
import { Image } from 'astro:assets'
type Props = z.infer<ReturnType<typeof heroSchema>>
const { title, description, cta, image, imageAlt } = Astro.props
---
<Section id="hero" className="mt-16 p-0 md:mt-0 md:flex-row md:p-0">
<div class="mb-8 px-6 py-10 md:mb-0 md:w-1/2 md:p-24">
<h1 class="mb-4 text-4xl font-bold leading-tight md:text-6xl" set:html={title} />
<p class="text-brand-text mb-6">{description}</p>
{
cta && (
<div class="flex w-full space-x-4">
<a href="#services" class="brand-btn w-full md:w-auto">
{cta?.title}
</a>
</div>
)
}
</div>
<div class="relative hidden md:block md:w-1/2">
<Image src={image} alt={imageAlt} class="absolute h-full w-full object-cover" />
</div>
</Section>

View File

@@ -1,12 +1,23 @@
---
import { Image } from 'astro:assets'
import wideCrossIconSrc from '@/assets/icons/WideCrossIcon.svg'
type Props = {
texts: string[]
}
const { texts } = Astro.props
const repeatedTexts = texts.length < 10 ? Array.from({ length: 10 / texts.length }, () => texts).flat() : texts;
const repeatedTexts =
texts.length < 10 ? Array.from({ length: 10 / texts.length }, () => texts).flat() : texts
---
<div class="flex h-full w-max animate-marquee items-center gap-10 text-5xl font-bold uppercase">
{repeatedTexts.map((text) => <h2>{text}</h2>)}
{
repeatedTexts.map((text) => (
<>
<h2>{text}</h2>
<Image src={wideCrossIconSrc} alt="cross-icon" />
</>
))
}
</div>

View File

@@ -23,7 +23,7 @@ const { title, charachteristics, highlighted = false, link } = Astro.props
charachteristics?.map((charachteristic) => (
<li class="flex items-start space-x-2 justify-between">
<span class="text-text-light">{charachteristic.text}</span>
{charachteristic.price && <span class="ml-2 text-brand text-nowrap">{charachteristic.price}</span>}
{charachteristic.price && <span class="ml-2 text-brand text-nowrap text-end" set:html={charachteristic.price} />}
</li>
))
}

View File

@@ -0,0 +1,60 @@
---
import MobileNavigation from './MobileNavigation'
import { useTranslations } from '@/i18n/utils'
type Props = {
lang?: string
}
const { lang } = Astro.props
const navItems = [
{ key: 'nav.coaches', href: '#coaches' },
{ key: 'nav.slider', href: '#image-slider' },
{ key: 'nav.services', href: '#services' },
{ key: 'nav.reviews', href: '#reviews' },
{ key: 'nav.contact', href: '#contacts' },
] as const
const t = useTranslations(lang)
---
<header
class="fixed left-0 right-0 top-0 z-50 flex h-[var(--header-height)] items-center justify-between bg-bg-main px-6 transition-transform duration-300 md:px-10"
id="main-header"
>
<a class="text-2xl font-bold text-text-light" set:html={t('header.logo')} href="#hero" />
<nav class="hidden md:block">
<ul class="grid grid-flow-col gap-6">
{
navItems.map((item) => (
<li>
<a href={item.href}>{t(item.key)}</a>
</li>
))
}
</ul>
</nav>
<MobileNavigation navItems={navItems} client:visible />
</header>
<script>
let lastScrollY = window.scrollY
const header = document.getElementById('main-header')
window.addEventListener('scroll', () => {
if (!header) return
const currentScrollY = window.scrollY
if (Math.abs(currentScrollY - lastScrollY) < 10) return
if (currentScrollY > lastScrollY && currentScrollY > 100) {
header.style.transform = 'translateY(-100%)'
} else {
header.style.transform = 'translateY(0)'
}
lastScrollY = currentScrollY
})
</script>

View File

@@ -1,28 +1,46 @@
import { defineCollection, z, type ImageFunction } from 'astro:content'
export const heroSchema = (image: ImageFunction) =>
z.object({
title: z.string(),
description: z.string(),
image: image(),
imageAlt: z.string(),
cta: z
.object({
title: z.string(),
href: z.string(),
target: z.string().optional(),
})
.optional(),
})
export const coachesSchema = (image: ImageFunction) =>
z.object({
name: z.string(),
description: z.string().optional(),
image: image(),
imageAlt: z.string(),
link: z.string().optional(),
})
export const servicesSchema = z.object({
title: z.string(),
description: z.string(),
price: z.string(),
description: z.string().optional(),
price: z.string().optional(),
link: z.object({
title: z.string(),
href: z.string(),
target: z.string().optional(),
}),
charachteristics: z.array(
z.object({
text: z.string(),
price: z.string().optional(),
})
),
charachteristics: z
.array(
z.object({
text: z.string(),
price: z.string().optional(),
})
)
.optional(),
})
export const reviewsSchema = (image: ImageFunction) =>
@@ -40,21 +58,7 @@ export const reviewsSchema = (image: ImageFunction) =>
const pages = defineCollection({
schema: ({ image }) =>
z.object({
hero: z
.object({
title: z.string(),
description: z.string(),
image: image(),
imageAlt: z.string(),
cta: z
.object({
title: z.string(),
href: z.string(),
target: z.string().optional(),
})
.optional(),
})
.nullable(),
hero: heroSchema(image).optional(),
marquee: z.array(z.string()).optional(),
coaches: z.array(coachesSchema(image)).optional(),
reviews: z.array(reviewsSchema(image)).optional(),

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 KiB

View File

@@ -8,67 +8,101 @@ hero:
title: 'Записаться'
href: '#services'
marquee:
- ВОТ И ВСЕ ЕБАТЬ
- ТЫ СЛАБ
- ДОСТИГАЙТЕ ЦЕЛЕЙ
- БУДЬТЕ СИЛЬНЕЕ
- ТРЕНИРУЙТЕСЬ С НАМИ
- МЕНЯЙТЕСЬ К ЛУЧШЕМУ
- СТАНОВИТЕСЬ ЗДОРОВЕЕ
slider:
- image: './assets/slider/1.jpeg'
- image: './assets/slider/IMG_6491.jpg'
imageAlt: 'first-slide'
- image: './assets/slider/2.jpeg'
- image: './assets/slider/IMG_6492.jpg'
imageAlt: 'second-slide'
- image: './assets/slider/3.jpeg'
- image: './assets/slider/IMG_6493.jpg'
imageAlt: 'third-slide'
- image: './assets/slider/IMG_6494.jpg'
imageAlt: 'fourth-slide'
- image: './assets/slider/IMG_6495.jpg'
imageAlt: 'fifth-slide'
- image: './assets/slider/IMG_6496.jpg'
imageAlt: 'sixth-slide'
- image: './assets/slider/IMG_6498.jpg'
imageAlt: 'seventh-slide'
- image: './assets/slider/IMG_6499.jpg'
imageAlt: 'eighth-slide'
- image: './assets/slider/IMG_6500.jpg'
imageAlt: 'ninth-slide'
- image: './assets/slider/IMG_6506.jpg'
imageAlt: 'tenth-slide'
- image: './assets/slider/IMG_6507.jpg'
imageAlt: 'eleventh-slide'
- image: './assets/slider/IMG_6509.jpg'
imageAlt: 'twelfth-slide'
- image: './assets/slider/IMG_6510.jpg'
imageAlt: 'thirteenth-slide'
- image: './assets/slider/IMG_6514.jpg'
imageAlt: 'fourteenth-slide'
- image: './assets/slider/IMG_6515.jpg'
imageAlt: 'fifteenth-slide'
- image: './assets/slider/IMG_6518.jpg'
imageAlt: 'sixteenth-slide'
- image: './assets/slider/IMG_6520.jpg'
imageAlt: 'seventeenth-slide'
coaches:
- image: './assets/coaches/1.jpg'
- image: './assets/coaches/1.jpeg'
imageAlt: 'first-coach'
name: 'Иванов Иван Иванович'
description: 'Тренер'
- image: './assets/coaches/2.jpg'
name: 'БАТЫР СУЛЕЙМАНОВ'
description: 'Профессиональный выступающий спортсмен и тренер. Опыт работы в фитнес индустрии более 12 лет'
link: 'https://t.me/therealskill_studio/4'
- image: './assets/coaches/2.jpeg'
imageAlt: 'second-coach'
name: 'Петров Петр Петрович'
description: 'Тренер'
- image: './assets/coaches/3.jpg'
imageAlt: 'third-coach'
name: 'Сидоров Сидор Сидорович'
name: 'СУЛЕЙМАНОВА АЛЕКСАНДРА'
description: 'Спортсмен и тренер. Опыт работы в фитнес индустрии 9 лет'
link: 'https://t.me/therealskill_studio/7'
services:
- title: 'Пробный день'
description: 'Идеально подходит для знакомства с нашим залом и услугами'
price: '1500 ₽'
- title: 'Разовые Персональные тренировки с тренером'
link:
title: 'Начать'
title: 'Записаться'
href: '/checkout/day-pass'
charachteristics:
- text: 'Доступ ко всему оборудованию'
price: '500 ₽'
- text: 'Персональный шкафчик и душ'
price: '300 ₽'
- text: 'Базовая оценка физической подготовки'
price: '700 ₽'
- title: 'Месячный абонемент'
description: 'Гибкая программа тренировок на месяц'
price: '5900 ₽'
- text: 'Разовая тренировка'
price: '5 000 ₽'
- text: 'Разовая СПЛИТ (2 человека)'
price: '8 000 ₽'
- text: 'Блок из 5 тренировок'
price: '22 500 ₽'
- text: 'Блок из 5 тренировок СПЛИТ'
price: '38 500 ₽'
- text: 'Блок из 10 тренировок'
price: '42 500 ₽'
- text: 'Блок из 10 тренировок СПЛИТ'
price: '75 000 ₽'
- text: 'Блок из 15 тренировок'
price: '60 000 ₽'
- text: 'Блок из 15 тренировок СПЛИТ'
price: '110 000 ₽'
- title: 'Безлимитные Абонементы на месяц'
description: '30 календарных дней занятий с тренером'
link:
title: 'Оформить'
title: 'Записаться'
href: '/checkout/monthly'
charachteristics:
- text: 'Полный доступ к залу и оборудованию'
price: '2500 ₽'
- text: '2 занятия с сертифицированным тренером'
price: '2000 ₽'
- text: 'Доступ ко всем групповым занятиям'
price: '1400 ₽'
- title: 'Годовой абонемент'
description: 'Лучшее предложение для постоянных клиентов'
price: '49900 ₽'
- text: 'Персональный безлимит'
price: '50 000 ₽'
- text: 'СПЛИТ безлимит'
price: '80 000 ₽'
- text: 'Мини-группа (3-5 человек) безлимит'
price: '30 000 ₽<br />c человека'
- title: 'Аренда зала / Разовые посещения'
description: 'Присутствие тренера БЕСПЛАТНО'
link:
title: 'Присоединиться'
title: 'Записаться'
href: '/checkout/annual'
charachteristics:
- text: 'Доступ ко всем зонам и премиум-оборудованию'
price: '20000 ₽'
- text: 'Ежемесячные персональные тренировки'
price: '18000 ₽'
- text: 'Индивидуальная консультация по питанию'
price: '11900 ₽'
- text: 'Разовое посещение Время 75 минут'
price: '1500 ₽<br />c человека'
- text: '«Приходящий тренер» Время 75 минут'
price: '1500 ₽<br />c человека'
reviews:
- review: 'Место силы. Комфортная студия. Для тренировок есть всё необходимое . Раздевалки уютные , так же всё продумано до мелочей . Можно после тренировки насладиться кофе . По месторасположению очень комфортно , для парковки места есть всегда. Уютно. Спортивно . Красиво . Стильно . Профессионально! От души рекомендую'
author:

View File

@@ -5,7 +5,14 @@ export const languages = {
export const ui = {
ru: {
'home.title': 'The real skill studio',
'seo.meta.description':
'The Real Skill Studio - профессиональная фитнес-студия в России. Персональные тренировки, групповые занятия, опытные тренеры.',
'seo.meta.keywords':
'фитнес, тренировки, спортзал, персональный тренер, групповые занятия, здоровье, спорт, Real Skill Studio"',
'seo.og.title': 'THEREALSKILL',
'seo.og.description':
'The Real Skill Studio - профессиональная фитнес-студия в России. Персональные тренировки, групповые занятия, опытные тренеры.',
'home.title': 'Фитнес-студия',
// Header
'header.logo': 'THEREAL<span class="text-brand">SKILL</span>',
'nav.coaches': 'Тренера',
@@ -20,7 +27,8 @@ export const ui = {
'ticker.shapeBody': 'ТЫ СЛАБ',
// Coaches Section
'coaches.title': 'НАШИ <span class="text-brand">ТРЕНЕРА</span>',
'coaches.subtitle': 'Профессиональные тренеры с многолетним опытом',
'coaches.subtitle': 'Вашему вниманию! Представляем наш тренерский состав',
'coaches.link': 'Подробнее',
// Slider Section
'slider.title':
'ПОСМОТРИТЕ, ГДЕ МЫ <br /> <span class="text-brand">ВЫПОЛНЯЕМ УПРАЖНЕНИЯ</span>',
@@ -38,7 +46,14 @@ export const ui = {
'footer.copyright': 'Все права защищены. Политика конфиденциальности.',
},
en: {
'home.title': 'Home Page',
'seo.meta.description':
'The Real Skill Studio - professional fitness studio in Russia. Personal training, group classes, experienced trainers.',
'seo.meta.keywords':
'fitness, training, gym, personal trainer, group classes, health, sports, Real Skill Studio"',
'seo.og.title': 'THEREALSKILL',
'seo.og.description':
'The Real Skill Studio - professional fitness studio in Russia. Personal training, group classes, experienced trainers.',
'home.title': 'Fitness Studio',
// Header
'header.logo': 'THEREAL<span class="text-brand">SKILL</span>',
'nav.coaches': 'Coaches',
@@ -54,6 +69,7 @@ export const ui = {
// Coaches Section
'coaches.title': 'OUR <span class="text-brand">COACHES</span>',
'coaches.subtitle': 'Professional trainers with years of experience',
'coaches.link': 'More info',
// Slider Section
'slider.title': 'TAKE A LOOK WHERE WE <br /> <span class="text-brand">EXERCISE</span>',
'slider.subtitle': 'Modern equipment in a spacious gym',

View File

@@ -1,4 +1,5 @@
---
import { useTranslations } from '@/i18n/utils'
import '@/styles/global.css'
type Props = {
@@ -6,6 +7,7 @@ type Props = {
}
const { title } = Astro.props
const t = useTranslations(Astro.currentLocale)
---
<!doctype html>
@@ -13,9 +15,17 @@ const { title } = Astro.props
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> -->
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<meta name="description" content={t('seo.meta.description')} />
<meta name="keywords" content={t('seo.meta.keywords')} />
<meta property="og:title" content={`${title} | ${t('seo.og.title')}`} />
<meta property="og:description" content={t('seo.og.description')} />
<meta property="og:type" content="website" />
<meta property="og:locale" content={Astro.currentLocale} />
<meta name="robots" content="index, follow" />
<link rel="canonical" href={Astro.url} />
<title>{`${title} | ${t('seo.og.title')}`}</title>
</head>
<body class="flex flex-col">
<slot />

View File

@@ -1,28 +1,23 @@
---
import MobileNavigation from '@/components/MobileNavigation.tsx'
import MobileNavigation from '@/components/header/MobileNavigation'
import { useTranslations } from '@/i18n/utils'
import BaseLayout from '../layouts/BaseLayout.astro'
import Section from '@/components/Section.astro'
import { Swiper } from '@/components/swiper'
import Header from '@/components/header/Header.astro'
import Footer from '@/components/Footer.astro'
import type { CollectionEntry } from 'astro:content'
import { getEntry } from 'astro:content'
import CoachCard from '@/components/CoachCard.astro'
import ServiceCard from '@/components/ServiceCard.astro'
import { Image } from 'astro:assets'
import Marquee from '@/components/Marquee.astro'
import { Reviews } from '@/components/Reviews.tsx'
import { Image } from 'astro:assets'
import Hero from '@/components/Hero.astro'
type Props = CollectionEntry<'pages'>['data']
const navItems = [
{ key: 'nav.coaches', href: '#coaches' },
{ key: 'nav.slider', href: '#image-slider' },
{ key: 'nav.services', href: '#services' },
{ key: 'nav.reviews', href: '#reviews' },
{ key: 'nav.contact', href: '#contacts' },
] as const
const { lang } = Astro.params
const page = await getEntry('pages', 'main')
const { slider, coaches, services, hero, marquee, reviews } = page?.data || {}
@@ -30,44 +25,9 @@ const t = useTranslations(lang)
---
<BaseLayout title={t('home.title')}>
<header
class="fixed left-0 right-0 top-0 z-50 flex h-[var(--header-height)] items-center justify-between bg-bg-main px-6 md:px-10"
>
<a class="text-2xl font-bold text-text-light" set:html={t('header.logo')} href="#hero" />
<nav class="hidden md:block">
<ul class="grid grid-flow-col gap-6">
{
navItems.map((item) => (
<li>
<a href={item.href}>{t(item.key)}</a>
</li>
))
}
</ul>
</nav>
<MobileNavigation navItems={navItems} client:visible />
</header>
<Header lang={lang} />
{
hero && (
<Section id="hero" className="md:flex-row mt-16 md:mt-0 p-0 md:p-0">
<div class="mb-8 px-6 py-10 md:mb-0 md:w-1/2 md:p-24">
<h1 class="mb-4 text-4xl font-bold leading-tight md:text-6xl" set:html={hero.title} />
<p class="text-brand-text mb-6">{hero.description}</p>
{hero.cta && (
<div class="flex space-x-4">
<a href="#services" class="brand-btn">
{hero.cta?.title}
</a>
</div>
)}
</div>
<div class="relative hidden md:block md:w-1/2">
<Image src={hero.image} alt={hero.imageAlt} class="absolute h-full w-full object-cover" />
</div>
</Section>
)
}
{hero && <Hero {...hero} />}
{
marquee && (
@@ -80,9 +40,9 @@ const t = useTranslations(lang)
{
coaches && (
<Section id="coaches" title={t('coaches.title')} description={t('coaches.subtitle')}>
<div class="grid w-full grid-flow-col place-items-center gap-6 overflow-x-scroll md:gap-14">
<div class="grid place-items-center gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-3">
{coaches.map((coach) => (
<CoachCard {...coach} />
<CoachCard {...coach} lang={lang} />
))}
</div>
</Section>
@@ -97,11 +57,13 @@ const t = useTranslations(lang)
description={t('slider.subtitle')}
className="bg-bg-light"
>
<Swiper
className="h-[540px]"
client:visible
images={slider.map(({ image, imageAlt }) => ({ ...image, alt: imageAlt }))}
/>
<div class="-ml-6 flex w-[calc(100%+(1.5rem*2))] md:ml-0 md:w-full">
<Swiper
className="h-[540px]"
client:visible
images={slider.map(({ image, imageAlt }) => ({ ...image, alt: imageAlt }))}
/>
</div>
</Section>
)
}
@@ -128,22 +90,5 @@ const t = useTranslations(lang)
)
}
<Section className="px-0 md:px-0" id="contacts">
<script
type="text/javascript"
charset="utf-8"
async
src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A069b17526dc83cbf87c488f13ae089eb3af0f8f74af57e1be82e183434693562&width=100%25&height=400&lang=ru_RU&scroll=true"
></script>
</Section>
<footer class="py-8 text-center">
<div class="container mx-auto px-6 md:px-10">
<div class="mb-4 text-2xl font-bold text-text-light" set:html={t('footer.logo')} />
<p class="text-light text-sm">
© {new Date().getFullYear()}
{t('footer.copyright')}
</p>
</div>
</footer>
<Footer lang={lang} />
</BaseLayout>

View File

@@ -10,7 +10,6 @@
html {
font-family: system-ui, sans-serif;
scroll-behavior: smooth;
@apply scroll-pt-[var(--header-height)];
}
html,
@@ -34,9 +33,9 @@
@layer components {
.brand-btn {
@apply rounded-full bg-brand px-4 py-2 text-center text-text-light transition-colors duration-300 hover:bg-brand/80;
@apply rounded-full bg-brand px-6 py-3 text-center text-text-light transition-colors duration-300 hover:bg-brand/80;
}
.brand-btn-outline {
@apply rounded-full border border-brand bg-transparent px-4 py-2 text-center text-text-light transition-colors duration-300 hover:bg-brand;
@apply rounded-full border border-brand bg-transparent px-6 py-3 text-center text-text-light transition-colors duration-300 hover:bg-brand;
}
}