Feature Deep-Dive — Alternating Rows
blockmarketingClassic zig-zag feature deep-dive: three full-width rows alternating copy and visual, each with an eyebrow, headline, checklist, inline link, and a bordered preview panel built from real primitives.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/feature-alternating-rows.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/feature-alternating-rows.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/feature-alternating-rows.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/feature-alternating-rows.jsonManifest:
https://uipkge.dev/r/svelte/feature-alternating-rows.jsonInstalls to:src/lib/components/blocks/feature-alternating-rows/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (1)
src/lib/components/blocks/feature-alternating-rows/FeatureAlternatingRows.svelte6.8 kB
<script lang="ts" module> import type { Component } from 'svelte' // Explicit shape: the three preview panels are structurally different, so the // optional keys keep the union from collapsing when the template narrows. export interface FeatureRow { eyebrow: string icon: Component title: string body: string points: string[] link: string reverse: boolean preview: { label: string lines?: { text: string; tone: 'add' | 'remove' | 'same' }[] rules?: { scope: string; value: string; allowed: boolean }[] stats?: { name: string; detail: string }[] } } </script> <script lang="ts"> import { ArrowRight, Check, GitBranch, ShieldCheck, Zap } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { Separator } from '$lib/components/ui/separator' const rows: FeatureRow[] = [ { eyebrow: 'Versioning', icon: GitBranch, title: 'Every change is a reviewable diff', body: 'Dashboards, metrics, and permissions are stored as plain files. Changes open a pull request instead of silently overwriting production.', points: ['Branch per change', 'Review before publish', 'One-click revert'], link: 'How version control works', reverse: false, preview: { label: 'metrics/revenue.yml', lines: [ { text: '+ window: trailing_28d', tone: 'add' }, { text: '- window: trailing_30d', tone: 'remove' }, { text: ' owner: finance-analytics', tone: 'same' }, ], }, }, { eyebrow: 'Access', icon: ShieldCheck, title: 'Permissions that follow the row, not the dashboard', body: 'Scope is evaluated at query time from your identity provider, so a shared link never leaks a region or account the viewer cannot see.', points: ['Row-level filters', 'SCIM group sync', 'Audited every query'], link: 'Read the access model', reverse: true, preview: { label: 'Effective access — EMEA analyst', rules: [ { scope: 'region', value: 'EMEA only', allowed: true }, { scope: 'revenue.net', value: 'Visible', allowed: true }, { scope: 'payroll.*', value: 'Blocked', allowed: false }, ], }, }, { eyebrow: 'Performance', icon: Zap, title: 'Cached where it matters, fresh where it counts', body: 'Hot aggregates are materialised on a schedule you control. Everything else falls through to the warehouse so the number is never stale by accident.', points: ['Per-metric freshness', 'Warehouse pass-through', 'Cost ceiling per team'], link: 'See the caching rules', reverse: false, preview: { label: 'Freshness', stats: [ { name: 'Revenue rollup', detail: 'materialised · 5 min' }, { name: 'Pipeline by stage', detail: 'materialised · 1 hr' }, { name: 'Raw event search', detail: 'live query' }, ], }, }, ] function toneClass(tone: 'add' | 'remove' | 'same') { return tone === 'add' ? 'text-success' : tone === 'remove' ? 'text-destructive' : 'text-muted-foreground' } </script> <section data-slot="feature-alternating-rows" class="bg-background"> <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div class="max-w-2xl"> <Badge variant="secondary">Platform</Badge> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Built like infrastructure, not a widget</h2> <p class="text-muted-foreground mt-3 text-lg"> Three decisions that stop a reporting layer from rotting after the first quarter. </p> </div> <div class="mt-16 space-y-16 lg:space-y-24"> {#each rows as row (row.title)} {@const Icon = row.icon} <div class="grid items-center gap-10 lg:grid-cols-2 lg:gap-16"> <div class:lg:order-2={row.reverse}> <div class="text-muted-foreground flex items-center gap-2"> <Icon class="size-4" aria-hidden="true" /> <span class="font-mono text-xs tracking-[0.14em] uppercase">{row.eyebrow}</span> </div> <h3 class="mt-4 text-2xl font-semibold tracking-tight">{row.title}</h3> <p class="text-muted-foreground mt-3 leading-relaxed">{row.body}</p> <ul class="mt-6 space-y-2.5"> {#each row.points as point (point)} <li class="flex items-start gap-2.5 text-sm"> <Check class="text-primary mt-0.5 size-4 shrink-0" aria-hidden="true" /> <span>{point}</span> </li> {/each} </ul> <Button variant="link" class="mt-5 h-auto p-0"> {row.link} <ArrowRight class="ml-1.5 size-3.5" aria-hidden="true" /> </Button> </div> <Card class={row.reverse ? 'lg:order-1' : ''}> <CardContent class="p-0"> <div class="border-border text-muted-foreground border-b px-4 py-2.5 font-mono text-xs"> {row.preview.label} </div> <!-- Diff preview --> {#if row.preview.lines} <div class="space-y-1 p-4 font-mono text-xs"> {#each row.preview.lines as line (line.text)} <p class={toneClass(line.tone)}> {line.text} </p> {/each} </div> <!-- Access preview --> {:else if row.preview.rules} <div class="divide-border divide-y"> {#each row.preview.rules as rule (rule.scope)} <div class="flex items-center justify-between gap-4 px-4 py-3"> <span class="font-mono text-xs">{rule.scope}</span> <Badge variant={rule.allowed ? 'secondary' : 'outline'}>{rule.value}</Badge> </div> {/each} </div> <!-- Freshness preview --> {:else} <div class="p-4"> <ul class="space-y-3"> {#each (row.preview.stats ?? []) as stat (stat.name)} <li> <div class="flex items-baseline justify-between gap-4"> <span class="text-sm font-medium">{stat.name}</span> <span class="text-muted-foreground font-mono text-xs">{stat.detail}</span> </div> <Separator class="mt-3" /> </li> {/each} </ul> </div> {/if} </CardContent> </Card> </div> {/each} </div> </div> </section>
Raw manifest:https://uipkge.dev/r/svelte/feature-alternating-rows.json