images optimizations

This commit is contained in:
Salam-vsem
2025-05-14 01:15:30 +03:00
parent 5d9b7c27ae
commit 2f26ad5464
14 changed files with 130 additions and 63 deletions

View File

@@ -12,14 +12,23 @@ const t = useTranslations(lang)
<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} />
<Image
src={image}
class="absolute h-full w-full object-cover"
alt={imageAlt}
width={300}
height={390}
format="webp"
fit="cover"
loading="lazy"
/>
</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">
<a href={link} target="_blank" class="mt-2 text-brand hover:text-brand/80">
{t('coaches.link')}
</a>
)

View File

@@ -27,6 +27,11 @@ const { title, description, cta, image, imageAlt } = Astro.props
}
</div>
<div class="relative hidden md:block md:h-[450px] lg:w-1/2">
<Image src={image} alt={imageAlt} class="absolute h-full w-full object-contain" />
<Image
src={image}
alt={imageAlt}
class="absolute h-full w-full object-contain"
loading="eager"
/>
</div>
</Section>

View File

@@ -6,10 +6,15 @@ import 'swiper/css/scrollbar'
import { Mousewheel, Scrollbar } from 'swiper/modules'
import StarIconSrc from '@/assets/icons/StarIcon.svg'
import { useTranslations } from '@/i18n/utils'
import type { GetImageResult } from 'astro'
type Review = z.infer<ReturnType<typeof reviewsSchema>>
type Author = Review['author']
export type OptimizedReview = Omit<Review, 'author'> & {
author: Omit<Author, 'image' | 'imageAlt'> & { image?: ImportAttributes }
}
type ReviewsProps = {
reviews: Review[]
reviews: OptimizedReview[]
lang?: string
}
@@ -36,7 +41,7 @@ export const Reviews: React.FC<ReviewsProps> = ({ reviews, lang }) => {
},
}}
>
{reviews.map((review, index) => (
{reviews.map(({ review, author }, index) => (
<SwiperSlide key={index} className={swiperSlideClassName}>
<div className="flex h-full flex-col gap-6 bg-bg-light p-6 md:px-14 md:py-16">
<div className="flex gap-1">
@@ -51,20 +56,24 @@ export const Reviews: React.FC<ReviewsProps> = ({ reviews, lang }) => {
))}
</div>
<blockquote className="break-words text-lg font-semibold text-text-light">
{review.review}
{review}
</blockquote>
<div className="mt-auto flex items-center gap-4">
<img
{...review.author.image}
alt={review.author.imageAlt}
loading="lazy"
className="h-16 w-16 rounded-full object-cover"
/>
<div className="flex flex-col">
<span className="font-semibold text-text-light">{review.author.name}</span>
<span>{review.author.description}</span>
{author && (
<div className="mt-auto flex items-center gap-4">
{author.image && (
<img
{...author.image}
loading="lazy"
decoding="async"
className="h-16 w-16 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="font-semibold text-text-light">{author.name}</span>
<span>{author.description}</span>
</div>
</div>
</div>
)}
</div>
</SwiperSlide>
))}

View File

@@ -12,10 +12,7 @@ const { title, description, className, id } = Astro.props
---
<section
class={cn(
'flex flex-col gap-6 py-12 px-4 md:py-24 md:px-16 lg:px-24 md:gap-12',
className
)}
class={cn('flex flex-col gap-6 py-12 px-4 md:py-24 md:px-16 lg:px-24 md:gap-12', className)}
id={id}
>
{

View File

@@ -9,7 +9,7 @@ const { title, charachteristics, highlighted = false, link } = Astro.props
<div
class:list={[
'flex h-full w-full flex-col border-2 px-5 py-6 md:px-7 md:py-8 md:max-w-[400px] md:min-h-[550px]',
'flex h-full w-full flex-col border-2 px-5 py-6 md:min-h-[550px] md:max-w-[400px] md:px-7 md:py-8',
'border border-transparent bg-card',
{ 'border-brand': highlighted },
]}
@@ -21,9 +21,11 @@ const { title, charachteristics, highlighted = false, link } = Astro.props
<ul class="mb-16 flex-grow space-y-4">
{
charachteristics?.map((charachteristic) => (
<li class="flex items-start space-x-2 justify-between">
<li class="flex items-start justify-between space-x-2">
<span class="text-text-light">{charachteristic.text}</span>
{charachteristic.price && <span class="ml-2 text-brand text-nowrap text-end" set:html={charachteristic.price} />}
{charachteristic.price && (
<span class="ml-2 text-nowrap text-end text-brand" set:html={charachteristic.price} />
)}
</li>
))
}

View File

@@ -13,7 +13,7 @@ export const NavButton: React.FC<Props> = ({ iconSrc, ...buttonProps }) => {
<button
{...buttonProps}
className={cn(
'border-border-light hidden rounded-full border border-solid md:block',
'hidden rounded-full border border-solid border-border-light md:block',
buttonProps.disabled && 'border-none',
buttonProps.className
)}

View File

@@ -1,16 +1,15 @@
import React from 'react'
import { Swiper as SwiperBase, SwiperSlide } from 'swiper/react'
import { A11y, Navigation, Pagination } from 'swiper/modules'
import { A11y, Navigation, Pagination } from 'swiper/modules' // Added Lazy module
import { cn } from '@/libs/cn'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import './styles.css'
import { NextButton, PrevButton } from './NavButtons'
import type { GetImageResult } from 'astro'
type SwiperProps = {
images: GetImageResult[]
images: ImportAttributes[]
className?: string
}
@@ -22,16 +21,16 @@ export const Swiper: React.FC<SwiperProps> = ({ images, className }) => {
pagination={{
clickable: true,
}}
watchSlidesProgress={true}
className={cn('h-full w-full', className)}
>
{images.map((image, index) => (
<SwiperSlide key={index}>
<img
{...image.attributes}
src={image.src}
srcSet={image.srcSet.attribute}
loading="lazy"
{...image}
loading={index === 0 ? 'eager' : 'lazy'}
decoding="async"
fetchPriority={index === 0 ? 'high' : 'auto'}
className="absolute h-full w-full object-cover md:px-[calc(50px+2rem)]"
/>
</SwiperSlide>

View File

@@ -1 +1 @@
export * from './Swiper';
export * from './Swiper'

23
src/constants/images.ts Normal file
View File

@@ -0,0 +1,23 @@
export const TW_WIDTHS = {
/** added, for Image, bellow xs */
XXS: 320,
XS: 475,
SM: 640,
MD: 768,
LG: 1024,
XL: 1280,
_2XL: 1536,
} as const
export const TW_SCREENS = {
...TW_WIDTHS,
HEIGHTS: {
XXS: 180,
XS: 268,
SM: 360,
MD: 432,
LG: 576,
XL: 720,
_2XL: 864,
},
} as const;

View File

@@ -1,17 +1,17 @@
import { ui } from './ui';
import { ui } from './ui'
import { i18n } from 'astro:config/client';
import { i18n } from 'astro:config/client'
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
const [, lang] = url.pathname.split('/')
if (lang in ui) return lang as keyof typeof ui;
if (lang in ui) return lang as keyof typeof ui
return i18n!.defaultLocale;
return i18n!.defaultLocale
}
export function useTranslations(lang?: string) {
return function t(key: keyof (typeof ui)[keyof typeof ui]) {
return ui[lang as keyof typeof ui]?.[key] || ui[i18n!.defaultLocale as keyof typeof ui][key];
};
return ui[lang as keyof typeof ui]?.[key] || ui[i18n!.defaultLocale as keyof typeof ui][key]
}
}

7
src/libs/image.ts Normal file
View File

@@ -0,0 +1,7 @@
import type { GetImageResult } from 'astro'
export const imageResultToImageAttributes = (imageResult: GetImageResult): ImportAttributes => ({
src: imageResult.src,
srcSet: imageResult.srcSet?.attribute,
...imageResult.attributes,
})

View File

@@ -1,5 +1,4 @@
---
import MobileNavigation from '@/components/header/MobileNavigation'
import { useTranslations } from '@/i18n/utils'
import BaseLayout from '../layouts/BaseLayout.astro'
import Section from '@/components/Section.astro'
@@ -12,9 +11,11 @@ import { getEntry } from 'astro:content'
import CoachCard from '@/components/CoachCard.astro'
import ServiceCard from '@/components/ServiceCard.astro'
import Marquee from '@/components/Marquee.astro'
import { Reviews } from '@/components/Reviews.tsx'
import { Reviews, type OptimizedReview } from '@/components/Reviews.tsx'
import Hero from '@/components/Hero.astro'
import { getImage } from 'astro:assets'
import { TW_SCREENS, TW_WIDTHS } from '@/constants/images'
import { imageResultToImageAttributes } from '@/libs/image'
type Props = CollectionEntry<'pages'>['data']
@@ -27,13 +28,35 @@ const optimizedSlider = slider
getImage({
src: slide.image,
alt: slide.imageAlt,
widths: [600, 1200],
quality: 90,
widths: [TW_SCREENS.SM, TW_SCREENS.LG],
sizes: `(max-width: ${TW_WIDTHS.LG}px) ${TW_SCREENS.SM}px, ${TW_SCREENS.LG}px`,
quality: 80,
format: 'webp',
})
loading: 'eager',
}).then(imageResultToImageAttributes)
)
)
: undefined
const optimizedReviews = reviews
? await Promise.all(
reviews.map(async (review): Promise<OptimizedReview> => {
if (!review.author?.image)
return { ...review, author: { ...review.author, image: undefined } }
const optimizedImage = await getImage({
src: review.author?.image,
alt: review.author?.imageAlt,
width: 64,
height: 64,
format: 'webp',
loading: 'lazy',
}).then(imageResultToImageAttributes)
return { ...review, author: { ...review.author, image: optimizedImage } }
})
)
: undefined
const t = useTranslations(lang)
---
@@ -53,7 +76,7 @@ const t = useTranslations(lang)
{
coaches && (
<Section id="coaches" title={t('coaches.title')} description={t('coaches.subtitle')}>
<div class="grid place-items-center gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-3">
<div class="grid place-items-center gap-4 sm:grid-cols-2 md:gap-8 lg:grid-cols-3">
{coaches.map((coach) => (
<CoachCard {...coach} lang={lang} />
))}
@@ -90,10 +113,10 @@ const t = useTranslations(lang)
}
{
reviews && (
optimizedReviews && (
<Section id="reviews" title={t('reviews.title')}>
<div class="-mx-4 flex w-[calc(100%+2rem)] md:-mx-16 md:w-[calc(100%+8rem)] lg:-mx-24 lg:w-[calc(100%+12rem)]">
<Reviews client:visible reviews={reviews} lang={lang} />
<Reviews client:visible reviews={optimizedReviews} lang={lang} />
</div>
</Section>
)