9643fe519e
The hero's right column now spreads vertically — count + label pinned at the top, action button(s) flushed to the bottom-right corner of the card via margin-top: auto. Row uses align-items: stretch so the side column reaches the full hero height regardless of how tall the left column gets (long descriptions, many pills, etc.). Single CSS change in the shared component, so it applies to every page that uses PageHeader (all 14 of them) — no per-page edits.
223 lines
5.3 KiB
Svelte
223 lines
5.3 KiB
Svelte
<script lang="ts">
|
|
import type { Snippet } from 'svelte';
|
|
|
|
export interface HeaderPill {
|
|
label: string;
|
|
tone?: 'mint' | 'sky' | 'orchid' | 'coral' | 'citrus' | 'primary';
|
|
icon?: string;
|
|
}
|
|
|
|
interface Props {
|
|
title: string;
|
|
/** Italic-emphasized word(s) appended to the title with a gradient. */
|
|
emphasis?: string;
|
|
/** Body text under the title. */
|
|
description?: string;
|
|
/** Small label above the title (breadcrumb / section). */
|
|
crumb?: string;
|
|
/** Right-side count meter — e.g. "12 providers". */
|
|
count?: number | string;
|
|
/** Label under the count, e.g. "providers". */
|
|
countLabel?: string;
|
|
/** Status pills shown beneath the description. */
|
|
pills?: HeaderPill[];
|
|
/** Primary actions (buttons) — rendered top-right next to the meter. */
|
|
children?: Snippet;
|
|
}
|
|
|
|
let {
|
|
title,
|
|
emphasis = '',
|
|
description = '',
|
|
crumb = '',
|
|
count,
|
|
countLabel = '',
|
|
pills = [],
|
|
children,
|
|
}: Props = $props();
|
|
|
|
const toneColors: Record<NonNullable<HeaderPill['tone']>, string> = {
|
|
mint: 'var(--color-mint)',
|
|
sky: 'var(--color-sky)',
|
|
orchid: 'var(--color-orchid)',
|
|
coral: 'var(--color-coral)',
|
|
citrus: 'var(--color-citrus)',
|
|
primary: 'var(--color-primary)',
|
|
};
|
|
</script>
|
|
|
|
<section class="subpage-hero">
|
|
<div class="subpage-hero__row">
|
|
<div class="subpage-hero__main">
|
|
{#if crumb}
|
|
<div class="subpage-hero__crumb">{crumb}</div>
|
|
{/if}
|
|
<h2 class="subpage-hero__title">
|
|
{title}{#if emphasis} <em>{emphasis}</em>{/if}
|
|
</h2>
|
|
{#if description}
|
|
<p class="subpage-hero__sub">{description}</p>
|
|
{/if}
|
|
{#if pills.length > 0}
|
|
<div class="subpage-hero__pills">
|
|
{#each pills as p}
|
|
<span class="subpage-hero__pill">
|
|
<span class="subpage-hero__pill-dot" style="background: {toneColors[p.tone ?? 'primary']}"></span>
|
|
{p.label}
|
|
</span>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="subpage-hero__side">
|
|
{#if count !== undefined}
|
|
<div class="subpage-hero__meter">
|
|
<div class="subpage-hero__meter-value font-mono">{count}</div>
|
|
{#if countLabel}
|
|
<div class="subpage-hero__meter-label">{countLabel}</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if children}
|
|
<div class="subpage-hero__actions">{@render children()}</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
.subpage-hero {
|
|
position: relative;
|
|
background: var(--color-glass);
|
|
backdrop-filter: blur(28px) saturate(160%);
|
|
-webkit-backdrop-filter: blur(28px) saturate(160%);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 22px;
|
|
box-shadow: var(--shadow-card);
|
|
padding: 1.4rem 1.6rem 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
overflow: hidden;
|
|
}
|
|
.subpage-hero::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
pointer-events: none;
|
|
background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
|
|
opacity: 0.4;
|
|
}
|
|
.subpage-hero__row {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
align-items: stretch;
|
|
justify-content: space-between;
|
|
gap: 1.5rem;
|
|
flex-wrap: wrap;
|
|
min-height: 100%;
|
|
}
|
|
.subpage-hero__main { min-width: 0; flex: 1; }
|
|
|
|
.subpage-hero__crumb {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.62rem;
|
|
color: var(--color-muted-foreground);
|
|
letter-spacing: 0.18em;
|
|
text-transform: uppercase;
|
|
margin-bottom: 0.55rem;
|
|
font-weight: 500;
|
|
}
|
|
.subpage-hero__title {
|
|
font-family: var(--font-display);
|
|
font-weight: 400;
|
|
font-size: 2.15rem;
|
|
line-height: 1.05;
|
|
letter-spacing: -0.025em;
|
|
color: var(--color-foreground);
|
|
margin: 0;
|
|
}
|
|
.subpage-hero__title em {
|
|
font-style: italic;
|
|
background: linear-gradient(135deg, var(--color-orchid), var(--color-primary) 60%, var(--color-sky));
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
.subpage-hero__sub {
|
|
font-size: 0.88rem;
|
|
color: var(--color-muted-foreground);
|
|
margin: 0.55rem 0 0;
|
|
line-height: 1.55;
|
|
max-width: 60ch;
|
|
}
|
|
.subpage-hero__pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
margin-top: 0.85rem;
|
|
}
|
|
.subpage-hero__pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.22rem 0.65rem;
|
|
border-radius: 999px;
|
|
background: var(--color-glass-strong);
|
|
border: 1px solid var(--color-border);
|
|
font-size: 0.7rem;
|
|
color: var(--color-muted-foreground);
|
|
font-weight: 500;
|
|
}
|
|
.subpage-hero__pill-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.subpage-hero__side {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
flex-shrink: 0;
|
|
}
|
|
.subpage-hero__meter {
|
|
text-align: right;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
}
|
|
.subpage-hero__actions {
|
|
margin-top: auto;
|
|
padding-top: 0.95rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
}
|
|
.subpage-hero__meter-value {
|
|
font-size: 2.15rem;
|
|
font-weight: 500;
|
|
color: var(--color-foreground);
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1;
|
|
letter-spacing: -0.025em;
|
|
}
|
|
.subpage-hero__meter-label {
|
|
font-size: 0.62rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.16em;
|
|
color: var(--color-muted-foreground);
|
|
margin-top: 0.4rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.subpage-hero { padding: 1.1rem 1.2rem 1.25rem; }
|
|
.subpage-hero__title { font-size: 1.7rem; }
|
|
.subpage-hero__row { flex-direction: column; align-items: stretch; }
|
|
.subpage-hero__side { justify-content: space-between; }
|
|
}
|
|
</style>
|