{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "blog-post-1",
  "title": "Blog Post 1: article with contents and progress",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A long-form article page: centered title and author row, cover, comfortable prose with pull quote and code, sticky table of contents with scroll spy, reading progress bar, author card and related posts.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/blog-1.json",
    "https://ui.ballmac.com/r/breadcrumb.json",
    "https://ui.ballmac.com/r/copy-button.json",
    "https://ui.ballmac.com/r/scroll-progress.json",
    "https://ui.ballmac.com/r/table-of-contents.json",
    "https://ui.ballmac.com/r/i18n.json",
    "https://ui.ballmac.com/r/media.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/blog-post-1/blog-post-1.tsx",
      "content": "// Ballmac UI: Blog Post 1. https://ui.ballmac.com/blocks/blog-post-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { Clock } from \"lucide-react\"\n\nimport { BlogCover } from \"@/components/ballmac/blocks/blog-1/blog-1\"\nimport { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from \"@/components/ballmac/breadcrumb\"\nimport { CopyButton } from \"@/components/ballmac/copy-button\"\nimport { ScrollProgress } from \"@/components/ballmac/scroll-progress\"\nimport { TableOfContents, type TocItem } from \"@/components/ballmac/table-of-contents\"\nimport { cn } from \"@/lib/utils\"\nimport { useLocale } from \"@/lib/ballmac/i18n\"\nimport { Media, type MediaSource } from \"@/components/ballmac/media\"\n\ntype BlogPostRelated = { title: string; category: string; href: string; readMinutes: number }\n\ntype BlogPost1Props = Omit<React.ComponentProps<\"article\">, \"title\"> & {\n  /** Topic shown in the breadcrumb and above the title. */\n  category?: string\n  /** The article title. */\n  title?: string\n  /** One or two sentences under the title. */\n  subtitle?: string\n  /** Publication date as an ISO string (YYYY-MM-DD), shown in UTC. */\n  date?: string\n  /** Estimated minutes to read. */\n  readMinutes?: number\n  /** Author's name. */\n  author?: string\n  /** Author's role. */\n  role?: string\n  /** One sentence about the author, shown in the card after the article. */\n  authorBio?: string\n  /** Absolute link copied by the share button. */\n  url?: string\n  /** Cover photo: an image URL (give it imageAlt), an object with alt text and a dark-mode file, or your own element. A generated cover is drawn when omitted. */\n  image?: MediaSource\n  /** Describes `image` when it is a plain URL. */\n  imageAlt?: string\n  /** Which generated cover to draw (0 to 4). */\n  cover?: number\n  /** Links for \"On this page\". The ids must match headings in `children`. */\n  toc?: TocItem[]\n  /** Pixels reserved for a sticky site header, for the contents list and anchor scrolling. */\n  stickyOffset?: number\n  /** More posts to read. Pass an empty array to hide the section. */\n  related?: BlogPostRelated[]\n  /** Link back to the blog index. */\n  blogHref?: string\n  /** Locale for the date. Fixed by default so server and browser match. */\n  locale?: string\n  /** The article body. Plain h2, h3, p, ul, ol, blockquote, pre and a elements are styled for you. */\n  children?: React.ReactNode\n}\n\nconst proseClass = cn(\n  \"text-foreground\",\n  \"[&_h2]:mt-14 [&_h2]:scroll-mt-28 [&_h2]:text-2xl [&_h2]:font-semibold [&_h2]:tracking-[-0.025em] [&_h2]:text-balance sm:[&_h2]:text-[1.7rem]\",\n  \"[&_h3]:mt-10 [&_h3]:scroll-mt-28 [&_h3]:text-xl [&_h3]:font-semibold [&_h3]:tracking-[-0.02em]\",\n  \"[&_p]:mt-5 [&_p]:text-[1.0625rem] [&_p]:leading-8 [&_p]:text-pretty\",\n  \"[&_ul]:mt-5 [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:ps-6 [&_ul]:text-[1.0625rem] [&_ul]:leading-8 [&_ul]:marker:text-muted-foreground\",\n  \"[&_ol]:mt-5 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:ps-6 [&_ol]:text-[1.0625rem] [&_ol]:leading-8 [&_ol]:marker:text-muted-foreground\",\n  \"[&_a]:font-medium [&_a]:underline [&_a]:decoration-border [&_a]:underline-offset-4 hover:[&_a]:decoration-foreground\",\n  \"[&_blockquote]:my-10 [&_blockquote]:border-s-2 [&_blockquote]:border-foreground [&_blockquote]:ps-6 [&_blockquote]:text-2xl [&_blockquote]:leading-snug [&_blockquote]:font-medium [&_blockquote]:tracking-[-0.02em] [&_blockquote]:text-balance\",\n  \"[&_pre]:mt-6 [&_pre]:overflow-x-auto [&_pre]:rounded-2xl [&_pre]:border [&_pre]:bg-muted/50 [&_pre]:p-5 [&_pre]:font-mono [&_pre]:text-[13px] [&_pre]:leading-6\",\n  \"[&_code:not(pre_code)]:rounded-md [&_code:not(pre_code)]:bg-muted [&_code:not(pre_code)]:px-1.5 [&_code:not(pre_code)]:py-0.5 [&_code:not(pre_code)]:font-mono [&_code:not(pre_code)]:text-[0.9em]\",\n  \"[&_hr]:my-12\"\n)\n\nconst defaultToc: TocItem[] = [\n  { id: \"the-problem\", title: \"The problem with five days\", level: 2 },\n  { id: \"three-changes\", title: \"Three changes that mattered\", level: 2 },\n  { id: \"approvals-in-flight\", title: \"Approvals in flight\", level: 3 },\n  { id: \"one-source-of-truth\", title: \"One source of truth\", level: 3 },\n  { id: \"what-we-measured\", title: \"What we measured\", level: 2 },\n  { id: \"what-is-next\", title: \"What is next\", level: 2 },\n]\n\nfunction DefaultBody() {\n  return (\n    <>\n      <p>\n        For years our own books took five working days to close. Nobody was slow; the work was scattered across inboxes, spreadsheets and a shared drive with\n        a folder structure only two people understood. This is the story of how we got it to one day, and what we changed in the product along the way.\n      </p>\n      <h2 id=\"the-problem\">The problem with five days</h2>\n      <p>\n        When we mapped the close, only about a fifth of the elapsed time was actual work. The rest was waiting: for a receipt, for an approval, for someone\n        to notice that a number looked off. Every wait was small, and together they were the whole week.\n      </p>\n      <blockquote>Most of a close is waiting. The fastest teams are not working harder, they are waiting less.</blockquote>\n      <h2 id=\"three-changes\">Three changes that mattered</h2>\n      <p>We resisted the urge to automate everything. Three changes did almost all of the work.</p>\n      <h3 id=\"approvals-in-flight\">Approvals in flight</h3>\n      <p>\n        Instead of a monthly approval sweep, approvals happen the moment an invoice is coded. A reviewer sees a small queue each morning rather than a\n        mountain on day three.\n      </p>\n      <h3 id=\"one-source-of-truth\">One source of truth</h3>\n      <p>\n        We deleted the side spreadsheets. Every figure in the close now links back to the transaction that produced it, so questions are answered by\n        clicking, not by emailing.\n      </p>\n      <pre>\n        <code>{`close.status(\"2026-09\")\\n// → { days: 1, open: 0, flagged: 2 }`}</code>\n      </pre>\n      <h2 id=\"what-we-measured\">What we measured</h2>\n      <ul>\n        <li>Median time to close fell from 5.1 days to 1.2 days over four months.</li>\n        <li>Late adjustments dropped by about two thirds.</li>\n        <li>The finance team reported spending most of their time on analysis rather than chasing.</li>\n      </ul>\n      <h2 id=\"what-is-next\">What is next</h2>\n      <p>\n        We are turning the pieces that helped us most into defaults for everyone. If you want to try them early, <a href=\"#\">join the beta</a> and tell us what\n        your own close looks like.\n      </p>\n    </>\n  )\n}\n\nconst defaultRelated: BlogPostRelated[] = [\n  { title: \"Designing calm software for busy teams\", category: \"Design\", href: \"#\", readMinutes: 6 },\n  { title: \"What 2,000 invoices taught us about getting paid\", category: \"Research\", href: \"#\", readMinutes: 7 },\n  { title: \"Inside our new audit log\", category: \"Engineering\", href: \"#\", readMinutes: 11 },\n]\n\nfunction BlogPost1({\n  category = \"Product\",\n  title = \"How we cut month-end close from five days to one\",\n  subtitle = \"Three workflow changes, one deleted spreadsheet and the dashboards that made them stick.\",\n  date = \"2026-09-24\",\n  readMinutes = 8,\n  author = \"Priya Raman\",\n  role = \"CEO\",\n  authorBio = \"Priya leads Acme. Before that she ran finance operations at two growth-stage companies and has closed more books than she cares to count.\",\n  url = \"https://acme.com/blog/month-end-close\",\n  image,\n  imageAlt,\n  cover = 0,\n  toc = defaultToc,\n  stickyOffset = 24,\n  related = defaultRelated,\n  blogHref = \"#\",\n  locale,\n  children,\n  className,\n  ...props\n}: BlogPost1Props) {\n  const defaultLocale = useLocale()\n  locale ??= defaultLocale\n  const formatted = new Intl.DateTimeFormat(locale, { dateStyle: \"long\", timeZone: \"UTC\" }).format(new Date(`${date}T00:00:00Z`))\n  const initials = author.split(\" \").map((w) => w[0]).join(\"\").slice(0, 2)\n  return (\n    <>\n      <ScrollProgress />\n      <article data-slot=\"blog-post-1\" className={cn(\"mx-auto max-w-6xl px-4 py-12 sm:px-6 md:py-20\", className)} {...props}>\n        <header className=\"mx-auto max-w-3xl text-center\">\n          <Breadcrumb className=\"flex justify-center\">\n            <BreadcrumbList>\n              <BreadcrumbItem><BreadcrumbLink href={blogHref}>Blog</BreadcrumbLink></BreadcrumbItem>\n              <BreadcrumbSeparator />\n              <BreadcrumbItem><BreadcrumbPage>{category}</BreadcrumbPage></BreadcrumbItem>\n            </BreadcrumbList>\n          </Breadcrumb>\n          <h1 className=\"mt-6 text-4xl font-semibold tracking-[-0.04em] text-balance sm:text-5xl lg:text-6xl lg:leading-[1.05]\">{title}</h1>\n          <p className=\"text-muted-foreground mt-5 text-lg text-pretty sm:text-xl\">{subtitle}</p>\n          <div className=\"mt-8 flex flex-wrap items-center justify-center gap-x-4 gap-y-3 text-sm\">\n            <div className=\"flex items-center gap-3\">\n              <span aria-hidden=\"true\" className=\"bg-chart-1/25 flex size-9 items-center justify-center rounded-full text-xs font-semibold\">{initials}</span>\n              <p className=\"text-start leading-tight\">\n                <span className=\"font-medium\">{author}</span>\n                <span className=\"text-muted-foreground block\">{role}</span>\n              </p>\n            </div>\n            <span aria-hidden=\"true\" className=\"bg-border hidden h-6 w-px sm:block\" />\n            <p className=\"text-muted-foreground flex items-center gap-2\">\n              <time dateTime={date}>{formatted}</time>\n              <span aria-hidden=\"true\">·</span>\n              <span className=\"inline-flex items-center gap-1\"><Clock className=\"size-3.5\" aria-hidden=\"true\" />{readMinutes} min read</span>\n            </p>\n            <CopyButton value={url} label=\"Copy link\" copiedLabel=\"Link copied\" variant=\"outline\" size=\"sm\" />\n          </div>\n        </header>\n\n        <div className=\"mx-auto mt-12 max-w-5xl overflow-hidden rounded-3xl border\">\n          {image ? (\n            <Media media={image} alt={imageAlt} aspect=\"21/9\" priority className=\"w-full\" />\n          ) : (\n            <BlogCover variant={cover} className=\"aspect-[16/9] w-full sm:aspect-[21/9]\" />\n          )}\n        </div>\n\n        <div className=\"mx-auto mt-12 grid max-w-5xl gap-12 lg:grid-cols-[minmax(0,42rem)_14rem] lg:justify-between\">\n          <div className=\"min-w-0\">\n            <div className={proseClass}>{children ?? <DefaultBody />}</div>\n\n            <aside aria-label=\"About the author\" className=\"bg-muted/40 mt-16 flex gap-4 rounded-3xl border p-6\">\n              <span aria-hidden=\"true\" className=\"bg-chart-1/25 flex size-12 shrink-0 items-center justify-center rounded-full text-sm font-semibold\">{initials}</span>\n              <div>\n                <p className=\"font-semibold\">Written by {author}</p>\n                <p className=\"text-muted-foreground mt-1 text-sm leading-relaxed text-pretty\">{authorBio}</p>\n              </div>\n            </aside>\n          </div>\n          {toc.length > 0 && (\n            <div className=\"hidden lg:block\">\n              <div className=\"sticky\" style={{ top: stickyOffset }}>\n                <TableOfContents items={toc} offset={stickyOffset + 72} />\n              </div>\n            </div>\n          )}\n        </div>\n\n        {related.length > 0 && (\n          <section aria-labelledby=\"related-heading\" className=\"mx-auto mt-20 max-w-5xl border-t pt-10\">\n            <h2 id=\"related-heading\" className=\"text-lg font-semibold tracking-tight\">Keep reading</h2>\n            <ul className=\"mt-6 grid gap-4 sm:grid-cols-3\">\n              {related.map((r) => (\n                <li key={r.title}>\n                  <a href={r.href} className=\"group/related hover:bg-accent focus-visible:ring-ring/50 block h-full rounded-2xl border p-5 outline-none transition-colors focus-visible:ring-[3px]\">\n                    <span className=\"text-muted-foreground text-xs font-medium tracking-wide uppercase\">{r.category}</span>\n                    <span className=\"mt-2 block leading-snug font-semibold text-balance\">{r.title}</span>\n                    <span className=\"text-muted-foreground mt-4 block text-sm\">{r.readMinutes} min read</span>\n                  </a>\n                </li>\n              ))}\n            </ul>\n          </section>\n        )}\n      </article>\n    </>\n  )\n}\n\nexport { BlogPost1, type BlogPost1Props, type BlogPostRelated }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/blog-post-1/blog-post-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "blog",
      "article",
      "post",
      "prose",
      "table of contents",
      "reading progress"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "An article template. Pass title, subtitle, author, date and readMinutes, give the body as children (plain h2, h3, p, ul, blockquote and pre are styled), and list the headings in toc.",
      "whenToUse": [
        "Blog posts, guides and announcements",
        "Any long reading page"
      ],
      "whenNotToUse": [
        "Documentation with a left sidebar (use your docs layout)"
      ],
      "composesWith": [
        "blog-1",
        "newsletter-1",
        "header-2",
        "footer-2"
      ],
      "a11y": [
        {
          "keys": "Tab",
          "action": "Reaches the breadcrumb, the copy button, links in the body, then the contents list"
        },
        {
          "keys": "Enter on a contents link",
          "action": "Scrolls to that heading; the current section is marked with aria-current"
        }
      ],
      "customization": [
        "children: your article, with ids on h2 and h3 headings",
        "toc: { id, title, level }[] matching those ids",
        "stickyOffset: pixels to keep clear of a fixed header",
        "related: [] hides the Keep reading section"
      ]
    },
    "examples": [
      "blog-post-1-demo",
      "blog-post-1-short"
    ],
    "url": "https://ui.ballmac.com/components/blog-post-1"
  },
  "categories": [
    "blocks",
    "blog"
  ],
  "type": "registry:block"
}