fix: consent mode v2 compliance + visual enhancements across sections
Some checks failed
Build & Push / build-and-push (push) Failing after 52s
Some checks failed
Build & Push / build-and-push (push) Failing after 52s
Google Consent Mode v2: region-specific defaults (granted globally, denied for EEA/UK), update all 4 consent types on accept/decline, add url_passthrough and ads_data_redaction for better measurement. Visual: unified 48px grid texture across all light sections, animated constellation SVG in Process section, radial glow on Philosophy, removed broken SVG connector lines and unused imports. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,3 +31,6 @@ public/media/
|
||||
|
||||
# superpowers
|
||||
.superpowers/
|
||||
|
||||
# private credentials
|
||||
private/
|
||||
|
||||
27
package-lock.json
generated
27
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@calcom/embed-react": "^1.5.3",
|
||||
"@google/genai": "^1.46.0",
|
||||
"@google/genai": "^1.48.0",
|
||||
"@payloadcms/db-postgres": "^3.80.0",
|
||||
"@payloadcms/next": "^3.80.0",
|
||||
"@payloadcms/richtext-lexical": "^3.80.0",
|
||||
@@ -1435,9 +1435,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@google/genai": {
|
||||
"version": "1.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.46.0.tgz",
|
||||
"integrity": "sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==",
|
||||
"version": "1.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.48.0.tgz",
|
||||
"integrity": "sha512-plonYK4ML2PrxsRD9SeqmFt76eREWkQdPCglOA6aYDzL1AAbE+7PUnT54SvpWGfws13L0AZEqGSpL7+1IPnTxQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"google-auth-library": "^10.3.0",
|
||||
@@ -3633,14 +3633,6 @@
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
@@ -6714,17 +6706,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||
"version": "0.5.20",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.20.tgz",
|
||||
"integrity": "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/embed-react": "^1.5.3",
|
||||
"@google/genai": "^1.46.0",
|
||||
"@google/genai": "^1.48.0",
|
||||
"@payloadcms/db-postgres": "^3.80.0",
|
||||
"@payloadcms/next": "^3.80.0",
|
||||
"@payloadcms/richtext-lexical": "^3.80.0",
|
||||
|
||||
@@ -10,6 +10,9 @@ function updateConsent(state: ConsentState) {
|
||||
if (typeof window !== 'undefined' && typeof window.gtag === 'function') {
|
||||
window.gtag('consent', 'update', {
|
||||
analytics_storage: state,
|
||||
ad_storage: state,
|
||||
ad_user_data: state,
|
||||
ad_personalization: state,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,33 @@ export default function GoogleAnalytics() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Consent Mode v2 — default to denied for EEA compliance */}
|
||||
{/* Consent Mode v2 — region-specific defaults */}
|
||||
<Script id="gtag-consent" strategy="beforeInteractive">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
|
||||
// Default: granted for regions without consent requirements
|
||||
gtag('consent', 'default', {
|
||||
analytics_storage: 'granted',
|
||||
ad_storage: 'granted',
|
||||
ad_user_data: 'granted',
|
||||
ad_personalization: 'granted',
|
||||
});
|
||||
|
||||
// EEA + UK: denied until user consents (GDPR)
|
||||
gtag('consent', 'default', {
|
||||
analytics_storage: 'denied',
|
||||
ad_storage: 'denied',
|
||||
ad_user_data: 'denied',
|
||||
ad_personalization: 'denied',
|
||||
wait_for_update: 500,
|
||||
region: ['AT','BE','BG','CY','CZ','DE','DK','EE','ES','FI','FR','GR','HR','HU','IE','IT','LT','LU','LV','MT','NL','PL','PT','RO','SE','SI','SK','IS','LI','NO','GB'],
|
||||
});
|
||||
|
||||
// Improve measurement when consent is denied
|
||||
gtag('set', 'url_passthrough', true);
|
||||
gtag('set', 'ads_data_redaction', true);
|
||||
`}
|
||||
</Script>
|
||||
|
||||
|
||||
@@ -36,7 +36,16 @@ export default function Configurator() {
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="configure" className="relative bg-surface py-24 overflow-hidden">
|
||||
<section
|
||||
id="configure"
|
||||
className="relative bg-surface py-24 overflow-hidden"
|
||||
style={{
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
].join(', '),
|
||||
}}
|
||||
>
|
||||
{/* Subtle diagonal accent line */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-px pointer-events-none"
|
||||
|
||||
@@ -57,7 +57,16 @@ export default function Discovery() {
|
||||
if (!voiceSupported) return null;
|
||||
|
||||
return (
|
||||
<section id="discover" className="relative bg-surface-high py-24 overflow-hidden">
|
||||
<section
|
||||
id="discover"
|
||||
className="relative bg-surface-high py-24 overflow-hidden"
|
||||
style={{
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
].join(', '),
|
||||
}}
|
||||
>
|
||||
{/* Top accent line */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-px pointer-events-none"
|
||||
|
||||
@@ -131,7 +131,24 @@ export default function Philosophy() {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<section id="about" className="bg-surface py-20">
|
||||
<section
|
||||
id="about"
|
||||
className="relative bg-surface py-20 overflow-hidden"
|
||||
style={{
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
].join(', '),
|
||||
}}
|
||||
>
|
||||
{/* Ambient radial glow */}
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{
|
||||
background: 'radial-gradient(ellipse 80% 60% at 20% 50%, rgba(91,164,217,0.04) 0%, transparent 70%)',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-start">
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import { motion, type Variants } from 'framer-motion';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { Search, LayoutDashboard, PenTool, Rocket } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
staggerContainerWide,
|
||||
@@ -17,16 +15,15 @@ import SectionHeader from '@/components/ui/SectionHeader';
|
||||
interface Step {
|
||||
numeral: string;
|
||||
key: 'discovery' | 'strategy' | 'build' | 'launch';
|
||||
Icon: LucideIcon;
|
||||
}
|
||||
|
||||
// ─── Data ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
const STEPS: Step[] = [
|
||||
{ numeral: '01', key: 'discovery', Icon: Search },
|
||||
{ numeral: '02', key: 'strategy', Icon: LayoutDashboard },
|
||||
{ numeral: '03', key: 'build', Icon: PenTool },
|
||||
{ numeral: '04', key: 'launch', Icon: Rocket },
|
||||
{ numeral: '01', key: 'discovery' },
|
||||
{ numeral: '02', key: 'strategy' },
|
||||
{ numeral: '03', key: 'build' },
|
||||
{ numeral: '04', key: 'launch' },
|
||||
];
|
||||
|
||||
// ─── Variants ─────────────────────────────────────────────────────────────────
|
||||
@@ -44,9 +41,134 @@ const numeralScaleVariants: Variants = {
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Animated blueprint network (desktop only) ───────────────────────────────
|
||||
|
||||
function FlowNetwork() {
|
||||
// Radial constellation — center hub with curved spokes to outer nodes
|
||||
const cx = 130, cy = 100; // center
|
||||
const nodes = [
|
||||
{ x: 50, y: 30 }, // top-left
|
||||
{ x: 210, y: 25 }, // top-right
|
||||
{ x: 240, y: 110 }, // right
|
||||
{ x: 195, y: 180 }, // bottom-right
|
||||
{ x: 55, y: 170 }, // bottom-left
|
||||
{ x: 20, y: 95 }, // left
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="hidden lg:block relative mt-8 w-full h-52" aria-hidden="true">
|
||||
<style>{`
|
||||
@keyframes flow-dash { to { stroke-dashoffset: -40; } }
|
||||
@keyframes flow-dash-slow { to { stroke-dashoffset: -40; } }
|
||||
@keyframes node-pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
@keyframes orbit-a {
|
||||
from { transform: rotate(0deg) translateX(28px) rotate(0deg); }
|
||||
to { transform: rotate(360deg) translateX(28px) rotate(-360deg); }
|
||||
}
|
||||
@keyframes orbit-b {
|
||||
from { transform: rotate(120deg) translateX(22px) rotate(-120deg); }
|
||||
to { transform: rotate(480deg) translateX(22px) rotate(-480deg); }
|
||||
}
|
||||
@keyframes orbit-c {
|
||||
from { transform: rotate(240deg) translateX(35px) rotate(-240deg); }
|
||||
to { transform: rotate(-120deg) translateX(35px) rotate(120deg); }
|
||||
}
|
||||
@keyframes ring-breathe {
|
||||
0%, 100% { opacity: 0.06; }
|
||||
50% { opacity: 0.14; }
|
||||
}
|
||||
.process-flow { animation: flow-dash 2.2s linear infinite; }
|
||||
.process-flow-slow { animation: flow-dash-slow 3.5s linear infinite; }
|
||||
.process-pulse { animation: node-pulse 2.8s ease-in-out infinite; }
|
||||
.process-orbit-a { animation: orbit-a 8s linear infinite; }
|
||||
.process-orbit-b { animation: orbit-b 11s linear infinite; }
|
||||
.process-orbit-c { animation: orbit-c 14s linear infinite; }
|
||||
.process-ring { animation: ring-breathe 4s ease-in-out infinite; }
|
||||
.process-ring-lg { animation: ring-breathe 5.5s ease-in-out infinite; }
|
||||
`}</style>
|
||||
<svg className="w-full h-full" viewBox="0 0 260 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* Breathing rings */}
|
||||
<circle cx={cx} cy={cy} r="24" stroke="rgba(91,164,217,0.1)" strokeWidth="1" fill="none" className="process-ring" />
|
||||
<circle cx={cx} cy={cy} r="42" stroke="rgba(91,164,217,0.07)" strokeWidth="1" fill="none" className="process-ring-lg" />
|
||||
<circle cx={cx} cy={cy} r="65" stroke="rgba(91,164,217,0.04)" strokeWidth="0.8" fill="none" strokeDasharray="3 6" />
|
||||
|
||||
{/* Curved spokes from center to each outer node */}
|
||||
{nodes.map((n, i) => {
|
||||
const mx = cx + (n.x - cx) * 0.5 + (i % 2 === 0 ? 15 : -15);
|
||||
const my = cy + (n.y - cy) * 0.5 + (i % 2 === 0 ? -12 : 12);
|
||||
return (
|
||||
<path
|
||||
key={i}
|
||||
d={`M ${cx} ${cy} Q ${mx} ${my} ${n.x} ${n.y}`}
|
||||
stroke="rgba(91,164,217,0.18)"
|
||||
strokeWidth="1.2"
|
||||
strokeDasharray="5 4"
|
||||
strokeLinecap="round"
|
||||
className={i % 2 === 0 ? 'process-flow' : 'process-flow-slow'}
|
||||
style={{ animationDelay: `${i * 0.3}s` }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Faint arcs connecting adjacent outer nodes */}
|
||||
{nodes.map((n, i) => {
|
||||
const next = nodes[(i + 1) % nodes.length];
|
||||
const mx = cx + (n.x + next.x - 2 * cx) * 0.15;
|
||||
const my = cy + (n.y + next.y - 2 * cy) * 0.15;
|
||||
return (
|
||||
<path
|
||||
key={`arc-${i}`}
|
||||
d={`M ${n.x} ${n.y} Q ${mx} ${my} ${next.x} ${next.y}`}
|
||||
stroke="rgba(91,164,217,0.08)"
|
||||
strokeWidth="0.8"
|
||||
strokeDasharray="3 5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Outer nodes — pulsing */}
|
||||
{nodes.map((n, i) => (
|
||||
<circle
|
||||
key={`node-${i}`}
|
||||
cx={n.x}
|
||||
cy={n.y}
|
||||
r="3"
|
||||
fill="#5BA4D9"
|
||||
className="process-pulse"
|
||||
style={{ animationDelay: `${i * 0.45}s` }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Center hub */}
|
||||
<circle cx={cx} cy={cy} r="5" fill="#5BA4D9" opacity="0.85" />
|
||||
<circle cx={cx} cy={cy} r="2" fill="#fff" opacity="0.9" />
|
||||
|
||||
{/* Orbiting particles */}
|
||||
<g style={{ transformOrigin: `${cx}px ${cy}px` }}>
|
||||
<circle cx={cx} cy={cy} r="2" fill="#5BA4D9" opacity="0.7" className="process-orbit-a" />
|
||||
</g>
|
||||
<g style={{ transformOrigin: `${cx}px ${cy}px` }}>
|
||||
<circle cx={cx} cy={cy} r="1.5" fill="rgba(91,164,217,0.5)" className="process-orbit-b" />
|
||||
</g>
|
||||
<g style={{ transformOrigin: `${cx}px ${cy}px` }}>
|
||||
<circle cx={cx} cy={cy} r="1.5" fill="rgba(91,164,217,0.35)" className="process-orbit-c" />
|
||||
</g>
|
||||
|
||||
{/* Corner brackets — architectural detail */}
|
||||
<path d="M 14 25 L 14 20 L 19 20" stroke="rgba(28,43,58,0.12)" strokeWidth="0.8" fill="none" />
|
||||
<path d="M 246 175 L 246 180 L 241 180" stroke="rgba(28,43,58,0.12)" strokeWidth="0.8" fill="none" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Sub-components ───────────────────────────────────────────────────────────
|
||||
|
||||
function StepCard({ numeral, stepKey, Icon }: { numeral: string; stepKey: string; Icon: LucideIcon }) {
|
||||
function StepCard({ numeral, stepKey }: { numeral: string; stepKey: string }) {
|
||||
const t = useTranslations();
|
||||
const title = t(`process.steps.${stepKey}.title`);
|
||||
const description = t(`process.steps.${stepKey}.description`);
|
||||
@@ -94,7 +216,16 @@ export default function Process() {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<section id="process" className="bg-surface-low py-20">
|
||||
<section
|
||||
id="process"
|
||||
className="relative bg-surface py-20"
|
||||
style={{
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
].join(', '),
|
||||
}}
|
||||
>
|
||||
<div className="container mx-auto px-6">
|
||||
|
||||
{/*
|
||||
@@ -121,59 +252,22 @@ export default function Process() {
|
||||
align="left"
|
||||
/>
|
||||
</div>
|
||||
<FlowNetwork />
|
||||
</div>
|
||||
|
||||
{/* ── Steps column ── */}
|
||||
<div className="lg:col-span-3">
|
||||
<div className="relative">
|
||||
{/* Dashed connector line — visible on sm+ grid layouts only */}
|
||||
<div
|
||||
className="hidden sm:block absolute inset-0 pointer-events-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="w-full h-full"
|
||||
preserveAspectRatio="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{/* Horizontal dashes across the middle gap */}
|
||||
<line
|
||||
x1="50%" y1="50%"
|
||||
x2="50%" y2="50%"
|
||||
stroke="rgba(91,164,217,0.18)"
|
||||
strokeWidth="1.5"
|
||||
strokeDasharray="4 6"
|
||||
/>
|
||||
{/* Vertical dashes down the centre gap */}
|
||||
<line
|
||||
x1="0%" y1="50%"
|
||||
x2="100%" y2="50%"
|
||||
stroke="rgba(91,164,217,0.18)"
|
||||
strokeWidth="1.5"
|
||||
strokeDasharray="4 6"
|
||||
/>
|
||||
<line
|
||||
x1="50%" y1="0%"
|
||||
x2="50%" y2="100%"
|
||||
stroke="rgba(91,164,217,0.18)"
|
||||
strokeWidth="1.5"
|
||||
strokeDasharray="4 6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
variants={staggerContainerWide}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-5"
|
||||
>
|
||||
{STEPS.map((step) => (
|
||||
<StepCard key={step.key} numeral={step.numeral} stepKey={step.key} Icon={step.Icon} />
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
<motion.div
|
||||
variants={staggerContainerWide}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewportOnce}
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-5"
|
||||
>
|
||||
{STEPS.map((step) => (
|
||||
<StepCard key={step.key} numeral={step.numeral} stepKey={step.key} />
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -286,7 +286,16 @@ export default function SelectedWorks() {
|
||||
const secondaryProjects = PROJECTS.filter((p) => !p.featured);
|
||||
|
||||
return (
|
||||
<section id="work" className="bg-surface-low py-20">
|
||||
<section
|
||||
id="work"
|
||||
className="bg-surface-low py-20"
|
||||
style={{
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
].join(', '),
|
||||
}}
|
||||
>
|
||||
<style>{`
|
||||
@keyframes dashed-drift {
|
||||
0% { background-position: 0 0, 100% 0, 100% 100%, 0 100%; }
|
||||
|
||||
@@ -112,6 +112,12 @@ export default function TrustBar() {
|
||||
<section
|
||||
aria-label="Trust indicators"
|
||||
className="relative bg-surface-low py-12"
|
||||
style={{
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
|
||||
].join(', '),
|
||||
}}
|
||||
>
|
||||
{/* Gradient bridge — blends hero into this section */}
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user