{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "case-study",
  "title": "Case Study",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/case-study/CaseStudyFeaturedStory.vue",
      "content": "<script setup lang=\"ts\">\nimport { ArrowRight, Quote } from 'lucide-vue-next'\nimport { Avatar, AvatarFallback } from '@/components/ui/avatar'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\n\nconst results = [\n  { value: '5 days', label: 'off the monthly close' },\n  { value: '11 → 1', label: 'reconciliation spreadsheets' },\n  { value: '340', label: 'certified metrics published' },\n  { value: '6 weeks', label: 'kickoff to full hand-over' },\n]\n\nconst facts = [\n  { term: 'Industry', detail: 'Freight forwarding' },\n  { term: 'Size', detail: '1,200 staff · 18 countries' },\n  { term: 'Stack', detail: 'Snowflake · dbt · Okta' },\n  { term: 'Live since', detail: 'Q1 2026' },\n]\n</script>\n\n<template>\n  <section data-slot=\"case-study-featured-story\" class=\"bg-background\">\n    <div class=\"mx-auto max-w-6xl px-6 py-20 lg:py-28\">\n      <div class=\"grid gap-12 lg:grid-cols-[1.4fr_1fr] lg:gap-16\">\n        <article>\n          <Badge variant=\"secondary\">Case study</Badge>\n          <h2 class=\"mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl\">\n            How Northwind Logistics closed the books five days early\n          </h2>\n\n          <div class=\"mt-8 space-y-8\">\n            <div>\n              <h3 class=\"text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase\">The challenge</h3>\n              <p class=\"mt-2.5 leading-relaxed\">\n                Month-end ran on eleven spreadsheets maintained by four people across three time zones. Every close\n                opened with a reconciliation pass nobody had budgeted for, and restatements were tracked in email.\n              </p>\n            </div>\n\n            <div>\n              <h3 class=\"text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase\">The approach</h3>\n              <p class=\"mt-2.5 leading-relaxed\">\n                We mirrored the warehouse schema, rebuilt revenue and margin as versioned definitions, and reconciled\n                them against the prior four quarters before anything was published. A single regional team piloted the\n                first close; the rest of finance moved over two weeks later.\n              </p>\n            </div>\n\n            <div>\n              <h3 class=\"text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase\">The result</h3>\n              <p class=\"mt-2.5 leading-relaxed\">\n                Close now starts from a reconciled position. Restatements open a reviewable change instead of a thread,\n                and the audit pack is generated from the same history.\n              </p>\n            </div>\n          </div>\n\n          <figure class=\"border-border mt-10 border-l-2 pl-6\">\n            <Quote class=\"text-muted-foreground/60 size-5\" aria-hidden=\"true\" />\n            <blockquote class=\"mt-3 text-lg leading-snug font-medium text-balance\">\n              We stopped arguing about whose number was right and started arguing about what to do next. That is the\n              whole change, and it was worth the six weeks.\n            </blockquote>\n            <figcaption class=\"mt-5 flex items-center gap-3\">\n              <Avatar class=\"size-9\">\n                <AvatarFallback class=\"text-xs\">EW</AvatarFallback>\n              </Avatar>\n              <div>\n                <p class=\"text-sm font-medium\">Erin Walsh</p>\n                <p class=\"text-muted-foreground text-xs\">VP Finance, Northwind Logistics</p>\n              </div>\n            </figcaption>\n          </figure>\n\n          <Button class=\"mt-10\">\n            Read the full story\n            <ArrowRight class=\"ml-2 size-4\" aria-hidden=\"true\" />\n          </Button>\n        </article>\n\n        <!-- Sticky results panel: keeps the numbers in view while the narrative scrolls. -->\n        <aside class=\"lg:sticky lg:top-24 lg:self-start\">\n          <Card>\n            <CardContent class=\"p-6\">\n              <h3 class=\"text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase\">Results</h3>\n              <dl class=\"mt-5 grid grid-cols-2 gap-x-4 gap-y-6\">\n                <div v-for=\"result in results\" :key=\"result.label\">\n                  <dt class=\"sr-only\">{{ result.label }}</dt>\n                  <dd>\n                    <span class=\"font-display block text-2xl font-bold tracking-tight\">{{ result.value }}</span>\n                    <span class=\"text-muted-foreground mt-1 block text-xs leading-snug\">{{ result.label }}</span>\n                  </dd>\n                </div>\n              </dl>\n\n              <Separator class=\"my-6\" />\n\n              <dl class=\"space-y-3\">\n                <div v-for=\"fact in facts\" :key=\"fact.term\" class=\"flex items-baseline justify-between gap-4 text-sm\">\n                  <dt class=\"text-muted-foreground\">{{ fact.term }}</dt>\n                  <dd class=\"text-right font-medium\">{{ fact.detail }}</dd>\n                </div>\n              </dl>\n            </CardContent>\n          </Card>\n        </aside>\n      </div>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/case-study/CaseStudyFeaturedStory.vue"
    },
    {
      "path": "packages/registry-vue/blocks/case-study/CaseStudyFilterableList.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { ArrowUpRight } from 'lucide-vue-next'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\nimport { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'\n\nconst studies = [\n  {\n    company: 'Northwind Logistics',\n    industry: 'Logistics',\n    result: '5 days off the monthly close',\n    stack: 'Snowflake · dbt · Okta',\n    readTime: '8 min',\n  },\n  {\n    company: 'Halden Health',\n    industry: 'Healthcare',\n    result: '71% fewer ad-hoc data requests',\n    stack: 'BigQuery · Airflow',\n    readTime: '6 min',\n  },\n  {\n    company: 'Verity Retail Group',\n    industry: 'Retail',\n    result: '2.4pp tighter forecast band',\n    stack: 'Redshift · dbt',\n    readTime: '7 min',\n  },\n  {\n    company: 'Calder Manufacturing',\n    industry: 'Manufacturing',\n    result: '11 spreadsheets retired',\n    stack: 'Postgres · Dagster',\n    readTime: '5 min',\n  },\n  {\n    company: 'Ridgeway Freight',\n    industry: 'Logistics',\n    result: 'Per-shipment landed cost',\n    stack: 'Snowflake · Fivetran',\n    readTime: '9 min',\n  },\n  {\n    company: 'Ashford Clinics',\n    industry: 'Healthcare',\n    result: 'PHI-scoped self-serve reporting',\n    stack: 'BigQuery · Entra ID',\n    readTime: '6 min',\n  },\n]\n\nconst ALL = 'All'\nconst industries = computed(() => [ALL, ...new Set(studies.map((study) => study.industry))])\nconst active = ref(ALL)\nconst visible = computed(() =>\n  active.value === ALL ? studies : studies.filter((study) => study.industry === active.value),\n)\n\n// Single-select emits '' when the pressed chip is pressed again; fall back to\n// ALL so the list is never empty by accident.\nfunction onChange(value: unknown) {\n  active.value = typeof value === 'string' && value ? value : ALL\n}\n</script>\n\n<template>\n  <section data-slot=\"case-study-filterable-list\" class=\"bg-background\">\n    <div class=\"mx-auto max-w-4xl px-6 py-20 lg:py-28\">\n      <div class=\"flex flex-wrap items-end justify-between gap-6\">\n        <div>\n          <Badge variant=\"secondary\">Customer stories</Badge>\n          <h2 class=\"mt-4 text-3xl font-semibold tracking-tight sm:text-4xl\">Read one close to your own</h2>\n        </div>\n\n        <ToggleGroup\n          :model-value=\"active\"\n          type=\"single\"\n          variant=\"outline\"\n          size=\"sm\"\n          class=\"flex-nowrap overflow-x-auto\"\n          aria-label=\"Filter case studies by industry\"\n          @update:model-value=\"onChange\"\n        >\n          <ToggleGroupItem v-for=\"industry in industries\" :key=\"industry\" :value=\"industry\">\n            {{ industry }}\n          </ToggleGroupItem>\n        </ToggleGroup>\n      </div>\n\n      <Card class=\"mt-8\">\n        <CardContent class=\"p-2\">\n          <div v-if=\"!visible.length\" class=\"px-4 py-16 text-center\">\n            <p class=\"text-sm font-medium\">No stories in that industry yet.</p>\n            <Button variant=\"outline\" size=\"sm\" class=\"mt-4\" @click=\"active = ALL\">Show all</Button>\n          </div>\n\n          <ul v-else>\n            <li v-for=\"(study, index) in visible\" :key=\"study.company\">\n              <a\n                href=\"#\"\n                class=\"hover:bg-muted focus-visible:ring-ring group flex items-center gap-4 rounded-lg p-4 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              >\n                <div class=\"min-w-0 flex-1\">\n                  <div class=\"flex flex-wrap items-center gap-2\">\n                    <p class=\"text-sm font-semibold\">{{ study.company }}</p>\n                    <Badge variant=\"outline\">{{ study.industry }}</Badge>\n                  </div>\n                  <p class=\"mt-1 text-sm\">{{ study.result }}</p>\n                  <p class=\"text-muted-foreground mt-1 font-mono text-xs\">{{ study.stack }}</p>\n                </div>\n                <span class=\"text-muted-foreground shrink-0 text-xs\">{{ study.readTime }}</span>\n                <ArrowUpRight\n                  class=\"text-muted-foreground size-4 shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5\"\n                  aria-hidden=\"true\"\n                />\n              </a>\n              <Separator v-if=\"index < visible.length - 1\" />\n            </li>\n          </ul>\n        </CardContent>\n      </Card>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/case-study/CaseStudyFilterableList.vue"
    },
    {
      "path": "packages/registry-vue/blocks/case-study/CaseStudyMetricCards.vue",
      "content": "<script setup lang=\"ts\">\nimport { ArrowUpRight } from 'lucide-vue-next'\nimport { Avatar, AvatarFallback } from '@/components/ui/avatar'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\n\nconst studies = [\n  {\n    metric: '5 days',\n    metricLabel: 'off the monthly close',\n    company: 'Northwind Logistics',\n    industry: 'Freight forwarding · 1,200 staff',\n    outcome: 'Replaced eleven reconciliation spreadsheets with one certified revenue model.',\n    quote: 'We stopped arguing about whose number was right and started arguing about what to do next.',\n    author: { name: 'Erin Walsh', role: 'VP Finance', initials: 'EW' },\n  },\n  {\n    metric: '71%',\n    metricLabel: 'fewer ad-hoc data requests',\n    company: 'Halden Health',\n    industry: 'Provider network · 40 clinics',\n    outcome: 'Clinic leads self-serve their own quality metrics without filing a ticket.',\n    quote: 'The analytics team went from a request queue to actually building things again.',\n    author: { name: 'Daniel Brooks', role: 'Head of Analytics', initials: 'DB' },\n  },\n  {\n    metric: '2.4pp',\n    metricLabel: 'tighter forecast accuracy',\n    company: 'Verity Retail Group',\n    industry: 'Omnichannel retail · 310 stores',\n    outcome: 'Sales and finance now forecast off the same pipeline definition, snapshotted weekly.',\n    quote: 'Forecast review stopped opening with twenty minutes of reconciling two decks.',\n    author: { name: 'Priya Raman', role: 'RevOps Director', initials: 'PR' },\n  },\n]\n</script>\n\n<template>\n  <section data-slot=\"case-study-metric-cards\" class=\"bg-background\">\n    <div class=\"mx-auto max-w-6xl px-6 py-20 lg:py-28\">\n      <div class=\"flex flex-wrap items-end justify-between gap-6\">\n        <div class=\"max-w-2xl\">\n          <Badge variant=\"secondary\">Customer results</Badge>\n          <h2 class=\"mt-4 text-3xl font-semibold tracking-tight sm:text-4xl\">What changed after the rollout</h2>\n          <p class=\"text-muted-foreground mt-3 text-lg\">\n            Three teams, measured against their own numbers from the quarter before.\n          </p>\n        </div>\n        <Button variant=\"outline\">All case studies</Button>\n      </div>\n\n      <div class=\"mt-10 grid gap-4 md:grid-cols-3\">\n        <Card v-for=\"study in studies\" :key=\"study.company\" class=\"group\">\n          <CardContent class=\"flex h-full flex-col p-6\">\n            <!-- Metric leads: it is the only part that survives a skim. -->\n            <p class=\"font-display text-4xl font-bold tracking-tight\">{{ study.metric }}</p>\n            <p class=\"text-muted-foreground mt-1 text-sm\">{{ study.metricLabel }}</p>\n\n            <Separator class=\"my-5\" />\n\n            <p class=\"text-sm font-semibold\">{{ study.company }}</p>\n            <p class=\"text-muted-foreground mt-0.5 text-xs\">{{ study.industry }}</p>\n            <p class=\"mt-4 text-sm leading-relaxed\">{{ study.outcome }}</p>\n\n            <blockquote class=\"border-border text-muted-foreground mt-5 border-l-2 pl-4 text-sm italic\">\n              “{{ study.quote }}”\n            </blockquote>\n\n            <div class=\"mt-auto flex items-center gap-3 pt-6\">\n              <Avatar class=\"size-8\">\n                <AvatarFallback class=\"text-xs\">{{ study.author.initials }}</AvatarFallback>\n              </Avatar>\n              <div class=\"min-w-0\">\n                <p class=\"truncate text-sm font-medium\">{{ study.author.name }}</p>\n                <p class=\"text-muted-foreground truncate text-xs\">{{ study.author.role }}</p>\n              </div>\n              <Button variant=\"ghost\" size=\"icon\" class=\"ml-auto\" :aria-label=\"`Read the ${study.company} case study`\">\n                <ArrowUpRight\n                  class=\"size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5\"\n                  aria-hidden=\"true\"\n                />\n              </Button>\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/case-study/CaseStudyMetricCards.vue"
    },
    {
      "path": "packages/registry-vue/blocks/case-study/CaseStudyMetricComparison.vue",
      "content": "<script setup lang=\"ts\">\nimport { ArrowRight, TrendingDown, TrendingUp } from 'lucide-vue-next'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\n\n// `better` records which direction is good, so the delta colour never has to be\n// hard-coded per row — a falling close time is good, falling accuracy is not.\nconst metrics = [\n  { name: 'Days to close', before: '9.4', after: '4.2', delta: '−55%', better: 'down' },\n  { name: 'Ad-hoc data requests / month', before: '212', after: '61', delta: '−71%', better: 'down' },\n  { name: 'Certified metrics published', before: '0', after: '340', delta: '+340', better: 'up' },\n  { name: 'Forecast accuracy band', before: '±6.1pp', after: '±3.7pp', delta: '−2.4pp', better: 'down' },\n]\n</script>\n\n<template>\n  <section data-slot=\"case-study-metric-comparison\" class=\"bg-background\">\n    <div class=\"mx-auto max-w-4xl px-6 py-20 lg:py-28\">\n      <Badge variant=\"secondary\">Northwind Logistics</Badge>\n      <h2 class=\"mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl\">\n        Two quarters either side of the rollout\n      </h2>\n      <p class=\"text-muted-foreground mt-3 text-lg\">\n        Their numbers, their definitions, measured the same way before and after. Nothing normalised.\n      </p>\n\n      <Card class=\"mt-8\">\n        <CardContent class=\"p-0\">\n          <Table>\n            <TableHeader>\n              <TableRow>\n                <TableHead>Metric</TableHead>\n                <TableHead class=\"text-right\">Q4 2025</TableHead>\n                <TableHead class=\"text-right\">Q2 2026</TableHead>\n                <TableHead class=\"text-right\">Change</TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              <TableRow v-for=\"metric in metrics\" :key=\"metric.name\">\n                <TableCell class=\"font-medium\">{{ metric.name }}</TableCell>\n                <TableCell class=\"text-muted-foreground text-right font-mono text-sm\">{{ metric.before }}</TableCell>\n                <TableCell class=\"text-right font-mono text-sm font-semibold\">{{ metric.after }}</TableCell>\n                <TableCell class=\"text-right\">\n                  <span class=\"text-success inline-flex items-center gap-1.5 font-mono text-sm\">\n                    <component\n                      :is=\"metric.better === 'down' ? TrendingDown : TrendingUp\"\n                      class=\"size-3.5\"\n                      aria-hidden=\"true\"\n                    />\n                    {{ metric.delta }}\n                  </span>\n                </TableCell>\n              </TableRow>\n            </TableBody>\n          </Table>\n\n          <Separator />\n\n          <p class=\"text-muted-foreground px-6 py-4 text-xs leading-relaxed\">\n            Measured from their own close calendar and ticket queue. The rollout completed between the two quarters; no\n            headcount was added or removed in finance during the period.\n          </p>\n        </CardContent>\n      </Card>\n\n      <Button class=\"mt-8\">\n        Read how they did it\n        <ArrowRight class=\"ml-2 size-4\" aria-hidden=\"true\" />\n      </Button>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/case-study/CaseStudyMetricComparison.vue"
    },
    {
      "path": "packages/registry-vue/blocks/case-study/CaseStudyResultsStrip.vue",
      "content": "<script setup lang=\"ts\">\nimport { ArrowRight } from 'lucide-vue-next'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\n\n// One result per customer. A second number halves the weight of the first.\nconst results = [\n  { wordmark: 'Northwind', metric: '5 days', label: 'off the monthly close' },\n  { wordmark: 'Halden', metric: '71%', label: 'fewer ad-hoc requests' },\n  { wordmark: 'Verity', metric: '2.4pp', label: 'tighter forecast band' },\n  { wordmark: 'Calder', metric: '11 → 1', label: 'reporting spreadsheets' },\n]\n</script>\n\n<template>\n  <section data-slot=\"case-study-results-strip\" class=\"border-border bg-card border-y\">\n    <div class=\"mx-auto max-w-6xl px-6 py-10\">\n      <div class=\"flex flex-wrap items-center justify-between gap-4\">\n        <Badge variant=\"secondary\">Proven</Badge>\n        <Button variant=\"link\" class=\"h-auto p-0 text-sm\">\n          All case studies\n          <ArrowRight class=\"ml-1.5 size-3.5\" aria-hidden=\"true\" />\n        </Button>\n      </div>\n\n      <div class=\"mt-6 grid gap-6 sm:grid-cols-2 lg:grid-cols-4 lg:gap-0\">\n        <div v-for=\"(result, index) in results\" :key=\"result.wordmark\" class=\"relative lg:px-6 lg:first:pl-0\">\n          <p class=\"text-sm font-semibold tracking-[0.18em] uppercase\">{{ result.wordmark }}</p>\n          <p class=\"font-display mt-3 text-3xl font-bold tracking-tight\">{{ result.metric }}</p>\n          <p class=\"text-muted-foreground mt-1 text-sm\">{{ result.label }}</p>\n\n          <Separator\n            v-if=\"index < results.length - 1\"\n            orientation=\"vertical\"\n            class=\"absolute top-0 right-0 hidden h-full lg:block\"\n          />\n        </div>\n      </div>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/case-study/CaseStudyResultsStrip.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/avatar.json",
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/table.json",
    "https://uipkge.dev/r/vue/toggle-group.json"
  ],
  "description": "Customer-proof set pairing a featured split story with a sticky results panel, a filterable story index, three-up metric cards, a before-and-after metric comparison table, and a compact results strip.",
  "categories": [
    "marketing"
  ]
}