feat: refactor styles and fix errors
This commit is contained in:
parent
b40644e307
commit
7c92da1b9b
4 changed files with 825 additions and 0 deletions
|
@ -100,6 +100,11 @@ article.card {
|
|||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.pagination-links a:hover,
|
||||
.sl-link-card:hover {
|
||||
border-color: var(--sl-color-accent);
|
||||
}
|
||||
|
||||
.starlight-aside--tip {
|
||||
background: linear-gradient(45deg, #ff512f, #f09819);
|
||||
border: none;
|
||||
|
|
163
src/pages/fr/blog/[id].astro
Normal file
163
src/pages/fr/blog/[id].astro
Normal file
|
@ -0,0 +1,163 @@
|
|||
---
|
||||
// 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>
|
266
src/pages/fr/insights/[id].astro
Normal file
266
src/pages/fr/insights/[id].astro
Normal file
|
@ -0,0 +1,266 @@
|
|||
---
|
||||
// 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>
|
391
src/pages/fr/products/[id].astro
Normal file
391
src/pages/fr/products/[id].astro
Normal file
|
@ -0,0 +1,391 @@
|
|||
---
|
||||
// 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>
|
Loading…
Add table
Reference in a new issue