Files
gw-svelte/src/lib/components/RouteSkeleton.svelte
T

731 lines
19 KiB
Svelte
Raw Normal View History

<script lang="ts">
export let pathname = '/';
const serviceRoutes = new Set(['/pack-walks', '/dog-walking', '/puppy-visits']);
const legalRoutes = new Set(['/terms-and-conditions', '/privacy-policy']);
const heroLines = [0, 1, 2];
const shortLines = [0, 1];
const cardTriplet = [0, 1, 2];
const cardQuartet = [0, 1, 2, 3];
const doubleStack = [0, 1];
function getVariant(path: string) {
if (path === '/') return 'home';
if (serviceRoutes.has(path)) return 'service';
if (path === '/about' || path === '/about-us') return 'about';
if (path === '/our-pricing') return 'pricing';
if (path === '/contact-us') return 'contact';
if (legalRoutes.has(path)) return 'legal';
return 'generic';
}
$: variant = getVariant(pathname);
</script>
<div class="route-skeleton" data-skeleton-variant={variant}>
<div class="skeleton-shell">
<header class="skeleton-header">
<div class="skeleton-inner skeleton-nav">
<div class="skeleton-row skeleton-nav-links" aria-hidden="true">
{#each cardQuartet as _}
<span class="skeleton-pill skeleton-shimmer"></span>
{/each}
</div>
<span class="skeleton-logo skeleton-shimmer" aria-hidden="true"></span>
<div class="skeleton-row skeleton-nav-actions" aria-hidden="true">
<span class="skeleton-circle skeleton-shimmer"></span>
<span class="skeleton-button skeleton-shimmer"></span>
</div>
</div>
</header>
{#if variant === 'home'}
<main class="skeleton-main">
<section class="skeleton-section skeleton-home-hero">
<div class="skeleton-inner skeleton-home-hero-grid">
<div class="skeleton-copy">
<span class="skeleton-kicker skeleton-shimmer"></span>
<span class="skeleton-title skeleton-title-lg skeleton-shimmer"></span>
<span class="skeleton-title skeleton-title-md skeleton-shimmer"></span>
{#each heroLines as _}
<span class="skeleton-line skeleton-shimmer"></span>
{/each}
<div class="skeleton-row skeleton-hero-actions">
<span class="skeleton-button skeleton-button-wide skeleton-shimmer"></span>
<span class="skeleton-button skeleton-button-outline skeleton-shimmer"></span>
</div>
</div>
<div class="skeleton-media skeleton-media-hero skeleton-shimmer"></div>
</div>
</section>
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-strip skeleton-shimmer"></div>
</div>
</section>
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-section-heading skeleton-section-heading-left">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
{#each shortLines as _}
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
{/each}
</div>
<div class="skeleton-grid skeleton-grid-3">
{#each cardTriplet as _}
<article class="skeleton-card skeleton-shimmer"></article>
{/each}
</div>
</div>
</section>
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-section-heading">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
</div>
<div class="skeleton-grid skeleton-grid-2">
{#each doubleStack as _}
<article class="skeleton-quote-card skeleton-shimmer">
<span class="skeleton-line skeleton-line-medium"></span>
<span class="skeleton-line"></span>
<span class="skeleton-line skeleton-line-medium"></span>
</article>
{/each}
</div>
</div>
</section>
<section class="skeleton-section skeleton-section-last">
<div class="skeleton-inner">
<div class="skeleton-booking">
<div class="skeleton-booking-header">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
<div class="skeleton-row skeleton-stepper">
<span class="skeleton-chip skeleton-shimmer"></span>
<span class="skeleton-chip skeleton-shimmer"></span>
</div>
</div>
<div class="skeleton-grid skeleton-grid-2">
<article class="skeleton-form-card skeleton-shimmer"></article>
<article class="skeleton-form-card skeleton-shimmer"></article>
</div>
</div>
</div>
</section>
</main>
{:else if variant === 'service'}
<main class="skeleton-main">
<section class="skeleton-section skeleton-service-hero">
<div class="skeleton-inner skeleton-home-hero-grid">
<div class="skeleton-copy">
<span class="skeleton-kicker skeleton-shimmer"></span>
<span class="skeleton-title skeleton-title-lg skeleton-shimmer"></span>
{#each heroLines as _}
<span class="skeleton-line skeleton-shimmer"></span>
{/each}
</div>
<div class="skeleton-media skeleton-media-hero skeleton-shimmer"></div>
</div>
</section>
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-section-heading">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
</div>
<div class="skeleton-grid skeleton-grid-3">
{#each cardTriplet as _}
<article class="skeleton-plan-card skeleton-shimmer"></article>
{/each}
</div>
</div>
</section>
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-grid skeleton-grid-3">
{#each cardTriplet as _}
<article class="skeleton-card skeleton-card-short skeleton-shimmer"></article>
{/each}
</div>
</div>
</section>
<section class="skeleton-section skeleton-section-last">
<div class="skeleton-inner">
<div class="skeleton-booking skeleton-shimmer"></div>
</div>
</section>
</main>
{:else if variant === 'about'}
<main class="skeleton-main">
<section class="skeleton-section skeleton-about-hero">
<div class="skeleton-inner skeleton-centered-heading">
<span class="skeleton-title skeleton-title-lg skeleton-shimmer"></span>
</div>
</section>
{#each doubleStack as index}
<section class="skeleton-section">
<div class:skeleton-about-grid-reverse={index % 2 === 1} class="skeleton-inner skeleton-about-grid">
<div class="skeleton-copy">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
{#each heroLines as _}
<span class="skeleton-line skeleton-shimmer"></span>
{/each}
</div>
<div class="skeleton-media skeleton-media-tall skeleton-shimmer"></div>
</div>
</section>
{/each}
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-section-heading">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
</div>
<div class="skeleton-grid skeleton-grid-3">
{#each cardTriplet as _}
<article class="skeleton-card skeleton-shimmer"></article>
{/each}
</div>
</div>
</section>
<section class="skeleton-section skeleton-section-last">
<div class="skeleton-inner">
<div class="skeleton-contact-card skeleton-shimmer"></div>
</div>
</section>
</main>
{:else if variant === 'pricing'}
<main class="skeleton-main">
<section class="skeleton-section skeleton-about-hero">
<div class="skeleton-inner skeleton-centered-heading">
<span class="skeleton-title skeleton-title-lg skeleton-shimmer"></span>
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
</div>
</section>
{#each doubleStack as _}
<section class="skeleton-section">
<div class="skeleton-inner">
<div class="skeleton-section-heading skeleton-section-heading-left">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
</div>
<div class="skeleton-grid skeleton-grid-3">
{#each cardTriplet as _}
<article class="skeleton-plan-card skeleton-shimmer"></article>
{/each}
</div>
</div>
</section>
{/each}
<section class="skeleton-section skeleton-section-last">
<div class="skeleton-inner">
<div class="skeleton-booking skeleton-shimmer"></div>
</div>
</section>
</main>
{:else if variant === 'contact'}
<main class="skeleton-main">
<section class="skeleton-section skeleton-about-hero">
<div class="skeleton-inner skeleton-centered-heading">
<span class="skeleton-title skeleton-title-lg skeleton-shimmer"></span>
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
</div>
</section>
<section class="skeleton-section skeleton-section-last">
<div class="skeleton-inner">
<div class="skeleton-booking">
<div class="skeleton-booking-header">
<span class="skeleton-title skeleton-title-sm skeleton-shimmer"></span>
<div class="skeleton-row skeleton-stepper">
<span class="skeleton-chip skeleton-shimmer"></span>
<span class="skeleton-chip skeleton-shimmer"></span>
</div>
</div>
<div class="skeleton-grid skeleton-grid-2">
<article class="skeleton-form-card skeleton-shimmer"></article>
<article class="skeleton-form-card skeleton-shimmer"></article>
</div>
</div>
</div>
</section>
</main>
{:else}
<main class="skeleton-main">
<section class="skeleton-section skeleton-about-hero">
<div class="skeleton-inner skeleton-centered-heading">
<span class="skeleton-title skeleton-title-lg skeleton-shimmer"></span>
<span class="skeleton-line skeleton-line-short skeleton-shimmer"></span>
</div>
</section>
<section class="skeleton-section skeleton-section-last">
<div class="skeleton-inner">
<div class="skeleton-legal-card skeleton-shimmer">
{#each cardQuartet as _}
<span class="skeleton-line skeleton-line-long"></span>
{/each}
{#each heroLines as _}
<span class="skeleton-line"></span>
{/each}
</div>
</div>
</section>
</main>
{/if}
<footer class="skeleton-footer">
<div class="skeleton-inner skeleton-footer-grid">
<article class="skeleton-footer-card skeleton-shimmer"></article>
<article class="skeleton-footer-card skeleton-shimmer"></article>
<article class="skeleton-footer-card skeleton-shimmer"></article>
</div>
</footer>
</div>
</div>
<style>
.route-skeleton {
min-height: 100vh;
background:
radial-gradient(circle at top left, rgba(229, 214, 194, 0.45), transparent 34%),
linear-gradient(180deg, #fcfaf6 0%, #f7f3eb 100%);
color: transparent;
}
.skeleton-shell {
min-height: 100vh;
}
.skeleton-header {
position: sticky;
top: 0;
z-index: 1;
backdrop-filter: blur(14px);
background: rgba(252, 250, 246, 0.84);
border-bottom: 1px solid rgba(33, 48, 33, 0.08);
}
.skeleton-main,
.skeleton-footer {
padding: 0 0 40px;
}
.skeleton-section {
padding: 32px 0 0;
}
.skeleton-section-last {
padding-bottom: 20px;
}
.skeleton-inner {
max-width: var(--max-w);
margin: 0 auto;
padding: 0 50px;
}
.skeleton-nav,
.skeleton-row,
.skeleton-home-hero-grid,
.skeleton-about-grid,
.skeleton-footer-grid,
.skeleton-booking-header,
.skeleton-grid {
display: grid;
gap: 22px;
}
.skeleton-nav {
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
align-items: center;
min-height: 92px;
}
.skeleton-row {
display: flex;
align-items: center;
}
.skeleton-nav-links {
gap: 14px;
}
.skeleton-nav-actions {
gap: 14px;
justify-content: end;
}
.skeleton-logo {
display: block;
width: 188px;
height: 46px;
border-radius: 18px;
}
.skeleton-pill,
.skeleton-circle,
.skeleton-button,
.skeleton-logo,
.skeleton-kicker,
.skeleton-title,
.skeleton-line,
.skeleton-media,
.skeleton-strip,
.skeleton-card,
.skeleton-plan-card,
.skeleton-quote-card,
.skeleton-chip,
.skeleton-form-card,
.skeleton-booking,
.skeleton-contact-card,
.skeleton-legal-card,
.skeleton-footer-card {
display: block;
position: relative;
overflow: hidden;
background: rgba(33, 48, 33, 0.08);
}
.skeleton-shimmer::after {
content: '';
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.58) 50%,
rgba(255, 255, 255, 0) 100%
);
animation: skeleton-shimmer 1.25s ease-in-out infinite;
}
.skeleton-pill {
width: 88px;
height: 14px;
border-radius: 999px;
}
.skeleton-circle {
width: 40px;
height: 40px;
border-radius: 999px;
}
.skeleton-button {
width: 148px;
height: 48px;
border-radius: 999px;
}
.skeleton-button-wide {
width: 182px;
}
.skeleton-button-outline {
width: 154px;
}
.skeleton-home-hero,
.skeleton-service-hero,
.skeleton-about-hero {
padding-top: 52px;
}
.skeleton-home-hero-grid,
.skeleton-about-grid {
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
align-items: center;
}
.skeleton-about-grid-reverse {
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
}
.skeleton-copy {
display: grid;
gap: 16px;
align-content: start;
}
.skeleton-kicker {
width: 96px;
height: 14px;
border-radius: 999px;
}
.skeleton-title {
border-radius: 18px;
}
.skeleton-title-lg {
width: min(540px, 100%);
height: 64px;
}
.skeleton-title-md {
width: min(460px, 88%);
height: 54px;
}
.skeleton-title-sm {
width: min(320px, 70%);
height: 44px;
}
.skeleton-line {
width: min(620px, 100%);
height: 16px;
border-radius: 999px;
}
.skeleton-line-short {
width: min(420px, 72%);
}
.skeleton-line-medium {
width: 86%;
}
.skeleton-line-long {
width: 100%;
}
.skeleton-hero-actions {
gap: 16px;
margin-top: 10px;
}
.skeleton-media {
border-radius: 30px;
}
.skeleton-media-hero {
min-height: 500px;
}
.skeleton-media-tall {
min-height: 360px;
}
.skeleton-strip {
height: 112px;
border-radius: 30px;
}
.skeleton-section-heading {
display: grid;
gap: 16px;
justify-items: center;
margin-bottom: 30px;
}
.skeleton-section-heading-left {
justify-items: start;
}
.skeleton-centered-heading {
display: grid;
gap: 16px;
justify-items: center;
}
.skeleton-grid-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.skeleton-grid-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.skeleton-card,
.skeleton-plan-card,
.skeleton-quote-card,
.skeleton-form-card {
border-radius: 30px;
min-height: 220px;
}
.skeleton-card-short {
min-height: 180px;
}
.skeleton-plan-card {
min-height: 280px;
}
.skeleton-quote-card {
min-height: 190px;
padding: 28px;
display: grid;
align-content: start;
gap: 16px;
}
.skeleton-booking {
padding: 30px;
border-radius: 34px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(33, 48, 33, 0.08);
box-shadow: 0 16px 38px rgba(33, 48, 33, 0.06);
}
.skeleton-booking-header {
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
margin-bottom: 26px;
}
.skeleton-stepper {
gap: 12px;
}
.skeleton-chip {
width: 108px;
height: 38px;
border-radius: 999px;
}
.skeleton-form-card {
min-height: 260px;
}
.skeleton-contact-card {
min-height: 220px;
border-radius: 36px;
}
.skeleton-legal-card {
min-height: 420px;
border-radius: 36px;
padding: 36px;
display: grid;
align-content: start;
gap: 18px;
}
.skeleton-footer {
padding-top: 12px;
}
.skeleton-footer-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.skeleton-footer-card {
min-height: 210px;
border-radius: 30px;
}
@keyframes skeleton-shimmer {
100% {
transform: translateX(100%);
}
}
@media (prefers-reduced-motion: reduce) {
.skeleton-shimmer::after {
animation: none;
}
}
@media (max-width: 1024px) {
.skeleton-home-hero-grid,
.skeleton-about-grid,
.skeleton-about-grid-reverse,
.skeleton-grid-3,
.skeleton-footer-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.skeleton-home-hero-grid,
.skeleton-about-grid,
.skeleton-about-grid-reverse {
align-items: start;
}
}
@media (max-width: 768px) {
.skeleton-inner {
padding: 0 24px;
}
.skeleton-nav {
grid-template-columns: 1fr auto;
gap: 16px;
min-height: 78px;
}
.skeleton-nav-links {
display: none;
}
.skeleton-nav-actions .skeleton-circle {
display: none;
}
.skeleton-home-hero,
.skeleton-service-hero,
.skeleton-about-hero {
padding-top: 28px;
}
.skeleton-home-hero-grid,
.skeleton-about-grid,
.skeleton-about-grid-reverse,
.skeleton-grid-2,
.skeleton-grid-3,
.skeleton-footer-grid,
.skeleton-booking-header {
grid-template-columns: 1fr;
}
.skeleton-title-lg {
height: 46px;
}
.skeleton-title-md,
.skeleton-title-sm {
height: 34px;
}
.skeleton-media-hero {
min-height: 320px;
}
.skeleton-media-tall,
.skeleton-card,
.skeleton-quote-card,
.skeleton-plan-card,
.skeleton-form-card,
.skeleton-footer-card {
min-height: 180px;
}
.skeleton-strip {
height: 80px;
}
.skeleton-booking,
.skeleton-legal-card {
padding: 24px;
}
}
</style>