supprime i18n, starlight, blog

This commit is contained in:
Jalil Arfaoui 2025-01-04 23:18:06 +01:00
parent 536d7de2ba
commit d41a01a4a1
13 changed files with 0 additions and 2525 deletions

View file

@ -1,158 +0,0 @@
---
// Import necessary components and utilities
import MainLayout from "@/layouts/MainLayout.astro";
import AvatarBlogLarge from "@components/ui/avatars/AvatarBlogLarge.astro";
import CardRelated from "@components/ui/cards/CardRelated.astro";
import Bookmark from "@components/ui/buttons/Bookmark.astro";
import SocialShare from "@components/ui/buttons/SocialShare.astro";
import PostFeedback from "@components/ui/feedback/PostFeedback.astro";
import { Image } from "astro:assets";
import { capitalize, formatDate } from "@utils/utils";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// Update getStaticPaths for English posts
export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) =>
id.startsWith("en/")
);
return blogPosts.map((post) => {
const idWithoutLang = post.id.replace(/^en\//, ""); // Remove the "en/" prefix
return {
params: { id: idWithoutLang },
props: { post },
};
});
}
// Get the current post's data
const { post } = Astro.props;
const blogPosts: CollectionEntry<"blog">[] = await getCollection(
"blog",
({ id }) => id.startsWith("en/")
);
// Filter out the current post to get related posts
// Note: This is a very basic way of choosing related posts, just for the purpose of the example.
// In a production site, you might want to implement a more robust algorithm, choosing related posts based on tags, categories, dates, authors, or keywords.
// See example: https://blog.codybrunner.com/2024/adding-related-articles-with-astro-content-collections/
const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
(blogEntry) => blogEntry.id !== post.id
);
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<section class="mx-auto max-w-3xl px-4 pb-12 pt-6 sm:px-6 lg:px-8 lg:pt-10">
<div class="max-w-2xl">
<div class="mb-6 flex items-center justify-between">
<div class="flex w-full gap-x-5 sm:items-center sm:gap-x-3">
<AvatarBlogLarge blogEntry={post} />
<div class="grow">
<div class="flex items-center justify-between gap-x-2">
<div>
<!--Post metadata and author info-->
<span class="font-bold text-neutral-700 dark:text-neutral-300">
{post.data.author}
</span>
<ul class="text-xs text-neutral-500">
<li
class="relative inline-block pe-6 before:absolute before:end-2 before:top-1/2 before:size-1 before:-translate-y-1/2 before:rounded-full before:bg-neutral-300 last:pe-0 last-of-type:before:hidden dark:text-neutral-400 dark:before:bg-neutral-600"
>
{formatDate(post.data.pubDate)}
</li>
<li
class="relative inline-block pe-6 before:absolute before:end-2 before:top-1/2 before:size-1 before:-translate-y-1/2 before:rounded-full before:bg-neutral-300 last:pe-0 last-of-type:before:hidden dark:text-neutral-400 dark:before:bg-neutral-600"
>
{post.data.readTime} min read
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!--Blog post title-->
<h2
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
>
{post.data.title}
</h2>
<!--Blog post contents-->
<div class="mb-5 space-y-5 md:mb-8 md:space-y-8">
{
post.data.contents.map((content: string, index: any) =>
index === 1 ? (
<>
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
<Image
class="w-full rounded-xl object-cover"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</>
) : (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
)
)
}
</div>
<div
class="mx-auto grid max-w-screen-lg gap-y-5 sm:flex sm:items-center sm:justify-between sm:gap-y-0"
>
<!--Blog post tags-->
<div
class="flex flex-wrap gap-x-2 gap-y-1 sm:flex-nowrap sm:items-center sm:gap-y-0"
>
{
post.data.tags?.map((tag: string) => (
<span class="inline-flex items-center gap-x-1.5 rounded-lg bg-neutral-400/30 px-3 py-1.5 text-xs font-medium text-neutral-700 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring dark:bg-neutral-700/60 dark:text-neutral-300">
{capitalize(tag)}
</span>
))
}
</div>
<!--Bookmark and Share buttons-->
<div class="flex items-center justify-end gap-x-1.5">
<Bookmark />
<div
class="mx-3 block h-4 border-e border-neutral-400 dark:border-neutral-500"
>
</div>
<div class="inline-flex">
<SocialShare pageTitle={post.data.title} />
</div>
</div>
</div>
</div>
<PostFeedback
title="Was this post helpful?"
firstChoice="Yes"
secondChoice="No"
/>
</section>
<!--Related articles section-->
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
<div class="mb-10 max-w-2xl">
<h2
class="text-balance text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
Related articles
</h2>
</div>
<div class="grid grid-cols-2 gap-6">
{relatedPosts.map((entry) => <CardRelated blogEntry={entry} />)}
</div>
</section>
</MainLayout>

View file

@ -1,118 +0,0 @@
---
// Import necessary components, modules and types
import MainLayout from "@/layouts/MainLayout.astro";
import CardBlog from "@components/ui/cards/CardBlog.astro";
import CardBlogRecent from "@components/ui/cards/CardBlogRecent.astro";
import CardInsight from "@components/ui/cards/CardInsight.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// Get all blogs post in English and sort them based on publish date
const englishBlogEntries = await getCollection("blog", ({ id }) => {
return id.startsWith("en/");
});
const englishInsightsEntries = await getCollection("insights", ({ id }) => {
return id.startsWith("en/");
});
const blogPosts: CollectionEntry<"blog">[] = englishBlogEntries.sort(
(a: CollectionEntry<"blog">, b: CollectionEntry<"blog">) =>
b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
// Get all insights posts
const insightPosts: CollectionEntry<"insights">[] = englishInsightsEntries;
// Separate the most recent post from others
const mostRecentPost: CollectionEntry<"blog"> = blogPosts[0];
const otherPosts: CollectionEntry<"blog">[] = blogPosts.slice(1);
// Define variables for page content
const title: string = "Your Gateway to Construction Excellence";
const subTitle: string =
"Explore the latest news, tips, and insights from ScrewFast to enhance your construction projects. From product spotlights to project management strategies, our blog is your go-to resource for all things hardware and construction.";
const secondTitle: string = "Insights";
const secondSubTitle: string =
"Stay up-to-date with the latest trends and developments in the construction industry with insights from ScrewFast's team of industry experts. ";
const pageTitle: string = `Blog | ${SITE.title}`;
---
<MainLayout
title={pageTitle}
structuredData={{
"@context": "https://schema.org",
"@type": "WebPage",
"@id": "https://screwfast.uk/blog",
url: "https://screwfast.uk/blog",
name: "Blog | ScrewFast",
description:
"Stay up-to-date with the latest trends and developments in the construction industry with insights from ScrewFast's team of industry experts.",
isPartOf: {
"@type": "WebSite",
url: "https://screwfast.uk",
name: "ScrewFast",
description:
"ScrewFast offers top-tier hardware tools and expert construction services to meet all your project needs.",
},
inLanguage: "en-US",
}}
>
<section
class="mx-auto max-w-[85rem] space-y-8 px-4 pt-16 sm:px-6 lg:px-8 2xl:max-w-full"
>
<!--Page header-->
<div class="mx-auto max-w-3xl text-left sm:text-center">
<h1
class="block text-balance text-4xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:text-5xl lg:text-6xl"
>
{title}
</h1>
<p
class="mt-4 text-pretty text-lg text-neutral-600 dark:text-neutral-400"
>
{subTitle}
</p>
</div>
</section>
<section
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<!--Blog posts grid-->
<div class="grid gap-6 lg:grid-cols-2">
{otherPosts.map((blogEntry) => <CardBlog blogEntry={blogEntry} />)}
</div>
</section>
<!--Most recent blog post-->
<section
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<CardBlogRecent blogEntry={mostRecentPost} />
</section>
<!--Insights section-->
<section
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h2
class="text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
{secondTitle}
</h2>
<p class="mt-1 text-pretty text-neutral-600 dark:text-neutral-400">
{secondSubTitle}
</p>
</div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{
insightPosts.map((insightEntry) => (
<CardInsight insightEntry={insightEntry} />
))
}
</div>
</section>
</MainLayout>

View file

@ -1,163 +0,0 @@
---
// Import necessary components and utilities
import MainLayout from "@/layouts/MainLayout.astro";
import AvatarBlogLarge from "@components/ui/avatars/AvatarBlogLarge.astro";
import CardRelated from "@components/ui/cards/CardRelated.astro";
import Bookmark from "@components/ui/buttons/Bookmark.astro";
import SocialShare from "@components/ui/buttons/SocialShare.astro";
import PostFeedback from "@components/ui/feedback/PostFeedback.astro";
import { Image } from "astro:assets";
import { capitalize, formatDate } from "@utils/utils";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// Update getStaticPaths for French posts
export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) =>
id.startsWith("fr/")
);
return blogPosts.map((post) => {
const idWithoutLang = post.id.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", id: idWithoutLang },
props: { post },
};
});
}
const { post } = Astro.props;
// Fetch related posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection(
"blog",
({ id }) => id.startsWith("fr/")
);
const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
(blogEntry) => blogEntry.id !== post.id
);
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<section class="mx-auto max-w-3xl px-4 pb-12 pt-6 sm:px-6 lg:px-8 lg:pt-10">
<div class="max-w-2xl">
<div class="mb-6 flex items-center justify-between">
<div class="flex w-full gap-x-5 sm:items-center sm:gap-x-3">
<AvatarBlogLarge blogEntry={post} />
<div class="grow">
<div class="flex items-center justify-between gap-x-2">
<div>
<div
class="hs-tooltip inline-block [--placement:bottom] [--trigger:hover]"
>
<!--Post metadata and author info-->
<span
class="font-bold text-neutral-700 dark:text-neutral-300"
>
{post.data.author}
</span>
</div>
<ul class="text-xs text-neutral-500">
<li
class="relative inline-block pe-6 before:absolute before:end-2 before:top-1/2 before:size-1 before:-translate-y-1/2 before:rounded-full before:bg-neutral-300 last:pe-0 last-of-type:before:hidden dark:text-neutral-400 dark:before:bg-neutral-600"
>
{formatDate(post.data.pubDate)}
</li>
<li
class="relative inline-block pe-6 before:absolute before:end-2 before:top-1/2 before:size-1 before:-translate-y-1/2 before:rounded-full before:bg-neutral-300 last:pe-0 last-of-type:before:hidden dark:text-neutral-400 dark:before:bg-neutral-600"
>
{post.data.readTime} min read
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!--Blog post title-->
<h2
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
>
{post.data.title}
</h2>
<!--Blog post contents-->
<div class="mb-5 space-y-5 md:mb-8 md:space-y-8">
{
post.data.contents.map((content: string, index: any) =>
index === 1 ? (
<>
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
<Image
class="w-full rounded-xl object-cover"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</>
) : (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
)
)
}
</div>
<div
class="mx-auto grid max-w-screen-lg gap-y-5 sm:flex sm:items-center sm:justify-between sm:gap-y-0"
>
<!--Blog post tags-->
<div
class="flex flex-wrap gap-x-2 gap-y-1 sm:flex-nowrap sm:items-center sm:gap-y-0"
>
{
post.data.tags?.map((tag: string, index) => (
<span class="inline-flex items-center gap-x-1.5 rounded-lg bg-neutral-400/30 px-3 py-1.5 text-xs font-medium text-neutral-700 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring dark:bg-neutral-700/60 dark:text-neutral-300">
{capitalize(tag)}
</span>
))
}
</div>
<!--Bookmark and Share buttons-->
<div class="flex items-center justify-end gap-x-1.5">
<Bookmark />
<div
class="mx-3 block h-4 border-e border-neutral-400 dark:border-neutral-500"
>
</div>
<div class="inline-flex">
<SocialShare pageTitle={post.data.title} />
</div>
</div>
</div>
</div>
<PostFeedback
title="Cet article était-il utile?"
firstChoice="Oui"
secondChoice="Non"
/>
</section>
<!--Related articles section-->
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
<div class="mb-10 max-w-2xl">
<h2
class="text-balance text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
Articles connexes
</h2>
</div>
<div class="grid grid-cols-2 gap-6">
{
relatedPosts.map((entry) => (
<CardRelated blogEntry={entry} recentBlogLocale="fr" />
))
}
</div>
</section>
</MainLayout>

View file

@ -1,163 +0,0 @@
---
// Import necessary components and utilities
import MainLayout from "@/layouts/MainLayout.astro";
import AvatarBlogLarge from "@components/ui/avatars/AvatarBlogLarge.astro";
import CardRelated from "@components/ui/cards/CardRelated.astro";
import Bookmark from "@components/ui/buttons/Bookmark.astro";
import SocialShare from "@components/ui/buttons/SocialShare.astro";
import PostFeedback from "@components/ui/feedback/PostFeedback.astro";
import { Image } from "astro:assets";
import { capitalize, formatDate } from "@utils/utils";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// Update getStaticPaths for French posts
export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) =>
id.startsWith("fr/")
);
return blogPosts.map((post) => {
const idWithoutLang = post.id.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", id: idWithoutLang },
props: { post },
};
});
}
const { post } = Astro.props;
// Fetch related posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection(
"blog",
({ id }) => id.startsWith("fr/")
);
const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
(blogEntry) => blogEntry.id !== post.id
);
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<section class="mx-auto max-w-3xl px-4 pb-12 pt-6 sm:px-6 lg:px-8 lg:pt-10">
<div class="max-w-2xl">
<div class="mb-6 flex items-center justify-between">
<div class="flex w-full gap-x-5 sm:items-center sm:gap-x-3">
<AvatarBlogLarge blogEntry={post} />
<div class="grow">
<div class="flex items-center justify-between gap-x-2">
<div>
<div
class="hs-tooltip inline-block [--placement:bottom] [--trigger:hover]"
>
<!--Post metadata and author info-->
<span
class="font-bold text-neutral-700 dark:text-neutral-300"
>
{post.data.author}
</span>
</div>
<ul class="text-xs text-neutral-500">
<li
class="relative inline-block pe-6 before:absolute before:end-2 before:top-1/2 before:size-1 before:-translate-y-1/2 before:rounded-full before:bg-neutral-300 last:pe-0 last-of-type:before:hidden dark:text-neutral-400 dark:before:bg-neutral-600"
>
{formatDate(post.data.pubDate)}
</li>
<li
class="relative inline-block pe-6 before:absolute before:end-2 before:top-1/2 before:size-1 before:-translate-y-1/2 before:rounded-full before:bg-neutral-300 last:pe-0 last-of-type:before:hidden dark:text-neutral-400 dark:before:bg-neutral-600"
>
{post.data.readTime} min read
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!--Blog post title-->
<h2
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
>
{post.data.title}
</h2>
<!--Blog post contents-->
<div class="mb-5 space-y-5 md:mb-8 md:space-y-8">
{
post.data.contents.map((content: string, index: any) =>
index === 1 ? (
<>
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
<Image
class="w-full rounded-xl object-cover"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</>
) : (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
)
)
}
</div>
<div
class="mx-auto grid max-w-screen-lg gap-y-5 sm:flex sm:items-center sm:justify-between sm:gap-y-0"
>
<!--Blog post tags-->
<div
class="flex flex-wrap gap-x-2 gap-y-1 sm:flex-nowrap sm:items-center sm:gap-y-0"
>
{
post.data.tags?.map((tag: string, index) => (
<span class="inline-flex items-center gap-x-1.5 rounded-lg bg-neutral-400/30 px-3 py-1.5 text-xs font-medium text-neutral-700 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring dark:bg-neutral-700/60 dark:text-neutral-300">
{capitalize(tag)}
</span>
))
}
</div>
<!--Bookmark and Share buttons-->
<div class="flex items-center justify-end gap-x-1.5">
<Bookmark />
<div
class="mx-3 block h-4 border-e border-neutral-400 dark:border-neutral-500"
>
</div>
<div class="inline-flex">
<SocialShare pageTitle={post.data.title} />
</div>
</div>
</div>
</div>
<PostFeedback
title="Cet article était-il utile?"
firstChoice="Oui"
secondChoice="Non"
/>
</section>
<!--Related articles section-->
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
<div class="mb-10 max-w-2xl">
<h2
class="text-balance text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
Articles connexes
</h2>
</div>
<div class="grid grid-cols-2 gap-6">
{
relatedPosts.map((entry) => (
<CardRelated blogEntry={entry} recentBlogLocale="fr" />
))
}
</div>
</section>
</MainLayout>

View file

@ -1,116 +0,0 @@
---
// Import necessary components, modules and types
import MainLayout from "@/layouts/MainLayout.astro";
import CardBlog from "@components/ui/cards/CardBlog.astro";
import CardBlogRecent from "@components/ui/cards/CardBlogRecent.astro";
import CardInsight from "@components/ui/cards/CardInsight.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// Get all blogs post in French and sort them based on publish date
const frenchBlogEntries = await getCollection("blog", ({ id }) => {
return id.startsWith("fr/");
});
const frenchInsightsEntries = await getCollection("insights", ({ id }) => {
return id.startsWith("fr/");
});
const blogPosts: CollectionEntry<"blog">[] = frenchBlogEntries.sort(
(a: CollectionEntry<"blog">, b: CollectionEntry<"blog">) =>
b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
// Get all insights posts
const insightPosts: CollectionEntry<"insights">[] = frenchInsightsEntries;
// Separate the most recent post from others
const mostRecentPost: CollectionEntry<"blog"> = blogPosts[0];
const otherPosts: CollectionEntry<"blog">[] = blogPosts.slice(1);
// Define variables for page content
const title: string = "Votre Passerelle vers l'Excellence en Construction";
const subTitle: string =
"Explorez les dernières actualités, astuces et analyses de ScrewFast pour améliorer vos projets de construction. Des mises en avant de produits aux stratégies de gestion de projet, notre blog est votre ressource incontournable pour tout ce qui concerne les outils et la construction.";
const secondTitle: string = "Perspectives";
const secondSubTitle: string =
"Restez à jour avec les dernières tendances et évolutions de l'industrie de la construction grâce aux analyses de l'équipe d'experts de ScrewFast.";
const pageTitle: string = `Blog | ${SITE.title}`;
---
<MainLayout lang="fr"
title={pageTitle}
structuredData={{
"@context": "https://schema.org",
"@type": "WebPage",
"@id": "https://screwfast.uk/fr/blog",
"url": "https://screwfast.uk/fr/blog",
"name": "Blog | ScrewFast",
"description": "Restez informé des dernières tendances et évolutions dans le secteur de la construction avec les analyses de l'équipe d'experts de ScrewFast.",
"isPartOf": {
"@type": "WebSite",
"url": "https://screwfast.uk/fr",
"name": "ScrewFast",
"description": "ScrewFast propose des outils matériels de premier ordre et des services de construction experts pour répondre à tous vos besoins de projet."
},
"inLanguage": "fr"
}}
>
<section
class="mx-auto max-w-[85rem] space-y-8 px-4 pt-16 sm:px-6 lg:px-8 2xl:max-w-full"
>
<!--Page header-->
<div class="mx-auto max-w-3xl text-left sm:text-center">
<h1
class="block text-balance text-4xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:text-5xl lg:text-6xl"
>
{title}
</h1>
<p
class="mt-4 text-pretty text-lg text-neutral-600 dark:text-neutral-400"
>
{subTitle}
</p>
</div>
</section>
<section
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<!--Blog posts grid-->
<div class="grid gap-6 lg:grid-cols-2">
{otherPosts.map((blogEntry) => <CardBlog blogEntry={blogEntry} blogLocale="fr"/>)}
</div>
</section>
<!--Most recent blog post-->
<section
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<CardBlogRecent blogEntry={mostRecentPost} recentBlogLocale="fr" />
</section>
<!--Insights section-->
<section
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h2
class="text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
{secondTitle}
</h2>
<p class="mt-1 text-pretty text-neutral-600 dark:text-neutral-400">
{secondSubTitle}
</p>
</div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{
insightPosts.map((insightEntry) => (
<CardInsight insightEntry={insightEntry} insightLocale="fr" />
))
}
</div>
</section>
</MainLayout>

View file

@ -1,30 +0,0 @@
---
// Import the necessary components
import MainLayout from "@/layouts/MainLayout.astro";
import ContactSection from "@components/sections/fr/ContactSection_fr.astro";
---
<!--Utilizing MainLayout for the outer layout of the page, and defining meta for SEO purposes-->
<MainLayout
title="Contact | ScrewFast"
lang="fr"
structuredData={{
"@context": "https://schema.org",
"@type": "WebPage",
"@id": "https://screwfast.uk//fr/contact",
url: "https://screwfast.uk//fr/contact",
name: "Nous Contacter | ScrewFast",
description:
"Vous avez des questions ou souhaitez discuter d'un projet ? Contactez-nous et élaborons ensemble la solution parfaite avec nos outils et services.",
isPartOf: {
"@type": "WebSite",
url: "https://screwfast.uk/fr/",
name: "ScrewFast",
description:
"ScrewFast propose des outils matériels de premier ordre et des services de construction experts pour répondre à tous vos besoins de projet.",
},
inLanguage: "fr",
}}
>
<ContactSection />
</MainLayout>

View file

@ -1,144 +0,0 @@
---
// Import the necessary components
import MainLayout from "@/layouts/MainLayout.astro";
import HeroSection from "@components/sections/landing/HeroSection.astro";
import HeroSectionAlt from "@components/sections/landing/HeroSectionAlt.astro";
import ClientsSection from "@components/sections/landing/ClientsSection.astro";
import FeaturesGeneral from "@components/sections/features/FeaturesGeneral.astro";
import FeaturesNavs from "@components/sections/features/FeaturesNavs.astro";
import TestimonialsSection from "@components/sections/testimonials/TestimonialsSection.astro";
import PricingSection from "@components/sections/pricing/PricingSection.astro";
import FAQ from "@components/sections/misc/FAQ.astro";
import AnnouncementBanner from "@components/ui/banners/AnnouncementBanner.astro";
import heroImage from "@images/hero-image.avif";
import faqs from "@data/fr/faqs.json";
import features from "@data/fr/features.json";
import pricing from "@data/fr/pricing.json";
import featureImage from "@images/features-image.avif";
import construction from "@images/construction-image.avif";
import tools from "@images/automated-tools.avif";
import dashboard from "@images/dashboard-image.avif";
const avatarSrcs: Array<string> = [
"https://images.unsplash.com/photo-1568602471122-7832951cc4c5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=facearea&facepad=2&w=300&h=300&q=80",
"https://images.unsplash.com/photo-1531927557220-a9e23c1e4794?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=facearea&facepad=2&w=300&h=300&q=80",
"https://images.unsplash.com/photo-1541101767792-f9b2b1c4f127?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&&auto=format&fit=facearea&facepad=3&w=300&h=300&q=80",
"https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=facearea&facepad=2&w=300&h=300&q=80",
];
---
<MainLayout lang="fr">
<AnnouncementBanner
btnId="dismiss-button"
btnTitle="Découvrez ScrewFast sur GitHub"
url="https://github.com/mearashadowfax/ScrewFast"
/>
<HeroSection
title=`Équipez vos projets avec <span class="text-yellow-500 dark:text-yellow-400">ScrewFast</span>`
subTitle="Outils matériels de haute qualité et services de construction experts pour tous les besoins en projet."
primaryBtn="Commencez à explorer"
primaryBtnURL="/products"
secondaryBtn="Contacter l'équipe commerciale"
secondaryBtnURL="/contact"
withReview={true}
avatars={avatarSrcs}
rating=`<span class="font-bold">4.8</span> / 5`
starCount={4}
reviews=`À partir de plus de <span class="font-bold">12,8k</span> avis`
src={heroImage}
alt="Pile de boîtes de produits ScrewFast contenant des outils matériels assortis"
/>
<ClientsSection
title="Faites confiance aux leaders de l'industrie"
subTitle="Découvrez la fiabilité choisie par les géants de l'industrie."
/>
<FeaturesGeneral
title="Répondre aux exigences de l'industrie"
subTitle="Chez ScrewFast, nous relevons les défis uniques rencontrés dans les secteurs du matériel et de la construction. Des outils de pointe aux services experts, nous sommes déterminés à vous aider à surmonter les obstacles et à atteindre vos objectifs."
src={featureImage}
alt="Produits ScrewFast dans des boîtes flottantes"
features={features}
/>
<FeaturesNavs
title=`Personnalisez les offres de <span class="text-yellow-500 dark:text-yellow-400">ScrewFast</span> pour répondre parfaitement à vos besoins en matériel et en construction.`
tabs={[
{
heading: "Outils de pointe",
content:
"Optimisez vos projets avec les outils de pointe de ScrewFast. Faites l'expérience d'une efficacité accrue dans la gestion de la construction avec nos solutions automatisées sophistiquées.",
svg: "tools",
src: tools,
alt: "Équipement lourd jaune et noir sur un champ d'herbe brune",
first: true,
},
{
heading: "Tableaux de bord intuitifs",
content:
"Naviguez facilement avec les tableaux de bord intuitifs de ScrewFast. Configurez et supervisez vos projets de manière transparente, avec des interfaces conviviales conçues pour une gestion efficace des flux de travail rapide et efficace.",
svg: "dashboard",
src: dashboard,
alt: "Capture d'écran ou représentation graphique du tableau de bord intuitif",
second: true,
},
{
heading: "Fonctionnalités robustes",
content:
"Minimisez la complexité, maximisez la productivité. Les fonctionnalités robustes de ScrewFast sont conçues pour rationaliser votre processus de construction, offrant des résultats qui se distinguent par leur excellence.",
svg: "house",
src: construction,
alt: "Structure métallique grise d'un bâtiment près d'une grue à tour pendant la journée",
},
]}
/>
<TestimonialsSection
title="Accélérez vos projets"
subTitle="Chez ScrewFast, nous assurons un démarrage rapide avec une configuration de compte instantanée. Découvrez la vitesse de la construction redéfinie."
testimonials={[
{
content:
"ScrewFast a considérablement augmenté l'efficacité de notre projet. La configuration a été instantanée et leurs temps de réponse rapides sont phénoménaux. Vraiment un changement de jeu dans le support matériel et de construction !",
author: "Samantha Ruiz",
role: "Directrice des opérations | ConstructIt Inc.",
avatarSrc:
"https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?q=80&w=1453&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D8&auto=format&fit=facearea&facepad=2&w=320&h=320&q=80",
},
]}
statistics={[
{
count: "70k+",
description:
"clients équipés — des bricoleurs aux grandes entreprises de construction",
},
{
count: "35%",
description:
"hausse de l'efficacité des projets avec les outils et services de ScrewFast",
},
{
count: "15,3%",
description:
"réduction des coûts de maintenance rapportée par des clients à long terme",
},
{
count: "2x",
description:
"assemblage plus rapide grâce à des solutions de fixation innovantes",
},
]}
/>
<PricingSection pricing={pricing} />
<FAQ title="Questions<br />fréquemment posées" faqs={faqs} />
<HeroSectionAlt
title="Construisons ensemble"
subTitle="ScrewFast est un modèle open source, méticuleusement conçu avec les frameworks Astro, Tailwind CSS et Preline UI."
url="https://github.com/mearashadowfax/ScrewFast"
/>
</MainLayout>

View file

@ -1,266 +0,0 @@
---
// Import section components
import { SITE } from "@data/constants";
import MainLayout from "@/layouts/MainLayout.astro";
import { Image } from "astro:assets";
import { getCollection, render } from "astro:content";
// Use `getStaticPaths` to generate static routes for generated pages on build
export async function getStaticPaths() {
const insightPosts = await getCollection("insights", ({ id }) => id.startsWith("fr/"));
return insightPosts.map((post) => {
const idWithoutLang = post.id.replace(/^fr\//, ''); // Remove the "fr/" prefix
return {
params: { lang: 'fr', id: idWithoutLang },
props: { post },
};
});
}
// Get the props for this page that define a specific insight post
const { post } = Astro.props;
const { Content } = await render(post);
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<section class="py-6 sm:py-8 lg:py-12">
<div class="mx-auto max-w-screen-xl px-4 md:px-8">
<div class="grid gap-8 md:grid-cols-2 lg:gap-12">
<div>
<div class="h-64 overflow-hidden rounded-lg shadow-lg md:h-auto">
<Image
class="h-full w-full object-cover object-center"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>
<div
id="progress-mobile"
class="fixed left-0 top-0 h-2 w-full bg-gradient-to-r from-orange-400/30 to-orange-400 md:hidden"
>
</div>
<div id="pin" class="mt-10 hidden space-y-4 md:block">
<div
class="h-px w-full overflow-hidden bg-neutral-300 dark:bg-neutral-700"
>
<div
id="progress"
class="h-px w-full bg-gradient-to-r from-orange-400/30 to-orange-400"
>
</div>
</div>
<p class="text-pretty text-sm text-neutral-500">
Table of Contents:
</p>
<div id="toc" class="">
<ul
class="space-y-2 text-pretty text-base text-neutral-700 transition duration-300 dark:text-neutral-400"
>
</ul>
</div>
</div>
</div>
<div class="md:pt-8">
<h1
class="mb-4 text-balance text-center text-2xl font-bold text-neutral-800 dark:text-neutral-200 sm:text-3xl md:mb-6 md:text-left"
>
{post.data.title}
</h1>
<article
class="text-pretty text-lg text-neutral-700 dark:text-neutral-300"
>
<Content />
</article>
</div>
</div>
</div>
</section>
</MainLayout>
<style is:global>
:root {
--transition-cubic: cubic-bezier(0.165, 0.84, 0.44, 1);
}
html {
scroll-behavior: smooth;
}
article h2,
article h3,
article h4,
article h5,
article h6 {
font-weight: bold;
margin-top: 2.5rem;
scroll-margin-top: 3rem;
}
h2 {
font-size: 1.5rem;
line-height: 2rem;
}
h3 {
font-size: 1.25rem;
line-height: 1.75rem;
}
h4 {
font-size: 1.125rem;
line-height: 1.75rem;
}
p {
margin-top: 1.5rem;
}
#toc li {
display: flex;
align-items: center;
opacity: 0.8;
transition: all 300ms var(--transition-cubic);
}
#toc li.selected {
opacity: 1;
}
#toc li svg {
width: 0;
height: 0;
transition:
height 400ms var(--transition-cubic),
width 400ms var(--transition-cubic);
}
#toc li.selected svg {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.3rem;
}
</style>
<script>
const onScroll = (): void => {
const article = document.querySelector("article");
if (!article) return;
const articleHeight = article.offsetHeight;
const articleOffsetTop = article.offsetTop;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
if (articleHeight && articleOffsetTop && scrollTop) {
const progress =
((scrollTop - articleOffsetTop) /
(articleHeight - window.innerHeight)) *
100;
const progressBar = document.getElementById("progress");
const progressBarMobile = document.getElementById("progress-mobile");
if (progressBar && progressBarMobile) {
progressBar.style.width = `${progress}%`;
progressBarMobile.style.width = `${progress}%`;
}
}
};
document.addEventListener("DOMContentLoaded", (event) => {
window.onscroll = onScroll;
// Set initial width of progress bar
const progressBar = document.getElementById("progress");
const progressBarMobile = document.getElementById("progress-mobile");
if (progressBar && progressBarMobile) {
progressBar.style.width = "0%";
progressBarMobile.style.width = "0%";
}
});
</script>
<script>
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
gsap.timeline({
scrollTrigger: {
scrub: 1,
pin: true,
trigger: "#pin",
start: "top 20%",
endTrigger: "footer",
end: "top bottom",
},
});
const SVG_HTML_STRING =
'<svg class="w-0 h-0 flex-none" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15"><path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></svg>';
function setActiveLinkById(id: string | null) {
const listItems = document.querySelectorAll("#toc li");
listItems.forEach((item) => item.classList.remove("selected"));
if (!id) return;
const activeLink = document.querySelector(`#toc a[href="#${id}"]`);
if (!activeLink) return;
const listItem = activeLink.parentElement;
listItem?.classList.add("selected");
}
document.addEventListener("DOMContentLoaded", function () {
// The article element that contains the Markdown content
const article: HTMLElement | null = document.querySelector("article");
// The ToC container <ul> element
const tocList: HTMLElement | null = document.querySelector("#toc ul");
const headings: NodeListOf<HTMLElement> | [] = article
? article.querySelectorAll("h1, h2, h3, h4, h5, h6")
: [];
headings.forEach((heading, i) => {
if (heading instanceof HTMLElement) {
const listItem = document.createElement("li");
listItem.className = "toc-level-" + heading.tagName.toLowerCase();
const tempDiv = document.createElement("div");
tempDiv.innerHTML = SVG_HTML_STRING;
const svg = tempDiv.firstChild;
listItem.appendChild(svg as Node);
const link = document.createElement("a");
link.href = "#" + heading.id;
link.textContent = heading.textContent;
listItem.appendChild(link);
tocList?.appendChild(listItem);
gsap.timeline({
scrollTrigger: {
trigger: heading,
start: "top 20%",
end: () =>
`bottom top+=${i === headings.length - 1 ? 0 : (headings[i + 1] as HTMLElement).getBoundingClientRect().height}`,
onEnter: () => setActiveLinkById(heading.id),
onLeaveBack: () =>
setActiveLinkById((headings[i - 1] as HTMLElement)?.id),
},
});
}
});
});
</script>

View file

@ -1,266 +0,0 @@
---
// Import section components
import { SITE } from "@data/constants";
import MainLayout from "@/layouts/MainLayout.astro";
import { Image } from "astro:assets";
import { getCollection, render } from "astro:content";
// Use `getStaticPaths` to generate static routes for generated pages on build
export async function getStaticPaths() {
const insightPosts = await getCollection("insights", ({ id }) => id.startsWith("fr/"));
return insightPosts.map((post) => {
const idWithoutLang = post.id.replace(/^fr\//, ''); // Remove the "fr/" prefix
return {
params: { lang: 'fr', id: idWithoutLang },
props: { post },
};
});
}
// Get the props for this page that define a specific insight post
const { post } = Astro.props;
const { Content } = await render(post);
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<section class="py-6 sm:py-8 lg:py-12">
<div class="mx-auto max-w-screen-xl px-4 md:px-8">
<div class="grid gap-8 md:grid-cols-2 lg:gap-12">
<div>
<div class="h-64 overflow-hidden rounded-lg shadow-lg md:h-auto">
<Image
class="h-full w-full object-cover object-center"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>
<div
id="progress-mobile"
class="fixed left-0 top-0 h-2 w-full bg-gradient-to-r from-orange-400/30 to-orange-400 md:hidden"
>
</div>
<div id="pin" class="mt-10 hidden space-y-4 md:block">
<div
class="h-px w-full overflow-hidden bg-neutral-300 dark:bg-neutral-700"
>
<div
id="progress"
class="h-px w-full bg-gradient-to-r from-orange-400/30 to-orange-400"
>
</div>
</div>
<p class="text-pretty text-sm text-neutral-500">
Table of Contents:
</p>
<div id="toc" class="">
<ul
class="space-y-2 text-pretty text-base text-neutral-700 transition duration-300 dark:text-neutral-400"
>
</ul>
</div>
</div>
</div>
<div class="md:pt-8">
<h1
class="mb-4 text-balance text-center text-2xl font-bold text-neutral-800 dark:text-neutral-200 sm:text-3xl md:mb-6 md:text-left"
>
{post.data.title}
</h1>
<article
class="text-pretty text-lg text-neutral-700 dark:text-neutral-300"
>
<Content />
</article>
</div>
</div>
</div>
</section>
</MainLayout>
<style is:global>
:root {
--transition-cubic: cubic-bezier(0.165, 0.84, 0.44, 1);
}
html {
scroll-behavior: smooth;
}
article h2,
article h3,
article h4,
article h5,
article h6 {
font-weight: bold;
margin-top: 2.5rem;
scroll-margin-top: 3rem;
}
h2 {
font-size: 1.5rem;
line-height: 2rem;
}
h3 {
font-size: 1.25rem;
line-height: 1.75rem;
}
h4 {
font-size: 1.125rem;
line-height: 1.75rem;
}
p {
margin-top: 1.5rem;
}
#toc li {
display: flex;
align-items: center;
opacity: 0.8;
transition: all 300ms var(--transition-cubic);
}
#toc li.selected {
opacity: 1;
}
#toc li svg {
width: 0;
height: 0;
transition:
height 400ms var(--transition-cubic),
width 400ms var(--transition-cubic);
}
#toc li.selected svg {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.3rem;
}
</style>
<script>
const onScroll = (): void => {
const article = document.querySelector("article");
if (!article) return;
const articleHeight = article.offsetHeight;
const articleOffsetTop = article.offsetTop;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
if (articleHeight && articleOffsetTop && scrollTop) {
const progress =
((scrollTop - articleOffsetTop) /
(articleHeight - window.innerHeight)) *
100;
const progressBar = document.getElementById("progress");
const progressBarMobile = document.getElementById("progress-mobile");
if (progressBar && progressBarMobile) {
progressBar.style.width = `${progress}%`;
progressBarMobile.style.width = `${progress}%`;
}
}
};
document.addEventListener("DOMContentLoaded", (event) => {
window.onscroll = onScroll;
// Set initial width of progress bar
const progressBar = document.getElementById("progress");
const progressBarMobile = document.getElementById("progress-mobile");
if (progressBar && progressBarMobile) {
progressBar.style.width = "0%";
progressBarMobile.style.width = "0%";
}
});
</script>
<script>
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
gsap.timeline({
scrollTrigger: {
scrub: 1,
pin: true,
trigger: "#pin",
start: "top 20%",
endTrigger: "footer",
end: "top bottom",
},
});
const SVG_HTML_STRING =
'<svg class="w-0 h-0 flex-none" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15"><path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></svg>';
function setActiveLinkById(id: string | null) {
const listItems = document.querySelectorAll("#toc li");
listItems.forEach((item) => item.classList.remove("selected"));
if (!id) return;
const activeLink = document.querySelector(`#toc a[href="#${id}"]`);
if (!activeLink) return;
const listItem = activeLink.parentElement;
listItem?.classList.add("selected");
}
document.addEventListener("DOMContentLoaded", function () {
// The article element that contains the Markdown content
const article: HTMLElement | null = document.querySelector("article");
// The ToC container <ul> element
const tocList: HTMLElement | null = document.querySelector("#toc ul");
const headings: NodeListOf<HTMLElement> | [] = article
? article.querySelectorAll("h1, h2, h3, h4, h5, h6")
: [];
headings.forEach((heading, i) => {
if (heading instanceof HTMLElement) {
const listItem = document.createElement("li");
listItem.className = "toc-level-" + heading.tagName.toLowerCase();
const tempDiv = document.createElement("div");
tempDiv.innerHTML = SVG_HTML_STRING;
const svg = tempDiv.firstChild;
listItem.appendChild(svg as Node);
const link = document.createElement("a");
link.href = "#" + heading.id;
link.textContent = heading.textContent;
listItem.appendChild(link);
tocList?.appendChild(listItem);
gsap.timeline({
scrollTrigger: {
trigger: heading,
start: "top 20%",
end: () =>
`bottom top+=${i === headings.length - 1 ? 0 : (headings[i + 1] as HTMLElement).getBoundingClientRect().height}`,
onEnter: () => setActiveLinkById(heading.id),
onLeaveBack: () =>
setActiveLinkById((headings[i - 1] as HTMLElement)?.id),
},
});
}
});
});
</script>

View file

@ -1,391 +0,0 @@
---
// Import section components
import MainLayout from "@/layouts/MainLayout.astro";
import ProductTabBtn from "@components/ui/buttons/ProductTabBtn.astro";
import PrimaryCTA from "@components/ui/buttons/PrimaryCTA.astro";
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
import { SITE } from "@data/constants";
// Global declaration for gsap animation library
declare global {
interface Window {
gsap: any;
}
}
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products", ({ id }) =>
id.startsWith("fr/")
);
return productEntries.map((product) => {
const idWithoutLang = product.id.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", id: idWithoutLang },
props: { product },
};
});
}
const { product } = Astro.props;
const pageTitle: string = `${product.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<div id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
</div>
<section
class="mx-auto flex max-w-[85rem] flex-col px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div>
<p
id="fadeText"
class="mb-8 max-w-prose text-pretty font-light text-neutral-700 dark:text-neutral-300 sm:text-xl"
>
{product.data.main.content}
</p>
</div>
<div
class="flex flex-col items-center justify-between space-y-4 sm:flex-row sm:space-y-0"
>
<div id="fadeInUp">
<h1
class="block text-4xl font-bold tracking-tighter text-neutral-800 dark:text-neutral-200 sm:text-5xl md:text-6xl lg:text-7xl"
>
{product.data.title}
</h1>
<p class="text-lg text-neutral-600 dark:text-neutral-400">
{product.data.description}
</p>
</div>
<div>
<Image
id="fadeInMoveRight"
src={product.data.main.imgMain}
class="w-[600px]"
alt={product.data.main.imgAlt}
format={"avif"}
loading={"eager"}
/>
</div>
</div>
</section>
<div class="mx-auto max-w-[85rem] px-4 pt-10 sm:px-6 lg:px-8 lg:pt-14">
<nav
class="mx-auto grid max-w-6xl gap-y-px sm:flex sm:gap-x-4 sm:gap-y-0"
aria-label="Tabs"
role="tablist"
>
{
product.data.tabs.map((tab, index) => (
<ProductTabBtn
title={tab.title}
id={tab.id}
dataTab={tab.dataTab}
first={index === 0}
/>
))
}
</nav>
<div class="mt-12 md:mt-16">
<div id="tabs-with-card-1" role="tabpanel">
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
<div class="grid gap-12 md:grid-cols-2">
<div class="lg:w-3/4">
<h2
class="text-balance text-3xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:leading-tight lg:text-4xl"
>
{product.data.longDescription.title}
</h2>
<p
class="mt-3 text-pretty text-neutral-600 dark:text-neutral-400"
>
{product.data.longDescription.subTitle}
</p>
<p class="mt-5">
<PrimaryCTA
title={product.data.longDescription.btnTitle}
url={product.data.longDescription.btnURL}
/>
</p>
</div>
<div class="space-y-6 lg:space-y-10">
{
product.data.descriptionList.map((list) => (
<div class="flex">
<div>
<h3 class="text-base font-bold text-neutral-800 dark:text-neutral-200 sm:text-lg">
{list.title}
</h3>
<p class="mt-1 text-neutral-600 dark:text-neutral-400">
{list.subTitle}
</p>
</div>
</div>
))
}
</div>
</div>
</div>
</div>
<div id="tabs-with-card-2" class="hidden" role="tabpanel">
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
<div class="grid w-full grid-cols-1 gap-x-16 md:grid-cols-2">
<div class="max-w-md space-y-6">
{
product.data.specificationsLeft.map((spec) => (
<div>
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
{spec.title}
</h3>
<p class="text-neutral-600 dark:text-neutral-400">
{spec.subTitle}
</p>
</div>
))
}
</div>
{
product.data.specificationsRight ? (
<div class="mt-6 max-w-md space-y-6 md:ml-auto md:mt-0">
{product.data.specificationsRight?.map((spec) => (
<div>
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
{spec.title}
</h3>
<p class="text-neutral-600 dark:text-neutral-400">
{spec.subTitle}
</p>
</div>
))}
</div>
) : product.data.tableData ? (
<div class="mt-6 space-y-6 md:ml-auto md:mt-0">
<div class="flex flex-col">
<div class="-m-1.5 overflow-x-auto">
<div class="inline-block min-w-full p-1.5 align-middle">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-neutral-300 dark:divide-neutral-700">
<thead>
<tr>
{product.data.tableData?.[0].feature?.map(
(header) => (
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-neutral-500 dark:text-neutral-500"
>
{header}
</th>
)
)}
</tr>
</thead>
<tbody class="divide-y divide-neutral-300 dark:divide-neutral-700">
{product.data.tableData?.map((row) =>
// Wrap each row's content in a separate <tr> element
row.description.map((rowData) => (
<tr>
{/* Iterate through each cell value in the row's description array */}
{rowData.map((cellValue) => (
// Render each cell value in its own <td> element
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium text-neutral-600 dark:text-neutral-400">
{cellValue}
</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
) : null
}
</div>
</div>
</div>
</div>
</div>
<div id="tabs-with-card-3" class="hidden" role="tabpanel">
<div class="mx-auto mb-20 flex w-full md:mb-28 2xl:w-4/5">
<div
class="relative left-12 top-12 z-10 overflow-hidden rounded-xl shadow-lg md:left-12 md:top-16 md:-ml-12 lg:ml-0"
>
{
product.data.blueprints.first && (
<Image
src={product.data.blueprints.first}
class="h-full w-full object-cover object-center"
alt="Blueprint Illustration"
format={"avif"}
/>
)
}
</div>
<div class="relative right-12 overflow-hidden rounded-xl shadow-xl">
{
product.data.blueprints.second && (
<Image
src={product.data.blueprints.second}
class="h-full w-full object-cover object-center"
alt="Blueprint Illustration"
format={"avif"}
/>
)
}
</div>
</div>
</div>
</MainLayout>
<script>
import { gsap } from "gsap";
type AnimationSettings = {
autoAlpha?: number;
y?: number;
x?: number;
willChange?: string;
};
function setElementAnimationDefaults(
id: string,
settings: AnimationSettings
) {
gsap.set(id, settings);
}
setElementAnimationDefaults("#fadeText", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
setElementAnimationDefaults("#fadeInUp", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
setElementAnimationDefaults("#fadeInMoveRight", {
autoAlpha: 0,
x: 300,
willChange: "transform, opacity",
});
let timeline = gsap.timeline({ defaults: { overwrite: "auto" } });
timeline.to("#fadeText", {
duration: 1.5,
autoAlpha: 1,
y: 0,
delay: 1,
ease: "power2.out",
});
timeline.to(
"#fadeInUp",
{ duration: 1.5, autoAlpha: 1, y: 0, ease: "power2.out" },
"-=1.2"
);
timeline.to(
"#fadeInMoveRight",
{ duration: 1.5, autoAlpha: 1, x: 0, ease: "power2.inOut" },
"-=1.4"
);
timeline.to("#overlay", { duration: 1, autoAlpha: 0, delay: 0.2 });
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
function setButtonInactive(btn: any, activeButton: any) {
if (btn !== activeButton) {
btn.classList.remove(
"active",
"bg-neutral-100",
"hover:border-transparent",
"dark:bg-white/[.05]"
);
const tabId = btn.getAttribute("data-target");
if (tabId) {
const contentElement = document.querySelector(tabId);
if (contentElement) {
contentElement.classList.add("hidden");
}
}
changeHeadingStyle(
btn,
["text-neutral-800", "dark:text-neutral-200"],
["text-orange-400", "dark:text-orange-300"]
);
}
}
function activateButton(button: any) {
button.classList.add(
"active",
"bg-neutral-100",
",hover:border-transparent",
"dark:bg-white/[.05]"
);
const tabId = button.getAttribute("data-target");
if (tabId) {
const contentElementToShow = document.querySelector(tabId);
if (contentElementToShow) {
contentElementToShow.classList.remove("hidden");
}
}
changeHeadingStyle(
button,
["text-orange-400", "dark:text-orange-300"],
["text-neutral-800", "dark:text-neutral-200"]
);
}
function changeHeadingStyle(
button: any,
addClasses: any,
removeClasses: any
) {
let heading = button.querySelector("span");
if (heading) {
heading.classList.remove(...removeClasses);
heading.classList.add(...addClasses);
}
}
const tabButtons = document.querySelectorAll("[data-target]");
if (tabButtons.length > 0) {
changeHeadingStyle(
tabButtons[0],
["text-orange-400", "dark:text-orange-300"],
[]
);
}
tabButtons.forEach((button) => {
button.addEventListener("click", () => {
tabButtons.forEach((btn) => setButtonInactive(btn, button));
activateButton(button);
});
});
});
</script>

View file

@ -1,391 +0,0 @@
---
// Import section components
import MainLayout from "@/layouts/MainLayout.astro";
import ProductTabBtn from "@components/ui/buttons/ProductTabBtn.astro";
import PrimaryCTA from "@components/ui/buttons/PrimaryCTA.astro";
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
import { SITE } from "@data/constants";
// Global declaration for gsap animation library
declare global {
interface Window {
gsap: any;
}
}
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products", ({ id }) =>
id.startsWith("fr/")
);
return productEntries.map((product) => {
const idWithoutLang = product.id.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", id: idWithoutLang },
props: { product },
};
});
}
const { product } = Astro.props;
const pageTitle: string = `${product.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<div id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
</div>
<section
class="mx-auto flex max-w-[85rem] flex-col px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div>
<p
id="fadeText"
class="mb-8 max-w-prose text-pretty font-light text-neutral-700 dark:text-neutral-300 sm:text-xl"
>
{product.data.main.content}
</p>
</div>
<div
class="flex flex-col items-center justify-between space-y-4 sm:flex-row sm:space-y-0"
>
<div id="fadeInUp">
<h1
class="block text-4xl font-bold tracking-tighter text-neutral-800 dark:text-neutral-200 sm:text-5xl md:text-6xl lg:text-7xl"
>
{product.data.title}
</h1>
<p class="text-lg text-neutral-600 dark:text-neutral-400">
{product.data.description}
</p>
</div>
<div>
<Image
id="fadeInMoveRight"
src={product.data.main.imgMain}
class="w-[600px]"
alt={product.data.main.imgAlt}
format={"avif"}
loading={"eager"}
/>
</div>
</div>
</section>
<div class="mx-auto max-w-[85rem] px-4 pt-10 sm:px-6 lg:px-8 lg:pt-14">
<nav
class="mx-auto grid max-w-6xl gap-y-px sm:flex sm:gap-x-4 sm:gap-y-0"
aria-label="Tabs"
role="tablist"
>
{
product.data.tabs.map((tab, index) => (
<ProductTabBtn
title={tab.title}
id={tab.id}
dataTab={tab.dataTab}
first={index === 0}
/>
))
}
</nav>
<div class="mt-12 md:mt-16">
<div id="tabs-with-card-1" role="tabpanel">
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
<div class="grid gap-12 md:grid-cols-2">
<div class="lg:w-3/4">
<h2
class="text-balance text-3xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:leading-tight lg:text-4xl"
>
{product.data.longDescription.title}
</h2>
<p
class="mt-3 text-pretty text-neutral-600 dark:text-neutral-400"
>
{product.data.longDescription.subTitle}
</p>
<p class="mt-5">
<PrimaryCTA
title={product.data.longDescription.btnTitle}
url={product.data.longDescription.btnURL}
/>
</p>
</div>
<div class="space-y-6 lg:space-y-10">
{
product.data.descriptionList.map((list) => (
<div class="flex">
<div>
<h3 class="text-base font-bold text-neutral-800 dark:text-neutral-200 sm:text-lg">
{list.title}
</h3>
<p class="mt-1 text-neutral-600 dark:text-neutral-400">
{list.subTitle}
</p>
</div>
</div>
))
}
</div>
</div>
</div>
</div>
<div id="tabs-with-card-2" class="hidden" role="tabpanel">
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
<div class="grid w-full grid-cols-1 gap-x-16 md:grid-cols-2">
<div class="max-w-md space-y-6">
{
product.data.specificationsLeft.map((spec) => (
<div>
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
{spec.title}
</h3>
<p class="text-neutral-600 dark:text-neutral-400">
{spec.subTitle}
</p>
</div>
))
}
</div>
{
product.data.specificationsRight ? (
<div class="mt-6 max-w-md space-y-6 md:ml-auto md:mt-0">
{product.data.specificationsRight?.map((spec) => (
<div>
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
{spec.title}
</h3>
<p class="text-neutral-600 dark:text-neutral-400">
{spec.subTitle}
</p>
</div>
))}
</div>
) : product.data.tableData ? (
<div class="mt-6 space-y-6 md:ml-auto md:mt-0">
<div class="flex flex-col">
<div class="-m-1.5 overflow-x-auto">
<div class="inline-block min-w-full p-1.5 align-middle">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-neutral-300 dark:divide-neutral-700">
<thead>
<tr>
{product.data.tableData?.[0].feature?.map(
(header) => (
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-neutral-500 dark:text-neutral-500"
>
{header}
</th>
)
)}
</tr>
</thead>
<tbody class="divide-y divide-neutral-300 dark:divide-neutral-700">
{product.data.tableData?.map((row) =>
// Wrap each row's content in a separate <tr> element
row.description.map((rowData) => (
<tr>
{/* Iterate through each cell value in the row's description array */}
{rowData.map((cellValue) => (
// Render each cell value in its own <td> element
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium text-neutral-600 dark:text-neutral-400">
{cellValue}
</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
) : null
}
</div>
</div>
</div>
</div>
</div>
<div id="tabs-with-card-3" class="hidden" role="tabpanel">
<div class="mx-auto mb-20 flex w-full md:mb-28 2xl:w-4/5">
<div
class="relative left-12 top-12 z-10 overflow-hidden rounded-xl shadow-lg md:left-12 md:top-16 md:-ml-12 lg:ml-0"
>
{
product.data.blueprints.first && (
<Image
src={product.data.blueprints.first}
class="h-full w-full object-cover object-center"
alt="Blueprint Illustration"
format={"avif"}
/>
)
}
</div>
<div class="relative right-12 overflow-hidden rounded-xl shadow-xl">
{
product.data.blueprints.second && (
<Image
src={product.data.blueprints.second}
class="h-full w-full object-cover object-center"
alt="Blueprint Illustration"
format={"avif"}
/>
)
}
</div>
</div>
</div>
</MainLayout>
<script>
import { gsap } from "gsap";
type AnimationSettings = {
autoAlpha?: number;
y?: number;
x?: number;
willChange?: string;
};
function setElementAnimationDefaults(
id: string,
settings: AnimationSettings
) {
gsap.set(id, settings);
}
setElementAnimationDefaults("#fadeText", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
setElementAnimationDefaults("#fadeInUp", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
setElementAnimationDefaults("#fadeInMoveRight", {
autoAlpha: 0,
x: 300,
willChange: "transform, opacity",
});
let timeline = gsap.timeline({ defaults: { overwrite: "auto" } });
timeline.to("#fadeText", {
duration: 1.5,
autoAlpha: 1,
y: 0,
delay: 1,
ease: "power2.out",
});
timeline.to(
"#fadeInUp",
{ duration: 1.5, autoAlpha: 1, y: 0, ease: "power2.out" },
"-=1.2"
);
timeline.to(
"#fadeInMoveRight",
{ duration: 1.5, autoAlpha: 1, x: 0, ease: "power2.inOut" },
"-=1.4"
);
timeline.to("#overlay", { duration: 1, autoAlpha: 0, delay: 0.2 });
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
function setButtonInactive(btn: any, activeButton: any) {
if (btn !== activeButton) {
btn.classList.remove(
"active",
"bg-neutral-100",
"hover:border-transparent",
"dark:bg-white/[.05]"
);
const tabId = btn.getAttribute("data-target");
if (tabId) {
const contentElement = document.querySelector(tabId);
if (contentElement) {
contentElement.classList.add("hidden");
}
}
changeHeadingStyle(
btn,
["text-neutral-800", "dark:text-neutral-200"],
["text-orange-400", "dark:text-orange-300"]
);
}
}
function activateButton(button: any) {
button.classList.add(
"active",
"bg-neutral-100",
",hover:border-transparent",
"dark:bg-white/[.05]"
);
const tabId = button.getAttribute("data-target");
if (tabId) {
const contentElementToShow = document.querySelector(tabId);
if (contentElementToShow) {
contentElementToShow.classList.remove("hidden");
}
}
changeHeadingStyle(
button,
["text-orange-400", "dark:text-orange-300"],
["text-neutral-800", "dark:text-neutral-200"]
);
}
function changeHeadingStyle(
button: any,
addClasses: any,
removeClasses: any
) {
let heading = button.querySelector("span");
if (heading) {
heading.classList.remove(...removeClasses);
heading.classList.add(...addClasses);
}
}
const tabButtons = document.querySelectorAll("[data-target]");
if (tabButtons.length > 0) {
changeHeadingStyle(
tabButtons[0],
["text-orange-400", "dark:text-orange-300"],
[]
);
}
tabButtons.forEach((button) => {
button.addEventListener("click", () => {
tabButtons.forEach((btn) => setButtonInactive(btn, button));
activateButton(button);
});
});
});
</script>

View file

@ -1,138 +0,0 @@
---
// Importing necessary components
import MainLayout from "@/layouts/MainLayout.astro";
import PrimaryCTA from "@components/ui/buttons/PrimaryCTA.astro";
import CardSmall from "@components/ui/cards/CardSmall.astro";
import CardWide from "@components/ui/cards/CardWide.astro";
import FeaturesStatsAlt from "@components/sections/features/FeaturesStatsAlt.astro";
import TestimonialsSectionAlt from "@components/sections/testimonials/TestimonialsSectionAlt.astro";
// Importing necessary functions from Astro
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
// Fetching all the product related content and sorting it by main.id
const product: CollectionEntry<"products">[] = (
await getCollection("products", ({ id }) => {
return id.startsWith("fr/");
})
).sort(
(a: CollectionEntry<"products">, b: CollectionEntry<"products">) =>
a.data.main.id - b.data.main.id
);
// Define variables for page content
const title: string = "Produits";
const subTitle: string =
"Explorez la durabilité et la précision des outils ScrewFast, conçus aussi bien pour les professionnels que pour les amateurs. Chacun de nos produits est fabriqué avec précision et conçu pour durer, garantissant que vous disposez du bon outil pour chaque tâche.";
// Testimonial data that will be rendered in the component
const testimonials = [
{
content:
"Depuis que nous avons adopté les outils matériels de ScrewFast, l'efficacité sur nos chantiers de construction a explosé. La durabilité des boulons hexagonaux et la précision des vis machine sont tout simplement inégalées. C'est rafraîchissant de travailler avec une entreprise qui comprend vraiment les exigences quotidiennes de l'industrie.",
author: "Jason Clark",
role: "Contremaître de chantier | TopBuild",
avatarSrc:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?q=80&w=1374&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=facearea&facepad=2&w=320&h=320&q=80",
avatarAlt: "Description de l'image",
},
{
content:
"En tant que designer d'intérieur, je suis toujours à la recherche de matériaux et d'outils de haute qualité qui m'aident à donner vie à mes visions. L'assortiment de vis mixtes de ScrewFast a révolutionné mes projets, offrant le mélange parfait de qualité et de variété. Le support client exceptionnel était la cerise sur le gâteau !",
author: "Maria Gonzalez",
role: "Designer d'intérieur | Creative Spaces",
avatarSrc:
"https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=1376&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D8&auto=format&fit=facearea&facepad=2&w=320&h=320&q=80",
avatarAlt: "Description de l'image",
},
{
content:
"Je suis menuisier professionnel depuis plus de 15 ans, et je peux sincèrement dire que les boulons et écrous à tarauder de ScrewFast sont parmi les meilleurs que j'ai utilisés. Ils adhèrent comme aucun autre, et j'ai une confiance totale dans chaque joint et élément. De plus, le service est impeccable - ils se soucient vraiment du succès de mon projet.",
author: "Richard Kim",
role: "Menuisier-Maître | WoodWright",
avatarSrc:
"https://images.unsplash.com/photo-1474176857210-7287d38d27c6?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D8&auto=format&fit=facearea&facepad=2&w=320&h=320&q=80",
avatarAlt: "Description de l'image",
},
];
---
<MainLayout
title="Produits | ScrewFast"
lang="fr"
structuredData={{
"@context": "https://schema.org",
"@type": "WebPage",
"@id": "https://screwfast.uk/fr/products",
url: "https://screwfast.uk/fr/products",
name: "Outils Matériels | ScrewFast",
description:
"Explorez la durabilité et la précision des outils ScrewFast, conçus aussi bien pour les professionnels que pour les passionnés.",
isPartOf: {
"@type": "WebSite",
url: "https://screwfast.uk/fr",
name: "ScrewFast",
description:
"ScrewFast propose des outils matériels de premier ordre et des services de construction experts pour répondre à tous vos besoins de projet.",
},
inLanguage: "fr",
}}
>
<div
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div class="mb-4 flex items-center justify-between gap-8 sm:mb-8 md:mb-12">
<div class="flex items-center gap-12">
<h1
class="text-balance text-2xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
{title}
</h1>
{
subTitle && (
<p class="hidden max-w-screen-sm text-pretty text-neutral-600 dark:text-neutral-400 md:block">
{subTitle}
</p>
)
}
</div>
<PrimaryCTA
title="Histoires de clients"
url="#testimonials"
noArrow={true}
/>
</div>
<!--Displaying products in alternating styles. Alternative product gets different card styling.-->
<!--Maps through all product entries and displays them with either CardSmall or CardWide based on their position.-->
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3 md:gap-6 xl:gap-8">
{
product.map((product, index) => {
const position = index % 4;
if (position === 0 || position === 3) {
return <CardSmall product={product} productLocale="fr" />;
} else {
return <CardWide product={product} productLocale="fr" />;
}
})
}
</section>
</div>
<!--Features statistics section-->
<FeaturesStatsAlt
title="Pourquoi choisir ScrewFast ?"
subTitle="Transformez vos idées en résultats tangibles avec les outils ScrewFast. Que vous commenciez par un croquis sur un coin de table ou plongiez dans un projet de construction complet, nos outils sont conçus pour vous aider à construire en toute confiance."
benefits={[
"Outils robustes et fiables pour des performances durables.",
"Solutions innovantes adaptées aux besoins de construction modernes.",
"Support client dédié au succès de votre projet.",
]}
/>
<!--Testimonials section-->
<TestimonialsSectionAlt
title="Ce que disent nos clients"
testimonials={testimonials}
/>
</MainLayout>

View file

@ -1,181 +0,0 @@
---
// Import necessary components
import MainLayout from "@/layouts/MainLayout.astro";
import MainSection from "@components/ui/blocks/MainSection.astro";
import LeftSection from "@components/ui/blocks/LeftSection.astro";
import RightSection from "@components/ui/blocks/RightSection.astro";
import FeaturesStats from "@components/sections/features/FeaturesStats.astro";
// Import necessary images
import blueprints from "@images/blueprints-image.avif";
import personWorking from "@images/person-working.avif";
import beforeAfter from "@images/before-after.avif";
import constructionWorkers from "@images/construction-workers.avif";
import aerialView from "@images/aerial-view.avif";
import usingTools from "@images/using-tools.avif";
import progressBuilding from "@images/progress-building.avif";
import underConstruction from "@images/under-construction.avif";
interface Article {
isRightSection: boolean;
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
single?: boolean;
img?: any;
imgAlt?: string;
imgOne?: any;
imgOneAlt?: string;
imgTwo?: any;
imgTwoAlt?: string;
}
const articles: Article[] = [
{
isRightSection: true,
title: "Fournir des conseils d'experts",
subTitle:
"Se lancer dans un projet de construction peut être accablant. Avec nos services de consultation professionnelle, nous vous guidons à chaque étape, en veillant à ce que vous preniez des décisions éclairées. Que vous soyez un passionné du bricolage ou un entrepreneur qualifié, nos experts sont là pour vous offrir des conseils sur mesure sur la sélection de produits, l'envergure du projet et la conformité aux réglementations locales.",
single: false,
imgOne: blueprints,
imgOneAlt: "Plans et tablette numérique avec des plans de construction.",
imgTwo: personWorking,
imgTwoAlt: "Personne travaillant au bureau",
},
{
isRightSection: false,
title: "Transformer les conceptions en réalité",
subTitle:
"Nos artisans qualifiés apportent précision et excellence à chaque projet de construction. Des installations mineures aux travaux structuraux substantiels, ScrewFast offre des services de construction fiables pour concrétiser vos plans. Nous assurons les normes les plus élevées de sécurité et de savoir-faire, en utilisant des outils et des matériaux de haute qualité de notre vaste inventaire.",
img: beforeAfter,
imgAlt: "Chantier de construction avant et après",
btnExists: true,
btnTitle: "En savoir plus",
btnURL: "#",
},
{
isRightSection: true,
title: "Naviguer dans les projets avec une supervision professionnelle",
subTitle:
"La gestion de projet efficace est au cœur de toute construction réussie. ScrewFast offre une planification approfondie et des services de gestion solides qui maintiennent votre projet dans les délais et dans le budget. Laissez-nous gérer les complexités de la coordination des flux de travail, de l'allocation des ressources et de la communication avec les parties prenantes pendant que vous vous concentrez sur votre vision.",
single: false,
imgOne: constructionWorkers,
imgOneAlt: "Ouvriers du bâtiment orchestrant un projet",
imgTwo: aerialView,
imgTwoAlt: "Vue aérienne d'une construction gérée",
},
{
isRightSection: false,
title: "Garantir des performances durables",
subTitle:
"Notre engagement envers votre projet ne s'arrête pas à son achèvement. ScrewFast propose des services de maintenance et de support continus pour assurer la longévité et les performances de votre construction. Des vérifications régulières à l'assistance en cas d'urgence, notre équipe réactive est là pour vous fournir un soutien sans faille.",
img: usingTools,
imgAlt:
"Homme en gilet orange et noir portant un casque blanc tenant un outil électrique jaune et noir",
},
{
isRightSection: true,
title: "Élaboration de stratégies sur mesure pour des défis uniques",
subTitle:
"Pour nos clients d'entreprise de plus grande envergure, ScrewFast propose des solutions personnalisées conçues pour répondre à des défis spécifiques de l'industrie. En comprenant vos besoins uniques, nous concevons des stratégies sur mesure visant à optimiser vos opérations, à améliorer l'efficacité et à faire avancer votre entreprise.",
single: false,
imgOne: progressBuilding,
imgOneAlt: "Structure de bâtiment en cours de construction",
imgTwo: underConstruction,
imgTwoAlt: "Bâtiment marron et gris en construction",
btnExists: true,
btnTitle: "Lire la suite",
btnURL: "#",
},
];
---
<MainLayout
title="Services | ScrewFast"
lang="fr"
structuredData={{
"@context": "https://schema.org",
"@type": "WebPage",
"@id": "https://screwfast.uk/fr/services",
url: "https://screwfast.uk/fr/services",
name: "Services de Consultation d'Experts | ScrewFast",
description:
"Unissant l'expertise à votre vision, ScrewFast fournit un service exceptionnel et des solutions complètes dans le secteur du matériel et de la construction, de la consultation à l'achèvement du projet.",
isPartOf: {
"@type": "WebSite",
url: "https://screwfast.uk/fr",
name: "ScrewFast",
description:
"ScrewFast propose des outils matériels de premier ordre et des services de construction experts pour répondre à tous vos besoins de projet.",
},
inLanguage: "fr",
}}
>
<!--MainSection is the introductory section of the page, it also contains a CTA button-->
<MainSection
title="Unir l'expertise à votre vision"
subTitle="Chez ScrewFast, nous sommes fiers de fournir des solutions complètes et un service exceptionnel dans l'industrie du matériel et de la construction. Notre équipe expérimentée est dédiée à soutenir votre projet de sa conception à son achèvement avec une gamme de services spécialisés."
btnExists={true}
btnTitle="Planifier une consultation"
btnURL="#"
/>
<!-- RightSection and LeftSection contain details about various services along with pertinent imagery.
They alternate for variety in design.
The 'btnExists' property is used to toggle the display of a button in these sections.
When btnExists={true}, a button is displayed.
This can be used to link to more detailed information or related resources.
RightSection can also conditionally render one or two images based on the 'single' property.
If 'single' is true, it displays one image, otherwise it displays two.
-->
{
articles.map((article) => {
return article.isRightSection ? (
<RightSection
title={article.title}
subTitle={article.subTitle}
single={article.single}
imgOne={article.imgOne}
imgOneAlt={article.imgOneAlt}
imgTwo={article.imgTwo}
imgTwoAlt={article.imgTwoAlt}
btnExists={article.btnExists}
btnTitle={article.btnTitle}
btnURL={article.btnURL}
/>
) : (
<LeftSection
title={article.title}
subTitle={article.subTitle}
img={article.img}
imgAlt={article.imgAlt}
btnExists={article.btnExists}
btnTitle={article.btnTitle}
btnURL={article.btnURL}
/>
);
})
}
<!--FeaturesStats section showcases essential stats valuable to users-->
<FeaturesStats
title="Par les chiffres"
subTitle="Notre engagement envers la qualité et la fiabilité est évident dans chaque projet que nous entreprenons. Chez ScrewFast, nous nous engageons à fournir des services de premier plan dans l'industrie qui garantissent que vos projets de construction sont conçus pour durer."
mainStatTitle="96%"
mainStatSubTitle="de nos clients évaluent leur expérience avec ScrewFast comme exceptionnelle"
stats={[
{
stat: "99,8%",
description: "taux de réalisation de projets",
},
{
stat: "5 000+",
description: "installations réussies",
},
{
stat: "85%",
description: "croissance client année après année",
},
]}
/>
</MainLayout>