Drop Cookies Settings from the footer, link the Home features that already have pages, and give leftover routes their own descriptions.
227 lines
8.4 KiB
TypeScript
227 lines
8.4 KiB
TypeScript
"use client";
|
|
|
|
import { memo } from "react";
|
|
import Image from "next/image";
|
|
import QuoteDecor from "./QuoteDecor";
|
|
import QuoteStatementDecor from "./QuoteStatementDecor";
|
|
import type { QuoteBlockViewProps } from "./QuoteBlock.types";
|
|
|
|
function QuoteBlockView({
|
|
className,
|
|
quote,
|
|
quoteSecondary,
|
|
statementStacked,
|
|
author,
|
|
source,
|
|
quoteId,
|
|
authorId,
|
|
config,
|
|
imageError,
|
|
currentAvatarSrc,
|
|
onImageError,
|
|
}: QuoteBlockViewProps) {
|
|
if (config.citationLayout) {
|
|
return (
|
|
<section
|
|
data-figma-node="23983-53436"
|
|
className={`${config.container} ${className}`.trim()}
|
|
aria-labelledby={quoteId}
|
|
>
|
|
<figure className="relative mx-auto w-full max-w-[1216px] overflow-hidden rounded-[24px] bg-[var(--color-surface-invert-brand-lavender)] px-[var(--space-600)] py-[var(--space-1000)] md:px-[var(--space-1200)] md:py-[var(--space-1800)]">
|
|
<div
|
|
className="pointer-events-none absolute -right-10 -top-16 size-[280px] rounded-full bg-white/25"
|
|
aria-hidden
|
|
/>
|
|
<div
|
|
className="pointer-events-none absolute -bottom-20 right-16 size-[320px] rounded-full bg-white/20"
|
|
aria-hidden
|
|
/>
|
|
<blockquote id={quoteId} className="relative">
|
|
<p
|
|
className={`font-bricolage-grotesque font-medium text-[var(--color-content-invert-primary)] ${config.quote}`}
|
|
>
|
|
“{quote}”
|
|
</p>
|
|
</blockquote>
|
|
<figcaption className="relative mt-10 flex flex-col gap-1">
|
|
<cite
|
|
id={authorId}
|
|
className={`font-inter not-italic uppercase text-[var(--color-content-invert-primary)] ${config.author}`}
|
|
>
|
|
{author}
|
|
</cite>
|
|
{source ? (
|
|
<p
|
|
className={`font-inter uppercase text-[var(--color-content-invert-primary)] ${config.source}`}
|
|
>
|
|
{source}
|
|
</p>
|
|
) : null}
|
|
</figcaption>
|
|
</figure>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
if (config.statementLayout) {
|
|
const statementTextClass =
|
|
"font-bricolage-grotesque text-medium-heading text-[var(--color-surface-default-tertiary)] md:text-xx-large-heading";
|
|
|
|
return (
|
|
<section
|
|
data-figma-node="21967-24638"
|
|
className={`${config.container} ${className}`.trim()}
|
|
aria-labelledby={quoteId}
|
|
role="region"
|
|
>
|
|
<div
|
|
className="relative box-border flex w-full max-w-[1440px] shrink-0 flex-col items-center justify-center gap-[var(--space-800)] overflow-hidden rounded-[var(--space-500)] bg-[var(--color-surface-invert-brand-primary)] px-[var(--space-800)] py-[var(--space-1200)] md:px-[var(--space-1800)] md:py-[var(--space-2400)]"
|
|
>
|
|
<QuoteStatementDecor />
|
|
<div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center text-center">
|
|
<p
|
|
id={quoteId}
|
|
className={
|
|
statementStacked
|
|
? `${statementTextClass} mb-0 flex w-full min-w-0 flex-col gap-[1lh] text-center`
|
|
: `${statementTextClass} mb-0 flex w-full min-w-0 flex-col gap-9 text-center md:gap-[length:var(--text-xx-large-heading--line-height)] lg:block lg:gap-0`
|
|
}
|
|
>
|
|
<span className={statementStacked ? "block" : "block lg:inline"}>
|
|
{quote}
|
|
</span>
|
|
{quoteSecondary ? (
|
|
<>
|
|
{statementStacked ? (
|
|
" "
|
|
) : (
|
|
<span className="hidden lg:inline">{" "}</span>
|
|
)}
|
|
<span className={statementStacked ? "block" : "block lg:inline"}>
|
|
{quoteSecondary}
|
|
</span>
|
|
</>
|
|
) : null}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section
|
|
className={`${config.container} ${className}`}
|
|
aria-labelledby={quoteId}
|
|
role="region"
|
|
>
|
|
<div
|
|
className={`${config.card} bg-[var(--color-surface-invert-brand-secondary)] relative overflow-hidden`}
|
|
>
|
|
{/* Background with noise texture */}
|
|
<div
|
|
className="absolute inset-0 bg-[var(--color-surface-invert-brand-secondary)]"
|
|
style={{
|
|
filter:
|
|
'url(\'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><defs><filter id="grain" filterUnits="objectBoundingBox" x="0" y="0" width="1" height="1" colorInterpolationFilters="sRGB"><feTurbulence type="fractalNoise" baseFrequency="0.4" numOctaves="3" seed="7" stitchTiles="stitch" result="noise"/><feColorMatrix in="noise" result="softNoise" type="matrix" values="0.8 0 0 0 0.3 0 0.6 0 0 0.2 0 0 1.0 0 0.4 0 0 0 0.25 0"/><feComposite in="softNoise" in2="SourceAlpha" operator="in" result="maskedNoise"/><feBlend in="SourceGraphic" in2="maskedNoise" mode="multiply"/></filter></defs></svg>#grain\')',
|
|
}}
|
|
/>
|
|
|
|
{/* DECORATIONS (behind content) */}
|
|
{config.showDecor && (
|
|
<QuoteDecor
|
|
className="pointer-events-none absolute z-0
|
|
left-0 top-0
|
|
w-full h-full"
|
|
aria-hidden="true"
|
|
/>
|
|
)}
|
|
|
|
<div className={`flex flex-col ${config.gap} relative z-10`}>
|
|
<div className={`flex flex-col ${config.avatarGap}`}>
|
|
<div className="relative">
|
|
{!imageError ? (
|
|
<Image
|
|
src={currentAvatarSrc}
|
|
alt=""
|
|
width={64}
|
|
height={64}
|
|
className={`filter sepia ${config.avatar}`}
|
|
loading="eager"
|
|
priority
|
|
onError={onImageError}
|
|
/>
|
|
) : (
|
|
<div
|
|
className={`flex items-center justify-center bg-[var(--color-gray-300)] rounded-full ${config.avatar} text-[var(--color-gray-600)] font-bold`}
|
|
>
|
|
<span className="text-sm md:text-base lg:text-lg xl:text-xl">
|
|
{author
|
|
.split(" ")
|
|
.map((n) => n[0])
|
|
.join("")
|
|
.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<blockquote
|
|
id={quoteId}
|
|
aria-labelledby={authorId}
|
|
className="relative"
|
|
>
|
|
<p
|
|
data-qopen="“"
|
|
data-qclose="”"
|
|
className={[
|
|
"font-bricolage-grotesque font-normal",
|
|
config.quote,
|
|
"text-[var(--color-content-invert-primary)]",
|
|
// give space for the hanging open-quote so it's not clipped:
|
|
"pl-[0.6em] -indent-[0.6em]",
|
|
// inject quotes
|
|
"relative before:content-[attr(data-qopen)] after:content-[attr(data-qclose)]",
|
|
// lock quote glyphs to your display face
|
|
"before:[font-family:var(--font-bricolage-grotesque)]",
|
|
"after:[font-family:var(--font-bricolage-grotesque)]",
|
|
].join(" ")}
|
|
>
|
|
{quote}
|
|
</p>
|
|
</blockquote>
|
|
</div>
|
|
<footer className="flex flex-col gap-[var(--space-200)] md:gap-[var(--space-300)] xl:gap-[var(--space-500)]">
|
|
<cite
|
|
id={authorId}
|
|
className={`font-normal ${config.author} text-[var(--color-content-invert-primary)] uppercase not-italic`}
|
|
>
|
|
{author}
|
|
</cite>
|
|
{source && (
|
|
<p
|
|
data-qopen="“"
|
|
data-qclose="”"
|
|
className={[
|
|
"font-normal",
|
|
config.source,
|
|
"text-[var(--color-content-invert-primary)] uppercase",
|
|
"pl-[0.6em] -indent-[0.6em]",
|
|
"relative before:content-[attr(data-qopen)] after:content-[attr(data-qclose)]",
|
|
"before:[font-family:var(--font-inter)] after:[font-family:var(--font-inter)]",
|
|
].join(" ")}
|
|
>
|
|
{source}
|
|
</p>
|
|
)}
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
QuoteBlockView.displayName = "QuoteBlockView";
|
|
|
|
export default memo(QuoteBlockView);
|