fill most content + improve ux
@@ -1,9 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
|
||||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
|
||||||
<style>
|
|
||||||
path { fill: #000; }
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
path { fill: #FFF; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 749 B |
4
src/assets/icons/WideCrossIcon.svg
Normal 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
|
After Width: | Height: | Size: 190 KiB |
@@ -1,22 +1,28 @@
|
|||||||
---
|
---
|
||||||
|
import type { coachesSchema } from '@/content.config'
|
||||||
|
import { useTranslations } from '@/i18n/utils'
|
||||||
import { Image } from 'astro:assets'
|
import { Image } from 'astro:assets'
|
||||||
|
import type { z } from 'astro:content'
|
||||||
|
|
||||||
type Props = {
|
type Props = z.infer<ReturnType<typeof coachesSchema>> & { lang?: string }
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
image: ImageMetadata
|
|
||||||
imageAlt: 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="flex h-full w-full flex-col items-center justify-start gap-5">
|
||||||
<div class="relative h-[390px] w-[300px]">
|
<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} />
|
<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">
|
<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>
|
<h3 class="text-xl font-bold uppercase text-text-light">{name}</h3>
|
||||||
{description && <p>{description}</p>}
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
29
src/components/Footer.astro
Normal 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
@@ -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>
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
---
|
---
|
||||||
|
import { Image } from 'astro:assets'
|
||||||
|
import wideCrossIconSrc from '@/assets/icons/WideCrossIcon.svg'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
texts: string[]
|
texts: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { texts } = Astro.props
|
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">
|
<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>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const { title, charachteristics, highlighted = false, link } = Astro.props
|
|||||||
charachteristics?.map((charachteristic) => (
|
charachteristics?.map((charachteristic) => (
|
||||||
<li class="flex items-start space-x-2 justify-between">
|
<li class="flex items-start space-x-2 justify-between">
|
||||||
<span class="text-text-light">{charachteristic.text}</span>
|
<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>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/components/header/Header.astro
Normal 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>
|
||||||
@@ -1,28 +1,46 @@
|
|||||||
import { defineCollection, z, type ImageFunction } from 'astro:content'
|
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) =>
|
export const coachesSchema = (image: ImageFunction) =>
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
image: image(),
|
image: image(),
|
||||||
imageAlt: z.string(),
|
imageAlt: z.string(),
|
||||||
|
link: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const servicesSchema = z.object({
|
export const servicesSchema = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string().optional(),
|
||||||
price: z.string(),
|
price: z.string().optional(),
|
||||||
link: z.object({
|
link: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
href: z.string(),
|
href: z.string(),
|
||||||
target: z.string().optional(),
|
target: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
charachteristics: z.array(
|
charachteristics: z
|
||||||
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
text: z.string(),
|
text: z.string(),
|
||||||
price: z.string().optional(),
|
price: z.string().optional(),
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const reviewsSchema = (image: ImageFunction) =>
|
export const reviewsSchema = (image: ImageFunction) =>
|
||||||
@@ -40,21 +58,7 @@ export const reviewsSchema = (image: ImageFunction) =>
|
|||||||
const pages = defineCollection({
|
const pages = defineCollection({
|
||||||
schema: ({ image }) =>
|
schema: ({ image }) =>
|
||||||
z.object({
|
z.object({
|
||||||
hero: z
|
hero: heroSchema(image).optional(),
|
||||||
.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(),
|
|
||||||
marquee: z.array(z.string()).optional(),
|
marquee: z.array(z.string()).optional(),
|
||||||
coaches: z.array(coachesSchema(image)).optional(),
|
coaches: z.array(coachesSchema(image)).optional(),
|
||||||
reviews: z.array(reviewsSchema(image)).optional(),
|
reviews: z.array(reviewsSchema(image)).optional(),
|
||||||
|
|||||||
BIN
src/content/pages/assets/coaches/1.jpeg
Normal file
|
After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 107 KiB |
BIN
src/content/pages/assets/coaches/2.jpeg
Normal file
|
After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 63 KiB |
BIN
src/content/pages/assets/slider/IMG_6491.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
src/content/pages/assets/slider/IMG_6492.jpg
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
src/content/pages/assets/slider/IMG_6493.jpg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
src/content/pages/assets/slider/IMG_6494.jpg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
src/content/pages/assets/slider/IMG_6495.jpg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
src/content/pages/assets/slider/IMG_6496.jpg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
src/content/pages/assets/slider/IMG_6498.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/content/pages/assets/slider/IMG_6499.jpg
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
src/content/pages/assets/slider/IMG_6500.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
src/content/pages/assets/slider/IMG_6506.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/content/pages/assets/slider/IMG_6507.jpg
Normal file
|
After Width: | Height: | Size: 887 KiB |
BIN
src/content/pages/assets/slider/IMG_6509.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/content/pages/assets/slider/IMG_6510.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/content/pages/assets/slider/IMG_6514.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/content/pages/assets/slider/IMG_6515.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/content/pages/assets/slider/IMG_6518.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/content/pages/assets/slider/IMG_6520.jpg
Normal file
|
After Width: | Height: | Size: 808 KiB |
@@ -8,67 +8,101 @@ hero:
|
|||||||
title: 'Записаться'
|
title: 'Записаться'
|
||||||
href: '#services'
|
href: '#services'
|
||||||
marquee:
|
marquee:
|
||||||
- ВОТ И ВСЕ ЕБАТЬ
|
- ДОСТИГАЙТЕ ЦЕЛЕЙ
|
||||||
- ТЫ СЛАБ
|
- БУДЬТЕ СИЛЬНЕЕ
|
||||||
|
- ТРЕНИРУЙТЕСЬ С НАМИ
|
||||||
|
- МЕНЯЙТЕСЬ К ЛУЧШЕМУ
|
||||||
|
- СТАНОВИТЕСЬ ЗДОРОВЕЕ
|
||||||
slider:
|
slider:
|
||||||
- image: './assets/slider/1.jpeg'
|
- image: './assets/slider/IMG_6491.jpg'
|
||||||
imageAlt: 'first-slide'
|
imageAlt: 'first-slide'
|
||||||
- image: './assets/slider/2.jpeg'
|
- image: './assets/slider/IMG_6492.jpg'
|
||||||
imageAlt: 'second-slide'
|
imageAlt: 'second-slide'
|
||||||
- image: './assets/slider/3.jpeg'
|
- image: './assets/slider/IMG_6493.jpg'
|
||||||
imageAlt: 'third-slide'
|
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:
|
coaches:
|
||||||
- image: './assets/coaches/1.jpg'
|
- image: './assets/coaches/1.jpeg'
|
||||||
imageAlt: 'first-coach'
|
imageAlt: 'first-coach'
|
||||||
name: 'Иванов Иван Иванович'
|
name: 'БАТЫР СУЛЕЙМАНОВ'
|
||||||
description: 'Тренер'
|
description: 'Профессиональный выступающий спортсмен и тренер. Опыт работы в фитнес индустрии более 12 лет'
|
||||||
- image: './assets/coaches/2.jpg'
|
link: 'https://t.me/therealskill_studio/4'
|
||||||
|
- image: './assets/coaches/2.jpeg'
|
||||||
imageAlt: 'second-coach'
|
imageAlt: 'second-coach'
|
||||||
name: 'Петров Петр Петрович'
|
name: 'СУЛЕЙМАНОВА АЛЕКСАНДРА'
|
||||||
description: 'Тренер'
|
description: 'Спортсмен и тренер. Опыт работы в фитнес индустрии 9 лет'
|
||||||
- image: './assets/coaches/3.jpg'
|
link: 'https://t.me/therealskill_studio/7'
|
||||||
imageAlt: 'third-coach'
|
|
||||||
name: 'Сидоров Сидор Сидорович'
|
|
||||||
services:
|
services:
|
||||||
- title: 'Пробный день'
|
- title: 'Разовые Персональные тренировки с тренером'
|
||||||
description: 'Идеально подходит для знакомства с нашим залом и услугами'
|
|
||||||
price: '1500 ₽'
|
|
||||||
link:
|
link:
|
||||||
title: 'Начать'
|
title: 'Записаться'
|
||||||
href: '/checkout/day-pass'
|
href: '/checkout/day-pass'
|
||||||
charachteristics:
|
charachteristics:
|
||||||
- text: 'Доступ ко всему оборудованию'
|
- text: 'Разовая тренировка'
|
||||||
price: '500 ₽'
|
price: '5 000 ₽'
|
||||||
- text: 'Персональный шкафчик и душ'
|
- text: 'Разовая СПЛИТ (2 человека)'
|
||||||
price: '300 ₽'
|
price: '8 000 ₽'
|
||||||
- text: 'Базовая оценка физической подготовки'
|
- text: 'Блок из 5 тренировок'
|
||||||
price: '700 ₽'
|
price: '22 500 ₽'
|
||||||
- title: 'Месячный абонемент'
|
- text: 'Блок из 5 тренировок СПЛИТ'
|
||||||
description: 'Гибкая программа тренировок на месяц'
|
price: '38 500 ₽'
|
||||||
price: '5900 ₽'
|
- text: 'Блок из 10 тренировок'
|
||||||
|
price: '42 500 ₽'
|
||||||
|
- text: 'Блок из 10 тренировок СПЛИТ'
|
||||||
|
price: '75 000 ₽'
|
||||||
|
- text: 'Блок из 15 тренировок'
|
||||||
|
price: '60 000 ₽'
|
||||||
|
- text: 'Блок из 15 тренировок СПЛИТ'
|
||||||
|
price: '110 000 ₽'
|
||||||
|
- title: 'Безлимитные Абонементы на месяц'
|
||||||
|
description: '30 календарных дней занятий с тренером'
|
||||||
link:
|
link:
|
||||||
title: 'Оформить'
|
title: 'Записаться'
|
||||||
href: '/checkout/monthly'
|
href: '/checkout/monthly'
|
||||||
charachteristics:
|
charachteristics:
|
||||||
- text: 'Полный доступ к залу и оборудованию'
|
- text: 'Персональный безлимит'
|
||||||
price: '2500 ₽'
|
price: '50 000 ₽'
|
||||||
- text: '2 занятия с сертифицированным тренером'
|
- text: 'СПЛИТ безлимит'
|
||||||
price: '2000 ₽'
|
price: '80 000 ₽'
|
||||||
- text: 'Доступ ко всем групповым занятиям'
|
- text: 'Мини-группа (3-5 человек) безлимит'
|
||||||
price: '1400 ₽'
|
price: '30 000 ₽<br />c человека'
|
||||||
- title: 'Годовой абонемент'
|
- title: 'Аренда зала / Разовые посещения'
|
||||||
description: 'Лучшее предложение для постоянных клиентов'
|
description: 'Присутствие тренера БЕСПЛАТНО'
|
||||||
price: '49900 ₽'
|
|
||||||
link:
|
link:
|
||||||
title: 'Присоединиться'
|
title: 'Записаться'
|
||||||
href: '/checkout/annual'
|
href: '/checkout/annual'
|
||||||
charachteristics:
|
charachteristics:
|
||||||
- text: 'Доступ ко всем зонам и премиум-оборудованию'
|
- text: 'Разовое посещение Время 75 минут'
|
||||||
price: '20000 ₽'
|
price: '1500 ₽<br />c человека'
|
||||||
- text: 'Ежемесячные персональные тренировки'
|
- text: '«Приходящий тренер» Время 75 минут'
|
||||||
price: '18000 ₽'
|
price: '1500 ₽<br />c человека'
|
||||||
- text: 'Индивидуальная консультация по питанию'
|
|
||||||
price: '11900 ₽'
|
|
||||||
reviews:
|
reviews:
|
||||||
- review: 'Место силы. Комфортная студия. Для тренировок есть всё необходимое . Раздевалки уютные , так же всё продумано до мелочей . Можно после тренировки насладиться кофе . По месторасположению очень комфортно , для парковки места есть всегда. Уютно. Спортивно . Красиво . Стильно . Профессионально! От души рекомендую'
|
- review: 'Место силы. Комфортная студия. Для тренировок есть всё необходимое . Раздевалки уютные , так же всё продумано до мелочей . Можно после тренировки насладиться кофе . По месторасположению очень комфортно , для парковки места есть всегда. Уютно. Спортивно . Красиво . Стильно . Профессионально! От души рекомендую'
|
||||||
author:
|
author:
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ export const languages = {
|
|||||||
|
|
||||||
export const ui = {
|
export const ui = {
|
||||||
ru: {
|
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
|
||||||
'header.logo': 'THEREAL<span class="text-brand">SKILL</span>',
|
'header.logo': 'THEREAL<span class="text-brand">SKILL</span>',
|
||||||
'nav.coaches': 'Тренера',
|
'nav.coaches': 'Тренера',
|
||||||
@@ -20,7 +27,8 @@ export const ui = {
|
|||||||
'ticker.shapeBody': 'ТЫ СЛАБ',
|
'ticker.shapeBody': 'ТЫ СЛАБ',
|
||||||
// Coaches Section
|
// Coaches Section
|
||||||
'coaches.title': 'НАШИ <span class="text-brand">ТРЕНЕРА</span>',
|
'coaches.title': 'НАШИ <span class="text-brand">ТРЕНЕРА</span>',
|
||||||
'coaches.subtitle': 'Профессиональные тренеры с многолетним опытом',
|
'coaches.subtitle': 'Вашему вниманию! Представляем наш тренерский состав',
|
||||||
|
'coaches.link': 'Подробнее',
|
||||||
// Slider Section
|
// Slider Section
|
||||||
'slider.title':
|
'slider.title':
|
||||||
'ПОСМОТРИТЕ, ГДЕ МЫ <br /> <span class="text-brand">ВЫПОЛНЯЕМ УПРАЖНЕНИЯ</span>',
|
'ПОСМОТРИТЕ, ГДЕ МЫ <br /> <span class="text-brand">ВЫПОЛНЯЕМ УПРАЖНЕНИЯ</span>',
|
||||||
@@ -38,7 +46,14 @@ export const ui = {
|
|||||||
'footer.copyright': 'Все права защищены. Политика конфиденциальности.',
|
'footer.copyright': 'Все права защищены. Политика конфиденциальности.',
|
||||||
},
|
},
|
||||||
en: {
|
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
|
||||||
'header.logo': 'THEREAL<span class="text-brand">SKILL</span>',
|
'header.logo': 'THEREAL<span class="text-brand">SKILL</span>',
|
||||||
'nav.coaches': 'Coaches',
|
'nav.coaches': 'Coaches',
|
||||||
@@ -54,6 +69,7 @@ export const ui = {
|
|||||||
// Coaches Section
|
// Coaches Section
|
||||||
'coaches.title': 'OUR <span class="text-brand">COACHES</span>',
|
'coaches.title': 'OUR <span class="text-brand">COACHES</span>',
|
||||||
'coaches.subtitle': 'Professional trainers with years of experience',
|
'coaches.subtitle': 'Professional trainers with years of experience',
|
||||||
|
'coaches.link': 'More info',
|
||||||
// Slider Section
|
// Slider Section
|
||||||
'slider.title': 'TAKE A LOOK WHERE WE <br /> <span class="text-brand">EXERCISE</span>',
|
'slider.title': 'TAKE A LOOK WHERE WE <br /> <span class="text-brand">EXERCISE</span>',
|
||||||
'slider.subtitle': 'Modern equipment in a spacious gym',
|
'slider.subtitle': 'Modern equipment in a spacious gym',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import { useTranslations } from '@/i18n/utils'
|
||||||
import '@/styles/global.css'
|
import '@/styles/global.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -6,6 +7,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { title } = Astro.props
|
const { title } = Astro.props
|
||||||
|
const t = useTranslations(Astro.currentLocale)
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -13,9 +15,17 @@ const { title } = Astro.props
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<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} />
|
<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>
|
</head>
|
||||||
<body class="flex flex-col">
|
<body class="flex flex-col">
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
---
|
---
|
||||||
import MobileNavigation from '@/components/MobileNavigation.tsx'
|
import MobileNavigation from '@/components/header/MobileNavigation'
|
||||||
import { useTranslations } from '@/i18n/utils'
|
import { useTranslations } from '@/i18n/utils'
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro'
|
import BaseLayout from '../layouts/BaseLayout.astro'
|
||||||
import Section from '@/components/Section.astro'
|
import Section from '@/components/Section.astro'
|
||||||
import { Swiper } from '@/components/swiper'
|
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 type { CollectionEntry } from 'astro:content'
|
||||||
import { getEntry } from 'astro:content'
|
import { getEntry } from 'astro:content'
|
||||||
import CoachCard from '@/components/CoachCard.astro'
|
import CoachCard from '@/components/CoachCard.astro'
|
||||||
import ServiceCard from '@/components/ServiceCard.astro'
|
import ServiceCard from '@/components/ServiceCard.astro'
|
||||||
import { Image } from 'astro:assets'
|
|
||||||
import Marquee from '@/components/Marquee.astro'
|
import Marquee from '@/components/Marquee.astro'
|
||||||
import { Reviews } from '@/components/Reviews.tsx'
|
import { Reviews } from '@/components/Reviews.tsx'
|
||||||
|
import { Image } from 'astro:assets'
|
||||||
|
import Hero from '@/components/Hero.astro'
|
||||||
|
|
||||||
type Props = CollectionEntry<'pages'>['data']
|
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 { lang } = Astro.params
|
||||||
const page = await getEntry('pages', 'main')
|
const page = await getEntry('pages', 'main')
|
||||||
const { slider, coaches, services, hero, marquee, reviews } = page?.data || {}
|
const { slider, coaches, services, hero, marquee, reviews } = page?.data || {}
|
||||||
@@ -30,44 +25,9 @@ const t = useTranslations(lang)
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={t('home.title')}>
|
<BaseLayout title={t('home.title')}>
|
||||||
<header
|
<Header lang={lang} />
|
||||||
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>
|
|
||||||
|
|
||||||
{
|
{hero && <Hero {...hero} />}
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
marquee && (
|
marquee && (
|
||||||
@@ -80,9 +40,9 @@ const t = useTranslations(lang)
|
|||||||
{
|
{
|
||||||
coaches && (
|
coaches && (
|
||||||
<Section id="coaches" title={t('coaches.title')} description={t('coaches.subtitle')}>
|
<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) => (
|
{coaches.map((coach) => (
|
||||||
<CoachCard {...coach} />
|
<CoachCard {...coach} lang={lang} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
@@ -97,11 +57,13 @@ const t = useTranslations(lang)
|
|||||||
description={t('slider.subtitle')}
|
description={t('slider.subtitle')}
|
||||||
className="bg-bg-light"
|
className="bg-bg-light"
|
||||||
>
|
>
|
||||||
|
<div class="-ml-6 flex w-[calc(100%+(1.5rem*2))] md:ml-0 md:w-full">
|
||||||
<Swiper
|
<Swiper
|
||||||
className="h-[540px]"
|
className="h-[540px]"
|
||||||
client:visible
|
client:visible
|
||||||
images={slider.map(({ image, imageAlt }) => ({ ...image, alt: imageAlt }))}
|
images={slider.map(({ image, imageAlt }) => ({ ...image, alt: imageAlt }))}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -128,22 +90,5 @@ const t = useTranslations(lang)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<Section className="px-0 md:px-0" id="contacts">
|
<Footer lang={lang} />
|
||||||
<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>
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
html {
|
html {
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, sans-serif;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
@apply scroll-pt-[var(--header-height)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
@@ -34,9 +33,9 @@
|
|||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.brand-btn {
|
.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 {
|
.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||