lgtm
This commit is contained in:
parent
11711c57e6
commit
81a16f543c
21 changed files with 29072 additions and 1913 deletions
|
|
@ -16,10 +16,19 @@ const HOME_SECTION_HEADING_CLASS =
|
|||
const HOME_BODY_CLASS = 'text-base leading-relaxed text-warm-600 dark:text-warm-400';
|
||||
const HOME_PRIMARY_BUTTON_CLASS =
|
||||
'bg-coral-500 text-white rounded-lg font-semibold hover:bg-coral-600 transition-colors text-base shadow-lg shadow-coral-500/25 text-center';
|
||||
const PRODUCT_DEMO_VIDEO_SRC = '/video/recording.mp4';
|
||||
const PRODUCT_DEMO_POSTER_SRC = '/video/recording.jpg';
|
||||
const PRODUCT_DEMO_VIDEO_BY_LANGUAGE: Record<string, string> = {
|
||||
en: 'recording',
|
||||
de: 'recording-de',
|
||||
zh: 'recording-zh',
|
||||
hi: 'recording-hi',
|
||||
};
|
||||
const PRODUCT_DEMO_SECTION_ID = 'product-demo-video';
|
||||
|
||||
function getProductDemoSlug(language: string | undefined): string {
|
||||
const code = language?.toLowerCase().split('-')[0] ?? 'en';
|
||||
return PRODUCT_DEMO_VIDEO_BY_LANGUAGE[code] ?? PRODUCT_DEMO_VIDEO_BY_LANGUAGE.en;
|
||||
}
|
||||
|
||||
function highlightBrandText(text: string) {
|
||||
const parts = text.split(BRAND_NAME);
|
||||
if (parts.length === 1) return text;
|
||||
|
|
@ -37,11 +46,26 @@ function highlightBrandText(text: string) {
|
|||
}
|
||||
|
||||
function ProductDemoVideo() {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const sectionRef = useRef<HTMLDivElement | null>(null);
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const currentVideoSrcRef = useRef<string | null>(null);
|
||||
const [shouldLoadVideo, setShouldLoadVideo] = useState(false);
|
||||
const [isVideoPlaying, setIsVideoPlaying] = useState(false);
|
||||
const productDemoSlug = getProductDemoSlug(i18n.language);
|
||||
const productDemoVideoSrc = `/video/${productDemoSlug}.mp4`;
|
||||
const productDemoPosterSrc = `/video/${productDemoSlug}.jpg`;
|
||||
|
||||
useEffect(() => {
|
||||
if (currentVideoSrcRef.current === productDemoVideoSrc) return;
|
||||
currentVideoSrcRef.current = productDemoVideoSrc;
|
||||
setIsVideoPlaying(false);
|
||||
|
||||
const video = videoRef.current;
|
||||
if (!video || !shouldLoadVideo) return;
|
||||
video.pause();
|
||||
video.load();
|
||||
}, [productDemoVideoSrc, shouldLoadVideo]);
|
||||
|
||||
useEffect(() => {
|
||||
const section = sectionRef.current;
|
||||
|
|
@ -70,8 +94,8 @@ function ProductDemoVideo() {
|
|||
setShouldLoadVideo(true);
|
||||
if (!video) return;
|
||||
|
||||
if (!video.getAttribute('src')) {
|
||||
video.src = PRODUCT_DEMO_VIDEO_SRC;
|
||||
if (video.getAttribute('src') !== productDemoVideoSrc) {
|
||||
video.src = productDemoVideoSrc;
|
||||
video.load();
|
||||
}
|
||||
|
||||
|
|
@ -86,11 +110,14 @@ function ProductDemoVideo() {
|
|||
ref={sectionRef}
|
||||
className={`${HOME_SECTION_CONTAINER_CLASS} pt-8 md:pt-12 pb-2`}
|
||||
>
|
||||
<h2 className={`${HOME_SECTION_HEADING_CLASS} mb-5 text-center`}>
|
||||
{t('home.productDemoLabel')}
|
||||
</h2>
|
||||
<div className="relative overflow-hidden rounded-lg border border-warm-200 bg-navy-950 shadow-sm dark:border-warm-700">
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={shouldLoadVideo ? PRODUCT_DEMO_VIDEO_SRC : undefined}
|
||||
poster={PRODUCT_DEMO_POSTER_SRC}
|
||||
src={shouldLoadVideo ? productDemoVideoSrc : undefined}
|
||||
poster={productDemoPosterSrc}
|
||||
controls
|
||||
playsInline
|
||||
preload={shouldLoadVideo ? 'metadata' : 'none'}
|
||||
|
|
@ -349,6 +376,40 @@ export default function HomePage({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Street-level detail */}
|
||||
<div className={`${HOME_SECTION_CONTAINER_CLASS} pt-10 md:pt-16 pb-2`}>
|
||||
<div className="grid gap-6 md:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] md:items-start">
|
||||
<div>
|
||||
<h2 className={`${HOME_SECTION_HEADING_CLASS} mb-4`}>{t('home.streetTitle')}</h2>
|
||||
<p className={`${HOME_BODY_CLASS} max-w-2xl`}>{t('home.streetIntro')}</p>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{[
|
||||
{
|
||||
title: t('home.streetCard1Title'),
|
||||
body: t('home.streetCard1Body'),
|
||||
},
|
||||
{
|
||||
title: t('home.streetCard2Title'),
|
||||
body: t('home.streetCard2Body'),
|
||||
},
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="rounded-lg border border-warm-200 bg-white/90 p-5 shadow-sm dark:border-warm-700 dark:bg-warm-800/90"
|
||||
>
|
||||
<h3 className="text-base font-bold text-navy-950 dark:text-warm-100">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-relaxed text-warm-600 dark:text-warm-400">
|
||||
{item.body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comparison table */}
|
||||
<div
|
||||
id="how-it-works"
|
||||
|
|
@ -424,16 +485,25 @@ export default function HomePage({
|
|||
</div>
|
||||
)}
|
||||
</td>
|
||||
{[row.postcode, row.guides].map((has, j) => (
|
||||
<td
|
||||
key={j}
|
||||
className={`px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base ${has ? 'text-green-500' : 'text-red-500'}`}
|
||||
>
|
||||
{has ? '\u2713' : '\u2717'}
|
||||
</td>
|
||||
))}
|
||||
<td className="px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base text-green-500 bg-teal-50 dark:bg-teal-900/30">
|
||||
✓
|
||||
{[row.postcode, row.guides].map((has, j) => {
|
||||
const statusLabel = has ? 'Yes' : 'No';
|
||||
return (
|
||||
<td
|
||||
key={j}
|
||||
aria-label={statusLabel}
|
||||
className={`px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base ${has ? 'text-green-500' : 'text-red-500'}`}
|
||||
>
|
||||
<span aria-hidden="true">{has ? '\u2713' : '\u2717'}</span>
|
||||
<span className="sr-only">{statusLabel}</span>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
<td
|
||||
aria-label="Yes"
|
||||
className="px-1.5 md:px-3 py-2.5 md:py-3.5 text-center text-base text-green-500 bg-teal-50 dark:bg-teal-900/30"
|
||||
>
|
||||
<span aria-hidden="true">✓</span>
|
||||
<span className="sr-only">Yes</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue