initial commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import Link from 'next/link';
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
type ButtonLinkProps = {
|
||||
href: string;
|
||||
children: ReactNode;
|
||||
variant?: 'primary' | 'secondary' | 'ghost';
|
||||
isExternal?: boolean;
|
||||
};
|
||||
|
||||
export function ButtonLink({href, children, variant = 'primary', isExternal = false}: ButtonLinkProps) {
|
||||
const className = `button button--${variant}`;
|
||||
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a className={className} href={href} target="_blank" rel="noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className={className} href={href}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import {ArrowRight, DoorOpen} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import {useRouter, useSearchParams} from 'next/navigation';
|
||||
import {useMemo, useState} from 'react';
|
||||
import {DemoFrame} from './DemoFrame';
|
||||
import type {DemoContent} from '@/types/content';
|
||||
|
||||
type DemoBrowserProps = {
|
||||
demos: DemoContent[];
|
||||
};
|
||||
|
||||
function isKnownDemo(slug: string | null, demos: DemoContent[]) {
|
||||
return demos.some(demo => demo.slug === slug);
|
||||
}
|
||||
|
||||
export function DemoBrowser({demos}: DemoBrowserProps) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const requestedDemo = searchParams.get('demo');
|
||||
const initialSlug = isKnownDemo(requestedDemo, demos) ? requestedDemo : demos[0]?.slug;
|
||||
const [activeSlug, setActiveSlug] = useState(initialSlug);
|
||||
|
||||
const activeDemo = useMemo(
|
||||
() => demos.find(demo => demo.slug === activeSlug) ?? demos[0],
|
||||
[activeSlug, demos]
|
||||
);
|
||||
|
||||
function selectDemo(slug: DemoContent['slug']) {
|
||||
setActiveSlug(slug);
|
||||
router.replace(`/pl/dema?demo=${slug}`, {scroll: false});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="demoBrowser">
|
||||
<aside className="demoBrowser__sidebar" aria-label="Lista dem">
|
||||
<p className="eyebrow">Wybierz demo</p>
|
||||
<div className="demoBrowser__list">
|
||||
{demos.map(demo => (
|
||||
<button
|
||||
className={demo.slug === activeDemo.slug ? 'demoBrowser__item isActive' : 'demoBrowser__item'}
|
||||
key={demo.slug}
|
||||
type="button"
|
||||
onClick={() => selectDemo(demo.slug)}
|
||||
>
|
||||
<span className="demoBrowser__thumb">
|
||||
{demo.imageUrl ? <Image src={demo.imageUrl} alt="" width={96} height={96} /> : <DoorOpen size={30} />}
|
||||
</span>
|
||||
<span>
|
||||
<strong>{demo.title}</strong>
|
||||
<small>{demo.eyebrow}</small>
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
<section className="demoBrowser__preview" aria-live="polite">
|
||||
<DemoFrame title={activeDemo.title} url={activeDemo.iframeUrl} openLabel={activeDemo.openLabel} />
|
||||
<div className="demoBrowser__details">
|
||||
<div>
|
||||
<p className="eyebrow">{activeDemo.eyebrow}</p>
|
||||
<h2>{activeDemo.title}</h2>
|
||||
<p>{activeDemo.intro}</p>
|
||||
</div>
|
||||
<Link className="button button--secondary" href={`/pl/${activeDemo.slug}`}>
|
||||
Poczytaj więcej
|
||||
<ArrowRight size={18} />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import {ExternalLink, Maximize2} from 'lucide-react';
|
||||
|
||||
type DemoFrameProps = {
|
||||
title: string;
|
||||
url: string;
|
||||
openLabel: string;
|
||||
};
|
||||
|
||||
export function DemoFrame({title, url, openLabel}: DemoFrameProps) {
|
||||
return (
|
||||
<div className="demoFrame">
|
||||
<div className="demoFrame__bar">
|
||||
<span>{title}</span>
|
||||
<div className="demoFrame__actions">
|
||||
<a href={url} target="_blank" rel="noreferrer" aria-label="Otwórz demo w nowej karcie">
|
||||
<ExternalLink size={18} />
|
||||
</a>
|
||||
<a href={url} target="_blank" rel="noreferrer" aria-label="Pełny ekran">
|
||||
<Maximize2 size={18} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="demoFrame__viewport">
|
||||
<iframe title={title} src={url} loading="lazy" allowFullScreen />
|
||||
<a className="demoFrame__fallback" href={url} target="_blank" rel="noreferrer">
|
||||
{openLabel}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {CheckCircle2, DoorOpen} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import {ButtonLink} from './ButtonLink';
|
||||
import {DemoFrame} from './DemoFrame';
|
||||
import {Faq} from './Faq';
|
||||
import {SectionHeader} from './SectionHeader';
|
||||
import {Stats} from './Stats';
|
||||
import {contact} from '@/config/contact';
|
||||
import type {DemoContent} from '@/types/content';
|
||||
|
||||
type DemoPageProps = {
|
||||
demo: DemoContent;
|
||||
};
|
||||
|
||||
export function DemoPage({demo}: DemoPageProps) {
|
||||
return (
|
||||
<main>
|
||||
<section className="demoHero sectionBand sectionBand--light">
|
||||
<div className="container demoHero__grid">
|
||||
<div>
|
||||
<p className="eyebrow">{demo.eyebrow}</p>
|
||||
<h1>{demo.title}</h1>
|
||||
<p>{demo.intro}</p>
|
||||
<p>{demo.description}</p>
|
||||
<div className="buttonRow">
|
||||
<ButtonLink href="#podglad">Zobacz demo</ButtonLink>
|
||||
<ButtonLink href={contact.url} variant="secondary" isExternal>
|
||||
Porozmawiajmy
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
{demo.imageUrl ? (
|
||||
<Image src={demo.imageUrl} alt="" width={640} height={640} priority />
|
||||
) : (
|
||||
<div className="demoHero__visual" aria-hidden="true">
|
||||
<DoorOpen size={112} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="podglad" className="sectionBand">
|
||||
<div className="container">
|
||||
<DemoFrame title={demo.title} url={demo.iframeUrl} openLabel={demo.openLabel} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand sectionBand--tint statsBand">
|
||||
<div className="container">
|
||||
<Stats stats={demo.stats} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand">
|
||||
<div className="container">
|
||||
<SectionHeader title={demo.benefitsIntro} />
|
||||
<div className="featureGrid featureGrid--three">
|
||||
{demo.benefits.map(benefit => (
|
||||
<article className="featureItem" key={benefit.title}>
|
||||
<CheckCircle2 size={22} />
|
||||
<h3>{benefit.title}</h3>
|
||||
<p>{benefit.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand sectionBand--dark">
|
||||
<div className="container finalCta finalCta--dark">
|
||||
<h2>{demo.cta.title}</h2>
|
||||
<p>{demo.cta.description}</p>
|
||||
<ButtonLink href={contact.url} isExternal>
|
||||
{demo.cta.button}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand sectionBand--tint">
|
||||
<div className="container narrow">
|
||||
<SectionHeader title="FAQ" />
|
||||
<Faq items={demo.faq} />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type {FaqItem} from '@/types/content';
|
||||
|
||||
type FaqProps = {
|
||||
items: FaqItem[];
|
||||
};
|
||||
|
||||
export function Faq({items}: FaqProps) {
|
||||
return (
|
||||
<div className="faqList">
|
||||
{items.map((item, index) => (
|
||||
<details key={`${index}-${item.question}`}>
|
||||
<summary>{item.question}</summary>
|
||||
<p>{item.answer}</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import Link from 'next/link';
|
||||
import {contact} from '@/config/contact';
|
||||
import {Logo} from './Logo';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="footer__inner">
|
||||
<div className="footer__brand">
|
||||
<Link href="/pl" aria-label="Ultifide konfiguratory 3D">
|
||||
<Logo isWhite />
|
||||
</Link>
|
||||
<p>Copyright © 2026, Ultifide</p>
|
||||
</div>
|
||||
<nav className="footer__nav" aria-label="Nawigacja stopki">
|
||||
<Link href="/pl">Platforma</Link>
|
||||
<Link href="/pl/demo">Demo biurka</Link>
|
||||
<Link href="/pl/demo-room">Demo wnętrz</Link>
|
||||
<Link href="/pl/demo-door">Demo drzwi</Link>
|
||||
<a href={contact.url}>Kontakt</a>
|
||||
</nav>
|
||||
<address className="footer__contact">
|
||||
<strong>Contact Us</strong>
|
||||
{contact.address.map(line => (
|
||||
<span key={line}>{line}</span>
|
||||
))}
|
||||
<a href={`mailto:${contact.email}`}>{contact.email}</a>
|
||||
<a href={`tel:${contact.phone.replaceAll(' ', '')}`}>{contact.phone}</a>
|
||||
</address>
|
||||
<div className="footer__social">
|
||||
<a href={contact.linkedin} aria-label="LinkedIn Ultifide">
|
||||
in
|
||||
</a>
|
||||
<a href={contact.facebook} aria-label="Facebook Ultifide">
|
||||
f
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
'use client';
|
||||
|
||||
import {Menu, X} from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {usePathname} from 'next/navigation';
|
||||
import {contact} from '@/config/contact';
|
||||
import {Logo} from './Logo';
|
||||
|
||||
const navItems = [
|
||||
{id: 'platforma', label: 'Platforma', href: '/pl#platforma'},
|
||||
{id: 'dema', label: 'Dema', href: '/pl/dema'},
|
||||
{id: 'funkcje', label: 'Funkcje', href: '/pl#funkcje'},
|
||||
{id: 'faq', label: 'FAQ', href: '/pl#faq'},
|
||||
{id: 'kontakt', label: 'Kontakt', href: contact.url}
|
||||
];
|
||||
|
||||
export function Header() {
|
||||
const pathname = usePathname();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [activeSection, setActiveSection] = useState('platforma');
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname !== '/pl') {
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ['platforma', 'dema', 'funkcje', 'faq']
|
||||
.map(id => document.getElementById(id))
|
||||
.filter((section): section is HTMLElement => Boolean(section));
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
entries => {
|
||||
const visibleEntry = entries
|
||||
.filter(entry => entry.isIntersecting)
|
||||
.sort((first, second) => second.intersectionRatio - first.intersectionRatio)[0];
|
||||
|
||||
if (visibleEntry?.target.id) {
|
||||
setActiveSection(visibleEntry.target.id);
|
||||
}
|
||||
},
|
||||
{rootMargin: '-30% 0px -55% 0px', threshold: [0.1, 0.35, 0.6]}
|
||||
);
|
||||
|
||||
sections.forEach(section => observer.observe(section));
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [pathname]);
|
||||
|
||||
function isActive(itemId: string) {
|
||||
if (itemId === 'kontakt') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pathname === '/pl') {
|
||||
return itemId === activeSection;
|
||||
}
|
||||
|
||||
if (itemId === 'dema') {
|
||||
return pathname === '/pl/dema' || pathname.startsWith('/pl/demo');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="siteHeader">
|
||||
<div className="siteHeader__inner">
|
||||
<Link className="siteHeader__logo" href="/pl" aria-label="Ultifide konfiguratory 3D">
|
||||
<Logo />
|
||||
</Link>
|
||||
<nav className="siteHeader__nav" aria-label="Nawigacja glowna">
|
||||
{navItems.map(item => (
|
||||
<a className={isActive(item.id) ? 'isActive' : undefined} key={item.href} href={item.href}>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
<a className="siteHeader__contact" href={contact.url}>
|
||||
Porozmawiajmy
|
||||
</a>
|
||||
<button
|
||||
className="siteHeader__menu"
|
||||
type="button"
|
||||
aria-label={isOpen ? 'Zamknij menu' : 'Otwórz menu'}
|
||||
aria-expanded={isOpen}
|
||||
onClick={() => setIsOpen(current => !current)}
|
||||
>
|
||||
{isOpen ? <X size={22} /> : <Menu size={22} />}
|
||||
</button>
|
||||
</div>
|
||||
{isOpen ? (
|
||||
<nav className="siteHeader__mobileNav" aria-label="Nawigacja mobilna">
|
||||
{navItems.map(item => (
|
||||
<a
|
||||
className={isActive(item.id) ? 'isActive' : undefined}
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
) : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import {Box, CheckCircle2, DoorOpen, Layers3, MousePointer2, PanelRight, SlidersHorizontal} from 'lucide-react';
|
||||
|
||||
export function HeroVisual() {
|
||||
return (
|
||||
<div className="heroVisual" aria-hidden="true">
|
||||
<div className="heroVisual__topbar">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
<div className="heroVisual__scene">
|
||||
<div className="heroVisual__product">
|
||||
<div className="heroVisual__cube heroVisual__cube--main">
|
||||
<Box size={70} />
|
||||
</div>
|
||||
<div className="heroVisual__orbit heroVisual__orbit--one">
|
||||
<DoorOpen size={28} />
|
||||
</div>
|
||||
<div className="heroVisual__orbit heroVisual__orbit--two">
|
||||
<Layers3 size={28} />
|
||||
</div>
|
||||
<div className="heroVisual__cursor">
|
||||
<MousePointer2 size={22} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="heroVisual__panel">
|
||||
<div>
|
||||
<PanelRight size={18} />
|
||||
<span>Konfiguracja</span>
|
||||
</div>
|
||||
<div className="heroVisual__option isActive">
|
||||
<CheckCircle2 size={16} />
|
||||
Materiał premium
|
||||
</div>
|
||||
<div className="heroVisual__option">
|
||||
<SlidersHorizontal size={16} />
|
||||
Reguły produktu
|
||||
</div>
|
||||
<div className="heroVisual__swatches">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="heroVisual__footer">
|
||||
<span>3D preview</span>
|
||||
<strong>API-ready</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
interface Props {
|
||||
className?: string;
|
||||
isWhite?: boolean;
|
||||
}
|
||||
|
||||
const UltifideLogo = ({ className, isWhite }: Props) => (
|
||||
<svg
|
||||
className={className}
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', width: 'auto' }}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="284"
|
||||
height="83"
|
||||
viewBox="0 0 284 83"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill={isWhite ? '#FFFFFF' : '#001C44'}
|
||||
d="M282.084 52.804c.453 0 .845.185 1.174.555.33.37.494.842.494 1.417 0 1.028-.72 1.973-2.162 2.836a19.642 19.642 0 01-4.698 1.972 19.893 19.893 0 01-4.822.616c-4.656 0-8.344-1.376-11.063-4.13-2.679-2.752-4.019-6.553-4.019-11.402 0-3.082.599-5.794 1.793-8.136 1.195-2.383 2.864-4.232 5.007-5.547 2.184-1.315 4.656-1.973 7.417-1.973 3.914 0 7.026 1.274 9.334 3.822 2.307 2.547 3.461 6 3.461 10.355 0 .822-.165 1.417-.494 1.787-.331.37-.866.555-1.608.555h-19.903c.371 7.068 3.729 10.602 10.075 10.602 1.607 0 2.988-.206 4.142-.617 1.153-.452 2.39-1.048 3.708-1.787 1.071-.617 1.793-.925 2.164-.925zm-10.817-19.909c-2.637 0-4.759.822-6.366 2.466-1.567 1.644-2.493 3.965-2.782 6.965h17.493c-.083-3.04-.845-5.363-2.287-6.965-1.443-1.644-3.462-2.466-6.058-2.466zm-24.439-17.061c.783 0 1.401.226 1.854.678.453.452.681 1.027.681 1.726v39.2c0 .74-.228 1.336-.681 1.788-.453.452-1.071.678-1.854.678-.783 0-1.401-.226-1.855-.678-.411-.452-.617-1.047-.617-1.787V54.11c-.907 1.89-2.246 3.349-4.018 4.376-1.731 1.027-3.77 1.54-6.119 1.54-2.638 0-4.965-.657-6.985-1.972-2.019-1.315-3.585-3.143-4.697-5.485-1.113-2.384-1.67-5.137-1.67-8.26 0-3.082.557-5.794 1.67-8.136 1.112-2.342 2.678-4.15 4.697-5.424 2.02-1.274 4.347-1.91 6.985-1.91 2.349 0 4.388.513 6.119 1.54 1.772 1.027 3.111 2.486 4.018 4.377v-16.52c0-.739.206-1.314.617-1.725.454-.452 1.072-.678 1.855-.678zm-11.621 40.064c2.926 0 5.172-.986 6.738-2.959 1.607-2.013 2.411-4.848 2.411-8.505 0-3.658-.804-6.472-2.411-8.445-1.566-1.972-3.812-2.958-6.738-2.958-2.925 0-5.212.986-6.86 2.958-1.608 1.973-2.411 4.746-2.411 8.321 0 3.657.803 6.513 2.411 8.568 1.648 2.013 3.935 3.02 6.86 3.02zM211.001 29.58c.7 0 1.236.184 1.607.554.371.37.557.863.557 1.48v25.825c0 .78-.248 1.397-.742 1.85-.454.41-1.03.616-1.731.616-.741 0-1.36-.206-1.854-.617-.454-.452-.68-1.068-.68-1.849V33.524h-13.537v23.915c0 .78-.247 1.397-.741 1.85-.453.41-1.03.616-1.731.616-.741 0-1.36-.206-1.854-.617-.454-.452-.68-1.068-.68-1.849V33.524h-4.141c-.701 0-1.257-.165-1.669-.493-.371-.37-.556-.843-.556-1.418 0-.616.185-1.11.556-1.48.412-.369.968-.554 1.669-.554h4.141v-1.048c0-3.698.927-6.595 2.781-8.69 1.855-2.137 4.513-3.35 7.974-3.637l1.484-.123c1.977-.165 2.966.452 2.966 1.849 0 1.191-.7 1.87-2.101 2.034l-1.484.123c-2.184.165-3.832.863-4.945 2.096-1.112 1.191-1.669 3-1.669 5.424v1.972h16.38zm-.309-13.314c.948 0 1.731.287 2.349.863.659.575.989 1.314.989 2.218 0 .945-.309 1.706-.927 2.281-.618.575-1.422.863-2.411.863-.989 0-1.792-.288-2.411-.863-.617-.575-.926-1.335-.926-2.28 0-.904.309-1.644.926-2.22.619-.575 1.422-.862 2.411-.862zm-34.678 43.639c-.741 0-1.36-.206-1.854-.617-.453-.452-.68-1.068-.68-1.849V31.43c0-.781.227-1.377.68-1.788.494-.452 1.113-.678 1.854-.678.742 0 1.34.226 1.793.678.453.41.68 1.007.68 1.787V57.44c0 .822-.227 1.438-.68 1.85-.453.41-1.051.616-1.793.616zm0-37.414c-.989 0-1.792-.288-2.41-.863-.618-.575-.927-1.335-.927-2.28 0-.904.309-1.644.927-2.22.618-.575 1.421-.862 2.41-.862.989 0 1.793.287 2.411.863.618.575.927 1.314.927 2.218 0 .946-.309 1.706-.927 2.281-.618.575-1.422.863-2.411.863zm-9.401 33.469c1.443.123 2.164.78 2.164 1.972 0 .698-.268 1.233-.804 1.602-.494.33-1.257.453-2.287.37l-1.669-.123c-3.296-.246-5.727-1.233-7.293-2.959-1.566-1.725-2.349-4.335-2.349-7.828v-15.47h-4.141c-.701 0-1.257-.165-1.669-.493-.371-.37-.557-.843-.557-1.418 0-.616.186-1.11.557-1.48.412-.37.968-.554 1.669-.554h4.141v-6.965c0-.78.227-1.377.68-1.788.453-.451 1.071-.678 1.854-.678.742 0 1.339.227 1.793.678.453.411.68 1.007.68 1.788v6.965h6.861c.659 0 1.174.185 1.545.555.412.37.618.863.618 1.479 0 .575-.206 1.048-.618 1.418-.371.328-.886.493-1.545.493h-6.861V49.24c0 2.26.453 3.904 1.359 4.931.948.986 2.349 1.541 4.204 1.664l1.668.123zm-25.761 3.944c-.742 0-1.36-.205-1.855-.616-.453-.452-.68-1.068-.68-1.85V18.3c0-.78.227-1.376.68-1.787.495-.452 1.113-.678 1.855-.678.7 0 1.277.226 1.73.678.495.41.742 1.007.742 1.787v39.14c0 .78-.247 1.397-.742 1.849-.453.41-1.03.616-1.73.616z"
|
||||
/>
|
||||
<path
|
||||
fill={isWhite ? '#FFFFFF' : '#001C44'}
|
||||
stroke="#001C44"
|
||||
strokeWidth="0.617"
|
||||
d="M105.338 56.904c2.312 2.127 5.687 3.165 10.073 3.165 4.356 0 7.715-1.038 10.026-3.165 2.346-2.158 3.498-5.277 3.498-9.306V30.811c0-.612-.198-1.127-.616-1.509-.415-.409-.971-.598-1.628-.598-.633 0-1.182.194-1.623.594l.207.228-.207-.228c-.421.382-.62.899-.62 1.513v17.087c0 2.888-.771 5.006-2.263 6.414-1.496 1.411-3.735 2.143-6.774 2.143-3.037 0-5.293-.731-6.821-2.144-1.491-1.408-2.262-3.526-2.262-6.413V30.811c0-.612-.198-1.127-.616-1.509-.412-.406-.95-.598-1.581-.598-.657 0-1.212.19-1.628.598-.417.382-.615.897-.615 1.509v16.787c0 4.055 1.135 7.175 3.45 9.306z"
|
||||
/>
|
||||
<g clipPath="url(#a)">
|
||||
<path
|
||||
fill={isWhite ? '#FFFFFF' : '#42A6FF'}
|
||||
d="M.161 47.292a39.51 39.51 0 00.763 4.94 38.687 38.687 0 003.457 9.515A39.121 39.121 0 0024.56 80.023h.01c11.485-5.528 19.928-16.435 22.207-29.396 0-.014-.013-.02-.023-.01a10.276 10.276 0 01-5.24 3.216c-.746.188-1.529.3-2.328.313-5.799.112-10.545-4.776-10.545-10.574V28.56c0-.026.004-.05.004-.076V21.03a2.196 2.196 0 00-2.483-2.17c-1.102.144-1.895 1.14-1.895 2.25v4.43c0 .447-.319.861-.766.9a.844.844 0 01-.921-.841v-8.341c0-1.043-.74-1.905-1.717-2.125-.052-.01-.102-.03-.154-.036a2.114 2.114 0 00-.612-.007c-1.102.145-1.898 1.142-1.898 2.25v4.493a.85.85 0 01-.131.45.834.834 0 01-.895.375c-.395-.088-.658-.467-.658-.871v-9.058c0-.97-.638-1.796-1.516-2.082a2.152 2.152 0 00-.967-.086c-1.102.145-1.895 1.142-1.895 2.25v14.238c0 .447-.318.862-.766.901a.844.844 0 01-.92-.842v-9.85a2.195 2.195 0 00-2.484-2.17c-1.102.144-1.894 1.14-1.894 2.249v5.999a.83.83 0 01-.25.595.834.834 0 01-.655.247c-.454-.03-.79-.447-.79-.905v-2.167c0-1.108-.792-2.105-1.894-2.25A2.198 2.198 0 000 21.026l.007 22.776.154 3.486v.004z"
|
||||
/>
|
||||
</g>
|
||||
<g clipPath="url(#b)">
|
||||
<path
|
||||
fill={isWhite ? '#FFFFFF' : '#42A6FF'}
|
||||
d="M26.713 80.461a38.476 38.476 0 0010.926 1.885h.017c.397 0 .792.016 1.19.016 21.54-.082 38.846-17.869 38.846-39.408V15.613c0-7.703-6.134-14.264-13.77-14.597-.198 0-.415-.016-.63-.016a14.37 14.37 0 00-12.941 8.282 13.898 13.898 0 00-1.29 4.894v28.778c0 .197 0 .414-.016.612v1.273c0 1.338-.066 2.644-.197 3.933a34.57 34.57 0 01-.447 3.24c0 .016-.017.033-.017.05a37.484 37.484 0 01-.908 3.85c-.066.23-.148.48-.213.71-.132.415-.264.826-.415 1.24-3.453 9.937-10.761 18.1-20.135 22.6z"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
fill={isWhite ? '#FFFFFF' : '#42A6FF'}
|
||||
d="M27.485 65.688c-3.471 1.138 2.255 3.087-1.567 3.186l2.11 1.063c-18.551-12.194-10.155-7.962 2.979 4.248C9.354 74.102 2.1 63.715 0 43.342v-27.66C0 7.89 6.167 1.252 13.844.916c.198 0 .416-.017.631-.017 5.75.033 10.72 3.444 13.01 8.378a14.134 14.134 0 011.296 4.951v29.114c0 .2 0 .419.017.618v1.288c0 1.354.066 2.675.198 3.98.1 1.104.248 2.189.45 3.277 0 .017.016.033.016.05a38.5 38.5 0 00.913 3.896c.066.233.149.486.215.719.132.419.264.835.416 1.254 3.472 10.052-12.943 2.712-3.52 7.264z"
|
||||
/>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h46.776v69.486H0z" transform="matrix(-1 0 0 1 46.776 10.537)" />
|
||||
</clipPath>
|
||||
<clipPath id="b">
|
||||
<path fill="#fff" d="M0 0h50.979v81.362H0z" transform="matrix(-1 0 0 1 77.692 1)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
UltifideLogo.defaultProps = {
|
||||
isWhite: false,
|
||||
};
|
||||
|
||||
export default UltifideLogo;
|
||||
|
||||
export const Logo = UltifideLogo;
|
||||
@@ -0,0 +1,15 @@
|
||||
type SectionHeaderProps = {
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export function SectionHeader({eyebrow, title, description}: SectionHeaderProps) {
|
||||
return (
|
||||
<div className="sectionHeader">
|
||||
{eyebrow ? <p className="eyebrow">{eyebrow}</p> : null}
|
||||
<h2>{title}</h2>
|
||||
{description ? <p>{description}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type {Stat} from '@/types/content';
|
||||
|
||||
type StatsProps = {
|
||||
stats: Stat[];
|
||||
};
|
||||
|
||||
export function Stats({stats}: StatsProps) {
|
||||
return (
|
||||
<div className="statsGrid">
|
||||
{stats.map(stat => (
|
||||
<div className="statItem" key={`${stat.value}-${stat.label}`}>
|
||||
<strong>{stat.value}</strong>
|
||||
<span>{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user