Files
lean-101-website/src/lib/components/SitePage.svelte
T

380 lines
14 KiB
Svelte
Raw Normal View History

2026-05-06 22:38:06 +12:00
<script>
import { onMount } from 'svelte';
import { absoluteUrl } from '$lib/site';
export let page;
const externalImage = absoluteUrl('/assets/lean101-logotipo.png');
const structuredData =
page.slug === 'home'
? JSON.stringify({
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: 'Lean 101',
url: absoluteUrl('/'),
image: externalImage,
logo: absoluteUrl('/assets/lean101-isotipo.png'),
description: page.meta.metaDescription,
email: page.meta.contactEmail,
areaServed: 'Worldwide',
serviceType: [
'Process excellence consulting',
'Coaching and training',
'Digital solutions'
]
})
: null;
/** @param {string} tone */
const serviceTagClass = (tone) => {
if (tone === 'orange') return 'service-tag orange';
if (tone === 'charcoal') return 'service-tag charcoal';
return 'service-tag';
};
/** @param {string} href */
const isExternalLink = (href) => href.startsWith('http');
/** @param {string} body */
const toParagraphs = (body) =>
String(body || '')
.split(/\n\s*\n/)
.map((paragraph) => paragraph.trim())
.filter(Boolean);
onMount(() => {
if (!window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
return;
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('in');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
document.querySelectorAll('.section, .hero-visual, .cta-wrap').forEach((element) => {
element.classList.add('reveal');
observer.observe(element);
});
return () => observer.disconnect();
});
</script>
<svelte:head>
<title>{page.meta.pageTitle}</title>
<meta name="description" content={page.meta.metaDescription} />
<meta name="robots" content="index,follow" />
<meta name="theme-color" content="#0070c7" />
<link rel="canonical" href={absoluteUrl(page.path)} />
<link rel="icon" href="/assets/lean101-isotipo.png" />
<link rel="preconnect" href="https://api.fontshare.com" />
<link
href="https://api.fontshare.com/v2/css?f[]=general-sans@400,500,600,700&display=swap"
rel="stylesheet"
/>
<meta property="og:type" content="website" />
<meta property="og:title" content={page.meta.pageTitle} />
<meta property="og:description" content={page.meta.socialDescription} />
<meta property="og:url" content={absoluteUrl(page.path)} />
<meta property="og:image" content={externalImage} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={page.meta.pageTitle} />
<meta name="twitter:description" content={page.meta.socialDescription} />
<meta name="twitter:image" content={externalImage} />
{#if structuredData}
<script type="application/ld+json">{structuredData}</script>
{/if}
</svelte:head>
<nav class="nav">
<div class="nav-inner">
<a href="/" class="nav-logo" aria-label="Lean 101 home">
<img src="/assets/lean101-isotipo.png" alt="Lean 101" />
</a>
<ul class="nav-links">
{#each page.nav.links as link}
<li><a href={link.href}>{link.label}</a></li>
{/each}
</ul>
<a href={page.nav.ctaHref} class="nav-cta">{page.nav.ctaLabel}</a>
</div>
</nav>
{#each page.sections as section}
{#if section.type === 'hero'}
{@const hero = section.data}
<header class="hero" id={section.anchor || undefined}>
<span class="hero-eyebrow">{hero.eyebrow}</span>
<h1>
{hero.titleLead}
{#if hero.titleEmphasis}
<br />
<em>{hero.titleEmphasis}</em>
{/if}
</h1>
<p class="hero-sub">{hero.subtext}</p>
<div class="hero-ctas">
<a href={hero.primaryCta.href} class="btn btn-primary">
{hero.primaryCta.label} <span class="btn-arrow"></span>
</a>
<a href={hero.secondaryCta.href} class="btn btn-secondary">
{hero.secondaryCta.label}
</a>
</div>
<div class="hero-visual">
<img src={hero.image.src} alt={hero.image.alt} />
{#if hero.stats?.length}
<div class="hero-stats">
{#each hero.stats as stat}
<div class="hero-stat">
<div class="hero-stat-label">{stat.label}</div>
<div class={`hero-stat-num ${stat.tone}`}>{stat.value}</div>
</div>
{/each}
</div>
{/if}
</div>
</header>
{:else if section.type === 'services'}
{@const block = section.data}
<section class="section" id={section.anchor || undefined}>
<div class="section-inner">
<div class="services-head">
<div class="section-eyebrow">{block.eyebrow}</div>
<h2 class="section-title">{block.title}</h2>
<p class="section-lede">{block.lede}</p>
</div>
<div class="services-grid">
{#each block.cards as card}
<article class="service-card">
<div class="service-img">
<span class={serviceTagClass(card.tagTone)}>{card.tag}</span>
<img src={card.image.src} alt={card.image.alt} />
</div>
<div class="service-body">
<h3 class="service-title">{card.title}</h3>
<p class="service-desc">{card.description}</p>
</div>
</article>
{/each}
</div>
</div>
</section>
{:else if section.type === 'process'}
{@const block = section.data}
<section class="section process" id={section.anchor || undefined}>
<div class="section-inner">
<div class="services-head">
<div class="section-eyebrow">{block.eyebrow}</div>
<h2 class="section-title">{block.title}</h2>
<p class="section-lede">{block.lede}</p>
</div>
<div class="flow">
{#each block.steps as step, index}
<div class="flow-step">
<div class="flow-step-label">{step.label}</div>
<h3 class="flow-step-title">{step.title}</h3>
<p class="flow-step-desc">{step.description}</p>
<div class="flow-step-outcome">{step.outcome}</div>
</div>
{#if index < block.steps.length - 1}
<div class="flow-arrow" aria-hidden="true">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14" />
<path d="m13 6 6 6-6 6" />
</svg>
</div>
{/if}
{/each}
</div>
</div>
</section>
{:else if section.type === 'outcomes'}
{@const block = section.data}
<section class="section outcomes" id={section.anchor || undefined}>
<div class="section-inner">
<div class="outcomes-head">
<div class="section-eyebrow">{block.eyebrow}</div>
<h2 class="section-title">{block.title}</h2>
<p class="section-lede">{block.lede}</p>
</div>
<div class="levers-grid">
{#each block.items as item, index}
<div class="lever">
<div class="lever-icon">
{#if index === 0}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>
{:else if index === 1}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /><path d="m9 12 2 2 4-4" /></svg>
{:else if index === 2}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="2" x2="12" y2="22" /><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" /></svg>
{:else if index === 3}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" y1="9" x2="9.01" y2="9" /><line x1="15" y1="9" x2="15.01" y2="9" /></svg>
{:else}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></svg>
{/if}
</div>
<span class="lever-num">{item.number}</span>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
{/each}
</div>
</div>
</section>
{:else if section.type === 'why'}
{@const block = section.data}
<section class="section whyus" id={section.anchor || undefined}>
<div class="section-inner">
<div class="whyus-layout">
<div>
<div class="section-eyebrow">{block.eyebrow}</div>
<h2 class="section-title">{block.title}</h2>
<p class="section-lede">{block.lede}</p>
</div>
<ul class="whyus-list">
{#each block.items as item, index}
<li class="whyus-item">
<div class="whyus-icon">
{#if index === 0}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" /><path d="M2 12h20" /></svg>
{:else if index === 1}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.4 2.4 0 0 1 0-3.4l2.6-2.6a2.4 2.4 0 0 1 3.4 0Z" /><path d="m14.5 12.5 2-2" /><path d="m11.5 9.5 2-2" /><path d="m8.5 6.5 2-2" /><path d="m17.5 15.5 2-2" /></svg>
{:else if index === 2}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 10v6M2 10l10-5 10 5-10 5z" /><path d="M6 12v5c3 3 9 3 12 0v-5" /></svg>
{:else}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" x2="12" y1="20" y2="10" /><line x1="18" x2="18" y1="20" y2="4" /><line x1="6" x2="6" y1="20" y2="16" /></svg>
{/if}
</div>
<div class="whyus-text">
<h4>{item.title}</h4>
<p>{item.description}</p>
</div>
</li>
{/each}
</ul>
</div>
</div>
</section>
{:else if section.type === 'richText'}
{@const block = section.data}
<section class="section generic-copy" id={section.anchor || undefined}>
<div class="section-inner generic-copy-inner">
{#if block.eyebrow}
<div class="section-eyebrow">{block.eyebrow}</div>
{/if}
{#if block.title}
<h2 class="section-title">{block.title}</h2>
{/if}
<div class="generic-copy-body">
{#each toParagraphs(block.body) as paragraph}
<p>{paragraph}</p>
{/each}
</div>
</div>
</section>
{:else if section.type === 'cta'}
{@const block = section.data}
<section class="cta-wrap" id={section.anchor || undefined}>
<div class="cta-card">
<div class="cta-inner">
<h2>
{block.titleLead} <em>{block.titleEmphasis}</em> {block.titleTail}
</h2>
<p>{block.body}</p>
<div class="cta-flow">
{#each block.flowSteps as step, index}
<div class="cta-flow-step"><span class="num">{index + 1}</span> {step}</div>
{#if index < block.flowSteps.length - 1}
<span class="cta-flow-arrow"></span>
{/if}
{/each}
</div>
<div class="hero-ctas">
<a href={block.primaryCta.href} class="btn btn-on-dark">
{block.primaryCta.label} <span class="btn-arrow"></span>
</a>
<a href={block.secondaryCta.href} class="btn btn-ghost-dark">
{block.secondaryCta.label}
</a>
</div>
</div>
</div>
</section>
{/if}
{/each}
<footer class="footer">
<div class="footer-inner">
<div class="footer-grid">
<div class="footer-brand">
<img src="/assets/lean101-logotipo.png" alt="Lean 101" />
<p>{page.footer.brandTagline}</p>
</div>
<div class="footer-col">
<h5>Services</h5>
<ul>
{#each page.footer.servicesLinks as link}
<li><a href={link.href}>{link.label}</a></li>
{/each}
</ul>
</div>
<div class="footer-col">
<h5>Company</h5>
<ul>
{#each page.footer.companyLinks as link}
<li><a href={link.href}>{link.label}</a></li>
{/each}
</ul>
</div>
<div class="footer-col">
<h5>Connect</h5>
<ul>
{#each page.footer.connectLinks as link}
<li>
<a
href={link.href}
target={isExternalLink(link.href) ? '_blank' : undefined}
rel={isExternalLink(link.href) ? 'noreferrer' : undefined}
>
{link.label}
</a>
</li>
{/each}
</ul>
</div>
</div>
<div class="footer-bottom">
<span>© {page.meta.copyrightYear} Lean 101. All rights reserved.</span>
<span>{page.meta.location}</span>
</div>
</div>
</footer>