diff --git a/parfum-shop/index.html b/parfum-shop/index.html index 1813149..b410625 100644 --- a/parfum-shop/index.html +++ b/parfum-shop/index.html @@ -1,16 +1,59 @@ - + - - - - - parfum-shop + + atmos · Konzeptionelle Düfte aus der Schweiz + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/parfum-shop/public/BLASSE SEIDE.png b/parfum-shop/public/BLASSE SEIDE.png deleted file mode 100644 index 1dc446f..0000000 Binary files a/parfum-shop/public/BLASSE SEIDE.png and /dev/null differ diff --git a/parfum-shop/public/DISCOVERYSET.png b/parfum-shop/public/DISCOVERYSET.png deleted file mode 100644 index 52df3b0..0000000 Binary files a/parfum-shop/public/DISCOVERYSET.png and /dev/null differ diff --git a/parfum-shop/public/HERO.jpeg b/parfum-shop/public/HERO.jpeg deleted file mode 100644 index 08924d8..0000000 Binary files a/parfum-shop/public/HERO.jpeg and /dev/null differ diff --git a/parfum-shop/public/KALTER BETON.png b/parfum-shop/public/KALTER BETON.png deleted file mode 100644 index 5cb7db8..0000000 Binary files a/parfum-shop/public/KALTER BETON.png and /dev/null differ diff --git a/parfum-shop/public/NASSER MARMOR.png b/parfum-shop/public/NASSER MARMOR.png deleted file mode 100644 index 4b3b3c2..0000000 Binary files a/parfum-shop/public/NASSER MARMOR.png and /dev/null differ diff --git a/parfum-shop/public/SCHWARZES BENZIN.png b/parfum-shop/public/SCHWARZES BENZIN.png deleted file mode 100644 index 116a324..0000000 Binary files a/parfum-shop/public/SCHWARZES BENZIN.png and /dev/null differ diff --git a/parfum-shop/public/VERBRANNTES CHROM.png b/parfum-shop/public/VERBRANNTES CHROM.png deleted file mode 100644 index d5be92e..0000000 Binary files a/parfum-shop/public/VERBRANNTES CHROM.png and /dev/null differ diff --git a/parfum-shop/public/WEISSE ASCHE.png b/parfum-shop/public/WEISSE ASCHE.png deleted file mode 100644 index 7baf6b0..0000000 Binary files a/parfum-shop/public/WEISSE ASCHE.png and /dev/null differ diff --git a/parfum-shop/public/atmos-discovery-set-thumbnail.png b/parfum-shop/public/atmos-discovery-set-thumbnail.png deleted file mode 100755 index 59bb364..0000000 Binary files a/parfum-shop/public/atmos-discovery-set-thumbnail.png and /dev/null differ diff --git a/parfum-shop/public/atmos-discovery-set-thumbnail.webp b/parfum-shop/public/atmos-discovery-set-thumbnail.webp new file mode 100644 index 0000000..18cc920 Binary files /dev/null and b/parfum-shop/public/atmos-discovery-set-thumbnail.webp differ diff --git a/parfum-shop/public/blasse-seide-product-image.png b/parfum-shop/public/blasse-seide-product-image.png deleted file mode 100644 index ad4b49a..0000000 Binary files a/parfum-shop/public/blasse-seide-product-image.png and /dev/null differ diff --git a/parfum-shop/public/blasse-seide-product-image.webp b/parfum-shop/public/blasse-seide-product-image.webp new file mode 100644 index 0000000..d8ea781 Binary files /dev/null and b/parfum-shop/public/blasse-seide-product-image.webp differ diff --git a/parfum-shop/public/blasse-seide-product-sample-image.png b/parfum-shop/public/blasse-seide-product-sample-image.png deleted file mode 100755 index f85610f..0000000 Binary files a/parfum-shop/public/blasse-seide-product-sample-image.png and /dev/null differ diff --git a/parfum-shop/public/blasse-seide-product-sample-image.webp b/parfum-shop/public/blasse-seide-product-sample-image.webp new file mode 100644 index 0000000..9fffd33 Binary files /dev/null and b/parfum-shop/public/blasse-seide-product-sample-image.webp differ diff --git a/parfum-shop/public/fonts/questrial/OFL.txt b/parfum-shop/public/fonts/questrial/OFL.txt new file mode 100644 index 0000000..48d2cb5 --- /dev/null +++ b/parfum-shop/public/fonts/questrial/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Questrial Project Authors (https://github.com/googlefonts/questrial) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/parfum-shop/public/fonts/questrial/questrial-latin-ext.woff2 b/parfum-shop/public/fonts/questrial/questrial-latin-ext.woff2 new file mode 100644 index 0000000..61d459b Binary files /dev/null and b/parfum-shop/public/fonts/questrial/questrial-latin-ext.woff2 differ diff --git a/parfum-shop/public/fonts/questrial/questrial-latin.woff2 b/parfum-shop/public/fonts/questrial/questrial-latin.woff2 new file mode 100644 index 0000000..a29a15b Binary files /dev/null and b/parfum-shop/public/fonts/questrial/questrial-latin.woff2 differ diff --git a/parfum-shop/public/fonts/questrial/questrial-vietnamese.woff2 b/parfum-shop/public/fonts/questrial/questrial-vietnamese.woff2 new file mode 100644 index 0000000..3bbc6fd Binary files /dev/null and b/parfum-shop/public/fonts/questrial/questrial-vietnamese.woff2 differ diff --git a/parfum-shop/public/kalter-beton-product-image.png b/parfum-shop/public/kalter-beton-product-image.png deleted file mode 100644 index 81c36da..0000000 Binary files a/parfum-shop/public/kalter-beton-product-image.png and /dev/null differ diff --git a/parfum-shop/public/kalter-beton-product-image.webp b/parfum-shop/public/kalter-beton-product-image.webp new file mode 100644 index 0000000..3d48b04 Binary files /dev/null and b/parfum-shop/public/kalter-beton-product-image.webp differ diff --git a/parfum-shop/public/kalter-beton-product-sample-image.png b/parfum-shop/public/kalter-beton-product-sample-image.png deleted file mode 100755 index b2ed715..0000000 Binary files a/parfum-shop/public/kalter-beton-product-sample-image.png and /dev/null differ diff --git a/parfum-shop/public/kalter-beton-product-sample-image.webp b/parfum-shop/public/kalter-beton-product-sample-image.webp new file mode 100644 index 0000000..37a29cc Binary files /dev/null and b/parfum-shop/public/kalter-beton-product-sample-image.webp differ diff --git a/parfum-shop/public/kalter-beton-product.png b/parfum-shop/public/kalter-beton-product.png deleted file mode 100644 index e19709e..0000000 Binary files a/parfum-shop/public/kalter-beton-product.png and /dev/null differ diff --git a/parfum-shop/public/nasser-marmor-product-image.png b/parfum-shop/public/nasser-marmor-product-image.png deleted file mode 100644 index 9097dd1..0000000 Binary files a/parfum-shop/public/nasser-marmor-product-image.png and /dev/null differ diff --git a/parfum-shop/public/nasser-marmor-product-image.webp b/parfum-shop/public/nasser-marmor-product-image.webp new file mode 100644 index 0000000..dce0d9f Binary files /dev/null and b/parfum-shop/public/nasser-marmor-product-image.webp differ diff --git a/parfum-shop/public/nasser-marmor-product-sample-image.png b/parfum-shop/public/nasser-marmor-product-sample-image.png deleted file mode 100755 index dd59ab7..0000000 Binary files a/parfum-shop/public/nasser-marmor-product-sample-image.png and /dev/null differ diff --git a/parfum-shop/public/nasser-marmor-product-sample-image.webp b/parfum-shop/public/nasser-marmor-product-sample-image.webp new file mode 100644 index 0000000..4d9893d Binary files /dev/null and b/parfum-shop/public/nasser-marmor-product-sample-image.webp differ diff --git a/parfum-shop/public/robots.txt b/parfum-shop/public/robots.txt new file mode 100644 index 0000000..718b774 --- /dev/null +++ b/parfum-shop/public/robots.txt @@ -0,0 +1,8 @@ +# robots.txt — atmos parfum-shop +# TODO: replace https://atmos.example with the real production domain + +User-agent: * +Allow: / +Disallow: /api/ + +Sitemap: https://atmos.example/sitemap.xml diff --git a/parfum-shop/public/schwarzes-benzin-product-image.png b/parfum-shop/public/schwarzes-benzin-product-image.png deleted file mode 100644 index 65efeab..0000000 Binary files a/parfum-shop/public/schwarzes-benzin-product-image.png and /dev/null differ diff --git a/parfum-shop/public/schwarzes-benzin-product-image.webp b/parfum-shop/public/schwarzes-benzin-product-image.webp new file mode 100644 index 0000000..06b711f Binary files /dev/null and b/parfum-shop/public/schwarzes-benzin-product-image.webp differ diff --git a/parfum-shop/public/schwarzes-benzin-product-sample-image.png b/parfum-shop/public/schwarzes-benzin-product-sample-image.png deleted file mode 100755 index cb0f6c6..0000000 Binary files a/parfum-shop/public/schwarzes-benzin-product-sample-image.png and /dev/null differ diff --git a/parfum-shop/public/schwarzes-benzin-product-sample-image.webp b/parfum-shop/public/schwarzes-benzin-product-sample-image.webp new file mode 100644 index 0000000..1acad4d Binary files /dev/null and b/parfum-shop/public/schwarzes-benzin-product-sample-image.webp differ diff --git a/parfum-shop/public/sitemap.xml b/parfum-shop/public/sitemap.xml new file mode 100644 index 0000000..3165da6 --- /dev/null +++ b/parfum-shop/public/sitemap.xml @@ -0,0 +1,68 @@ + + + + https://atmos.ch/ + weekly + 1.0 + + + https://atmos.ch/about + monthly + 0.7 + + + https://atmos.ch/discovery-set + monthly + 0.9 + + + https://atmos.ch/small-batch + monthly + 0.7 + + + https://atmos.ch/support + yearly + 0.4 + + + https://atmos.ch/impressum + yearly + 0.2 + + + https://atmos.ch/datenschutz + yearly + 0.2 + + + https://atmos.ch/duft/kalter-beton + monthly + 0.8 + + + https://atmos.ch/duft/nasser-marmor + monthly + 0.8 + + + https://atmos.ch/duft/blasse-seide + monthly + 0.8 + + + https://atmos.ch/duft/weisse-asche + monthly + 0.8 + + + https://atmos.ch/duft/verbranntes-chrom + monthly + 0.8 + + + https://atmos.ch/duft/schwarzes-benzin + monthly + 0.8 + + diff --git a/parfum-shop/public/verbranntes-chrom-product-image.png b/parfum-shop/public/verbranntes-chrom-product-image.png deleted file mode 100644 index 8ede93d..0000000 Binary files a/parfum-shop/public/verbranntes-chrom-product-image.png and /dev/null differ diff --git a/parfum-shop/public/verbranntes-chrom-product-image.webp b/parfum-shop/public/verbranntes-chrom-product-image.webp new file mode 100644 index 0000000..49ea60b Binary files /dev/null and b/parfum-shop/public/verbranntes-chrom-product-image.webp differ diff --git a/parfum-shop/public/verbrannteschrom-product-sample-image.png b/parfum-shop/public/verbrannteschrom-product-sample-image.png deleted file mode 100755 index aeeedc0..0000000 Binary files a/parfum-shop/public/verbrannteschrom-product-sample-image.png and /dev/null differ diff --git a/parfum-shop/public/verbrannteschrom-product-sample-image.webp b/parfum-shop/public/verbrannteschrom-product-sample-image.webp new file mode 100644 index 0000000..34d9ca4 Binary files /dev/null and b/parfum-shop/public/verbrannteschrom-product-sample-image.webp differ diff --git a/parfum-shop/public/weisse-asche-product-image.png b/parfum-shop/public/weisse-asche-product-image.png deleted file mode 100644 index a8d03c5..0000000 Binary files a/parfum-shop/public/weisse-asche-product-image.png and /dev/null differ diff --git a/parfum-shop/public/weisse-asche-product-image.webp b/parfum-shop/public/weisse-asche-product-image.webp new file mode 100644 index 0000000..ef23c71 Binary files /dev/null and b/parfum-shop/public/weisse-asche-product-image.webp differ diff --git a/parfum-shop/public/weisse-asche-product-sample-image.png b/parfum-shop/public/weisse-asche-product-sample-image.png deleted file mode 100755 index 4210ffc..0000000 Binary files a/parfum-shop/public/weisse-asche-product-sample-image.png and /dev/null differ diff --git a/parfum-shop/public/weisse-asche-product-sample-image.webp b/parfum-shop/public/weisse-asche-product-sample-image.webp new file mode 100644 index 0000000..68a0ab9 Binary files /dev/null and b/parfum-shop/public/weisse-asche-product-sample-image.webp differ diff --git a/parfum-shop/src/App.jsx b/parfum-shop/src/App.jsx index 6532791..575a3a0 100644 --- a/parfum-shop/src/App.jsx +++ b/parfum-shop/src/App.jsx @@ -59,6 +59,10 @@ function App() { + + Zum Inhalt springen + +
} /> diff --git a/parfum-shop/src/components/Footer.css b/parfum-shop/src/components/Footer.css index ed62013..956edae 100644 --- a/parfum-shop/src/components/Footer.css +++ b/parfum-shop/src/components/Footer.css @@ -4,9 +4,9 @@ overflow: hidden; background: radial-gradient(circle at 82% 10%, rgba(var(--theme-accent-rgb) / 0.15), transparent 22rem), - #171717; - color: #f5f5f5; - border-top: 1px solid rgba(255, 255, 255, 0.08); + var(--footer-bg); + color: var(--footer-text); + border-top: 1px solid var(--footer-border); } .site-footer--flush { @@ -18,7 +18,7 @@ position: absolute; right: var(--page-x); bottom: -0.16em; - color: rgba(255, 255, 255, 0.035); + color: var(--footer-watermark); font-size: clamp(5.5rem, 18vw, 20rem); line-height: 0.8; letter-spacing: 0; @@ -54,7 +54,7 @@ .site-footer__text { max-width: 32rem; margin: 0; - color: rgba(255, 255, 255, 0.7); + color: var(--footer-text-muted); font-size: var(--text-base); line-height: 1.65; } @@ -67,7 +67,7 @@ } .site-footer__heading { - color: rgba(255, 255, 255, 0.52); + color: var(--footer-text-faint); font-size: var(--text-xs); letter-spacing: 0.22em; text-transform: uppercase; @@ -81,7 +81,7 @@ .site-footer__nav a { width: fit-content; - color: #f5f5f5; + color: var(--footer-text); font-size: var(--text-sm); line-height: 1.2; text-decoration: none; diff --git a/parfum-shop/src/components/ProductDetailPage.css b/parfum-shop/src/components/ProductDetailPage.css index 14b0189..d600caa 100644 --- a/parfum-shop/src/components/ProductDetailPage.css +++ b/parfum-shop/src/components/ProductDetailPage.css @@ -94,7 +94,7 @@ height: auto; object-fit: contain; display: block; - filter: drop-shadow(0 34px 72px rgba(0, 0, 0, 0.42)); + filter: var(--shadow-product); } .is-transition-arriving .product-hero-image, @@ -250,46 +250,75 @@ } .size-card { - min-height: 112px; - padding: clamp(0.85rem, 1.4vw, 1.05rem); + position: relative; + min-height: 116px; + display: flex; + flex-direction: column; + gap: 0.35rem; + padding: clamp(0.95rem, 1.6vw, 1.2rem); border: 1px solid var(--theme-border); - background: var(--theme-paper); + background: transparent; color: var(--theme-text); text-align: left; cursor: pointer; + isolation: isolate; transition: - transform var(--duration-med) var(--ease-out), border-color var(--duration-med) var(--ease-out), background-color var(--duration-med) var(--ease-out), - box-shadow var(--duration-med) var(--ease-out); + color var(--duration-med) var(--ease-out); +} + +/* Top accent line — appears on hover, stays on active. */ +.size-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: var(--theme-accent); + transform: scaleX(0); + transform-origin: left center; + transition: transform var(--duration-med) var(--ease-out); } .size-card:hover, .size-card:focus-visible { - transform: translateY(-2px); - border-color: rgba(var(--theme-accent-rgb) / 0.58); - box-shadow: var(--theme-shadow-soft); + border-color: var(--theme-border-strong); +} + +.size-card:hover::before, +.size-card:focus-visible::before { + transform: scaleX(0.45); } .size-card.active { border-color: var(--theme-accent); - background: - linear-gradient(135deg, rgba(var(--theme-accent-rgb) / 0.12), transparent 62%), - var(--theme-paper); + background: rgba(var(--theme-accent-rgb) / 0.06); +} + +.size-card.active::before { + transform: scaleX(1); } .size-title { display: block; - margin-bottom: 0.65rem; - color: var(--theme-text); - font-size: var(--text-sm); + color: var(--theme-text-muted); + font-size: var(--text-xs); + letter-spacing: 0.18em; + text-transform: uppercase; + transition: color var(--duration-med) var(--ease-out); +} + +.size-card.active .size-title { + color: var(--theme-accent); } .size-card strong { display: block; - margin-bottom: 0.35rem; + margin-top: auto; color: var(--theme-text); - font-size: clamp(1.05rem, 1.4vw, 1.38rem); + font-size: clamp(1.1rem, 1.55vw, 1.45rem); font-weight: 400; letter-spacing: 0; line-height: 1.05; @@ -931,7 +960,7 @@ width: min(86%, 430px); height: auto; object-fit: contain; - filter: drop-shadow(0 28px 58px rgba(0, 0, 0, 0.34)); + filter: var(--shadow-product-card); transition: transform var(--duration-slow) var(--ease-out); } @@ -1170,18 +1199,18 @@ } .size-card { - min-height: 62px; - padding: 0.55rem 0.6rem; + min-height: 70px; + gap: 0.2rem; + padding: 0.6rem 0.7rem; } .size-title { - margin-bottom: 0.25rem; - font-size: 0.76rem; + font-size: 0.66rem; + letter-spacing: 0.14em; } .size-card strong { - margin-bottom: 0; - font-size: 0.95rem; + font-size: 0.98rem; } .size-card small, diff --git a/parfum-shop/src/components/ProductDetailPage.jsx b/parfum-shop/src/components/ProductDetailPage.jsx index ac5e582..64a8fdc 100644 --- a/parfum-shop/src/components/ProductDetailPage.jsx +++ b/parfum-shop/src/components/ProductDetailPage.jsx @@ -2,12 +2,53 @@ import { useEffect, useMemo, useState } from "react"; import { Link, useParams } from "react-router"; import perfumes from "../data/perfumes"; import SharedNavbar from "./SharedNavbar"; +import PageMeta from "./seo/PageMeta"; import { useProductTransition } from "../transitions/ProductTransitionContext"; -import { formatChf } from "../shop/money"; import { useShop } from "../shop/useShop"; +import { formatChf } from "../shop/money"; import "./ProductDetailPage.css"; -const STORY_PANEL_IMAGE = "/placeholder-character-panel.jpg"; +// TODO: replace with the real production origin once available. +const SITE_ORIGIN = "https://atmos.example"; + +function buildProductJsonLd(perfume) { + const priceMatch = String(perfume.prices?.full || "").match(/(\d+)/); + const price = priceMatch ? Number(priceMatch[1]) : undefined; + const reviews = perfume.reviews; + + const data = { + "@context": "https://schema.org", + "@type": "Product", + name: perfume.name, + sku: `${perfume.id}-${perfume.slug}`, + description: `${perfume.text} ${perfume.mood}`.trim(), + image: `${SITE_ORIGIN}${perfume.image}`, + brand: { "@type": "Brand", name: "atmos" }, + category: "Parfum", + }; + + if (price !== undefined) { + data.offers = { + "@type": "Offer", + priceCurrency: "CHF", + price, + availability: "https://schema.org/InStock", + url: `${SITE_ORIGIN}/duft/${perfume.slug}`, + }; + } + + if (reviews?.score && reviews?.total) { + data.aggregateRating = { + "@type": "AggregateRating", + ratingValue: reviews.score, + reviewCount: reviews.total, + bestRating: 5, + worstRating: 1, + }; + } + + return data; +} const priceToCents = (price) => { const match = String(price).match(/(\d+)/); @@ -24,6 +65,8 @@ const getSampleImage = (perfume) => const getImageForSize = (perfume, size) => size === "sample" ? getSampleImage(perfume) : getFullSizeImage(perfume); +const STORY_PANEL_IMAGE = "/placeholder-character-panel.jpg"; + function ProductPurchasePanel({ perfume, selectedSize, @@ -144,10 +187,12 @@ function ProductHero({
{perfume.name}
@@ -218,7 +263,7 @@ function ProductStorySection({ perfume }) { @@ -545,11 +590,23 @@ function ProductDetailContent({ perfumeSlug }) { return () => window.clearInterval(interval); }, [safeCommentPages.length]); + const productJsonLd = useMemo(() => buildProductJsonLd(perfume), [perfume]); + return (
+ +