[{"data":1,"prerenderedAt":439},["ShallowReactive",2],{"post-shipping-the-blog-migration-en":3,"related-posts-shipping-the-blog-migration-en":404,"related-categories-shipping-the-blog-migration-en":429},{"id":4,"title":5,"author":6,"authorImageUrl":7,"authorUrl":8,"body":9,"categorySlug":380,"categoryText":381,"dateModified":382,"datePublished":382,"description":383,"extension":384,"faqs":385,"image":392,"language":393,"meta":394,"navigation":395,"path":396,"seo":397,"stem":398,"tags":399,"__hash__":403},"blog\u002Fblog\u002Fshipping-the-blog-migration.md","Shipping the Blog Migration Into the Library","Bruma","\u002Fimages\u002Fauthors\u002Fbruma.jpg","https:\u002F\u002Fbrumaombra.com",{"type":10,"value":11,"toc":341},"minimark",[12,17,21,24,36,41,46,49,52,56,59,62,65,72,78,83,87,90,93,159,163,166,169,173,176,180,183,187,190,194,197,201,204,208,211,215,218,222,225,229,232,236,239,243,246,250,253,257,260,264,267,271,274,278,281,285,288,292,295,299,302,306,309,313,316,320,323,327,330,334,337],[13,14,16],"h2",{"id":15},"the-migration-changes-what-the-demo-is-proving","The migration changes what the demo is proving",[18,19,20],"p",{},"Once the blog UI moves into the library, the demo app stops being a static gallery and becomes a real integration surface. It now needs to prove that the components can work with content collections, route params, and markdown rendering rather than fixed arrays alone.",[18,22,23],{},"That is a more meaningful test, because it mirrors the way a consuming product actually uses the library.",[18,25,26,27,31,32,35],{},"In practice, the integration stays easy to scan when names such as ",[28,29,30],"code",{},"queryCollection"," and ",[28,33,34],{},"@brumaombra\u002Fui-vintage\u002Fcontent"," remain compact inline references.",[37,38],"blog-list",{":items":39,"variant":40},"[\"The page queries real markdown entries instead of rendering hardcoded arrays\",\"Shared article components now sit behind dedicated library subpath imports\",\"The demo validates wrappers, content rendering, and route composition together\"]","numbered",[42,43,45],"h3",{"id":44},"production-like-does-not-mean-product-specific","Production-like does not mean product-specific",[18,47,48],{},"The demo does not need every conversion card or SEO helper from the original app. It does need the real data flow: content collection, frontmatter schema, content renderer, related post logic, and category navigation.",[50,51],"hr",{},[13,53,55],{"id":54},"a-small-set-of-example-posts-is-enough-to-validate-the-route-graph","A small set of example posts is enough to validate the route graph",[18,57,58],{},"With a handful of posts, the demo can validate the full blog route graph:",[37,60],{":items":61},"[\"The \u002Fblog index\",\"The \u002Fblog\u002F[slug] article page\",\"The \u002Fblog\u002Fcategories overview\",\"The \u002Fblog\u002Fcategories\u002F[slug] listing\"]",[18,63,64],{},"That is the minimum useful slice for testing whether the migrated components still compose correctly around actual content.",[66,67],"flow",{":items":68,"description":69,"orientation":70,"title":71},"[\"Markdown entry\",\"Collection query\",\"Localized route\",\"Rendered article\"]","The demo follows the same path as a consuming application.","horizontal","From markdown to route",[73,74],"code-block",{"code":75,"language":76,"title":77},"The article is loaded from Markdown and rendered through the shared blog surface.\n","text","Plain text output",[79,80],"blog-table",{":headers":81,":rows":82},"[\"Route\",\"What it validates\"]","[[\"\u002Fblog\",\"Index composition, featured posts, and paginated lists\"],[\"\u002Fblog\u002F[slug]\",\"Article content, author info, TOC, and related blocks\"],[\"\u002Fblog\u002Fcategories\",\"Category aggregation and navigation\"],[\"\u002Fblog\u002Fcategories\u002F[slug]\",\"Filtered post listings with pagination\"]]",[42,84,86],{"id":85},"the-content-schema-matters-as-much-as-the-components","The content schema matters as much as the components",[18,88,89],{},"The article page only works smoothly because the content schema includes the same metadata the UI expects: title, description, image, author, publish dates, category information, language, and FAQs.",[18,91,92],{},"If those fields drift, the page becomes brittle. Keeping the schema explicit is part of keeping the library usable.",[94,95,100],"pre",{"className":96,"code":97,"language":98,"meta":99,"style":99},"language-ts shiki shiki-themes github-light github-dark","const post = await queryCollection('blog')\n  .path(route.path)\n  .first();\n","ts","",[28,101,102,136,148],{"__ignoreMap":99},[103,104,107,111,115,118,121,125,129,133],"span",{"class":105,"line":106},"line",1,[103,108,110],{"class":109},"szBVR","const",[103,112,114],{"class":113},"sj4cs"," post",[103,116,117],{"class":109}," =",[103,119,120],{"class":109}," await",[103,122,124],{"class":123},"sScJk"," queryCollection",[103,126,128],{"class":127},"sVt8B","(",[103,130,132],{"class":131},"sZZnC","'blog'",[103,134,135],{"class":127},")\n",[103,137,139,142,145],{"class":105,"line":138},2,[103,140,141],{"class":127},"  .",[103,143,144],{"class":123},"path",[103,146,147],{"class":127},"(route.path)\n",[103,149,151,153,156],{"class":105,"line":150},3,[103,152,141],{"class":127},[103,154,155],{"class":123},"first",[103,157,158],{"class":127},"();\n",[13,160,162],{"id":161},"related-content-should-feel-deterministic","Related content should feel deterministic",[18,164,165],{},"Even in a small demo, related content should not feel random. Prioritizing same-category posts first keeps the page behavior understandable and closer to what a real product would do.",[18,167,168],{},"That is enough for this app. The point is not to recreate a full editorial recommendation engine. The point is to validate that the layout, links, and supporting components work when content is real.",[13,170,172],{"id":171},"a-longer-article-gives-the-table-of-contents-room-to-work","A longer article gives the table of contents room to work",[18,174,175],{},"This section intentionally adds more structure so the floating table of contents can be tested with a realistic amount of navigation.",[42,177,179],{"id":178},"start-with-the-reading-context","Start with the reading context",[18,181,182],{},"The first subsection gives the reader a clear place to begin before the article branches into smaller topics.",[42,184,186],{"id":185},"keep-each-section-focused","Keep each section focused",[18,188,189],{},"Short, focused sections make a long table of contents easier to scan even when the navigation itself needs to scroll.",[42,191,193],{"id":192},"leave-room-for-the-next-idea","Leave room for the next idea",[18,195,196],{},"Consistent spacing between sections helps the active heading remain easy to identify while reading.",[13,198,200],{"id":199},"heading-density-changes-the-navigation-rhythm","Heading density changes the navigation rhythm",[18,202,203],{},"More headings make it easier to see whether active-state tracking and nested heading indentation continue to work together.",[42,205,207],{"id":206},"compare-primary-sections","Compare primary sections",[18,209,210],{},"Primary sections should remain visually stronger than their subtitles in both the in-flow and floating versions of the navigation.",[42,212,214],{"id":213},"compare-supporting-subtitles","Compare supporting subtitles",[18,216,217],{},"Supporting subtitles should stay readable without making the navigation card grow beyond the viewport.",[42,219,221],{"id":220},"follow-the-active-chapter","Follow the active chapter",[18,223,224],{},"As the reader moves down the page, the highlighted entry should continue to follow the visible heading.",[13,226,228],{"id":227},"a-docked-navigation-needs-a-bounded-height","A docked navigation needs a bounded height",[18,230,231],{},"The floating card should remain available without covering an unreasonable amount of the article or extending below the viewport.",[42,233,235],{"id":234},"test-a-short-viewport","Test a short viewport",[18,237,238],{},"A shorter viewport should make the navigation list scroll internally instead of pushing the card outside the screen.",[42,240,242],{"id":241},"test-a-wide-viewport","Test a wide viewport",[18,244,245],{},"At wider desktop sizes, the card can show more entries while keeping the same relationship to the prose container.",[42,247,249],{"id":248},"test-repeated-heading-levels","Test repeated heading levels",[18,251,252],{},"Repeated h2 and h3 pairs make indentation, spacing, and active styling easier to inspect.",[13,254,256],{"id":255},"long-navigation-should-remain-keyboard-friendly","Long navigation should remain keyboard friendly",[18,258,259],{},"The table of contents is also a keyboard navigation surface, so a larger list should not change its focus behavior.",[42,261,263],{"id":262},"move-through-the-list-with-tab","Move through the list with Tab",[18,265,266],{},"Each heading link should remain reachable in document order whether the card is in flow or docked.",[42,268,270],{"id":269},"activate-a-distant-heading","Activate a distant heading",[18,272,273],{},"Selecting a heading near the end of the list should close the mobile overlay when needed and scroll to the matching content.",[42,275,277],{"id":276},"return-focus-after-closing","Return focus after closing",[18,279,280],{},"When the mobile navigation closes, focus should return to its trigger so the reading flow remains predictable.",[13,282,284],{"id":283},"the-active-heading-should-survive-dense-content","The active heading should survive dense content",[18,286,287],{},"Dense articles are useful for checking whether the observer keeps the correct chapter active around section boundaries.",[42,289,291],{"id":290},"enter-a-new-section","Enter a new section",[18,293,294],{},"The active state should change when a new primary heading enters the reading window.",[42,296,298],{"id":297},"pass-through-a-subtitle","Pass through a subtitle",[18,300,301],{},"Subtitles should be able to become active without losing their parent section from the overall navigation structure.",[42,303,305],{"id":304},"reach-the-final-section","Reach the final section",[18,307,308],{},"The last entries should remain selectable even when they begin below the visible portion of the floating list.",[13,310,312],{"id":311},"a-stress-test-should-still-feel-like-an-article","A stress test should still feel like an article",[18,314,315],{},"The extra headings are deliberately lightweight, but they preserve the kind of hierarchy a longer production post might use.",[42,317,319],{"id":318},"keep-the-prose-meaningful","Keep the prose meaningful",[18,321,322],{},"Each heading should introduce a small idea rather than existing only as a repeated placeholder.",[42,324,326],{"id":325},"preserve-the-visual-cadence","Preserve the visual cadence",[18,328,329],{},"Paragraphs between headings make it possible to inspect scrolling, spacing, and heading offsets together.",[42,331,333],{"id":332},"finish-with-a-clear-conclusion","Finish with a clear conclusion",[18,335,336],{},"A final group of subtitles gives the TOC a complete ending to track and keeps the test content easy to remove later.",[338,339,340],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":99,"searchDepth":138,"depth":138,"links":342},[343,346,349,350,355,360,365,370,375],{"id":15,"depth":138,"text":16,"children":344},[345],{"id":44,"depth":150,"text":45},{"id":54,"depth":138,"text":55,"children":347},[348],{"id":85,"depth":150,"text":86},{"id":161,"depth":138,"text":162},{"id":171,"depth":138,"text":172,"children":351},[352,353,354],{"id":178,"depth":150,"text":179},{"id":185,"depth":150,"text":186},{"id":192,"depth":150,"text":193},{"id":199,"depth":138,"text":200,"children":356},[357,358,359],{"id":206,"depth":150,"text":207},{"id":213,"depth":150,"text":214},{"id":220,"depth":150,"text":221},{"id":227,"depth":138,"text":228,"children":361},[362,363,364],{"id":234,"depth":150,"text":235},{"id":241,"depth":150,"text":242},{"id":248,"depth":150,"text":249},{"id":255,"depth":138,"text":256,"children":366},[367,368,369],{"id":262,"depth":150,"text":263},{"id":269,"depth":150,"text":270},{"id":276,"depth":150,"text":277},{"id":283,"depth":138,"text":284,"children":371},[372,373,374],{"id":290,"depth":150,"text":291},{"id":297,"depth":150,"text":298},{"id":304,"depth":150,"text":305},{"id":311,"depth":138,"text":312,"children":376},[377,378,379],{"id":318,"depth":150,"text":319},{"id":325,"depth":150,"text":326},{"id":332,"depth":150,"text":333},"product-updates","Product Updates","2026-06-14T00:00:00Z","A practical summary of what changed once the original blog implementation was moved into a reusable Nuxt-friendly component library.","md",[386,389],{"question":387,"answer":388},"Why keep Nuxt Content in the demo app?","Because the demo should validate the same content querying and rendering path that a production consumer would use, not just the visual components in isolation.",{"question":390,"answer":391},"How many example posts does the demo ship?","Six posts per language. That is enough to fill the index, give related posts and categories real choices, put more than one post in a category, and share tags across posts, while staying small enough to read through in one sitting.","\u002Fimages\u002Fblog\u002Fshipping-the-blog-migration\u002Fcover.jpg","en",{},true,"\u002Fblog\u002Fshipping-the-blog-migration",{"title":5,"description":383},"blog\u002Fshipping-the-blog-migration",[400,401,402],"Nuxt","Content","Component Libraries","0L0uGSnv52bAHW2hpkDl-rAx3QtH-AMwVjH5y1GmYJ8",[405,411,417,423],{"title":406,"description":407,"image":408,"categoryText":409,"path":410},"Shipping a Vue Library Without a Build Step","UI Vintage publishes its source and lets each Nuxt app compile it. That choice removes a whole class of problems, and adds a couple of new ones.","\u002Fimages\u002Fblog\u002Fsource-published-packages\u002Fcover.jpg","Engineering","\u002Fblog\u002Fsource-published-packages",{"title":412,"description":413,"image":414,"categoryText":415,"path":416},"Designing Focus States People Actually Notice","Keyboard users rely on the focus ring to know where they are. Most designs hide it or make it faint. A few small rules make it both visible and good-looking.","\u002Fimages\u002Fblog\u002Ffocus-states-people-notice\u002Fcover.jpg","Accessibility","\u002Fblog\u002Ffocus-states-people-notice",{"title":418,"description":419,"image":420,"categoryText":421,"path":422},"Why Spring Animations Feel Faster Than They Are","A spring that overshoots slightly can take longer than a plain fade and still feel quicker. Here is why, and where the trick stops working.","\u002Fimages\u002Fblog\u002Fwhy-spring-animations-feel-faster\u002Fcover.jpg","Motion Design","\u002Fblog\u002Fwhy-spring-animations-feel-faster",{"title":424,"description":425,"image":426,"categoryText":427,"path":428},"Designing a Reusable Editorial System","How we turned a one-off blog UI into a reusable content surface that still feels intentional once it leaves the original product.","\u002Fimages\u002Fblog\u002Fdesigning-a-reusable-editorial-system\u002Fcover.jpg","Design Systems","\u002Fblog\u002Fdesigning-a-reusable-editorial-system",[430,432,434,436],{"name":421,"slug":431,"count":106,"image":420},"motion-design",{"name":409,"slug":433,"count":106,"image":408},"engineering",{"name":415,"slug":435,"count":106,"image":414},"accessibility",{"name":427,"slug":437,"count":138,"image":438},"design-systems","\u002Fimages\u002Fblog\u002Fdark-mode-is-a-second-design\u002Fcover.jpg",1792259222221]