From 2e45d89a7a9e147fff1b7514e738c5ffe46e97dd Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Sun, 19 Apr 2026 20:41:30 +0200 Subject: [PATCH 01/28] feat(web): rewrite header with real helpbase nav MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kills placeholder nav (Automation/Scalability/Backup/Analytics dropdown, Marketplace/Guides/API Integration/Partnerships items, Continue button pointing to #). Replaces with real nav: Docs, Pricing (anchor), GitHub. Primary CTA is "Deploy now" → /login. Secondary CTA is "Sign in". Unblocks cold-visitor tests while the full marketing page rewrite is in flight. Ship before any marketing traffic lands. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/header.tsx | 291 +++++++++------------------------ 1 file changed, 77 insertions(+), 214 deletions(-) diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index f18a898..52741e9 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -3,94 +3,20 @@ import Link from 'next/link' import { Button } from '@/components/ui/button' import React from 'react' import { useScroll, useMotionValueEvent } from 'motion/react' -import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from '@/components/ui/navigation-menu' import { Menu, X, ArrowRight } from 'lucide-react' import { useMedia } from '@/hooks/use-media' -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' import { cn } from '@workspace/ui/lib/utils' -interface FeatureLink { - href: string +interface NavLink { name: string - description?: string -} - -interface MobileLink { - groupName?: string - links?: FeatureLink[] - name?: string - href?: string + href: string + external?: boolean } -const features: FeatureLink[] = [ - { - href: '#automation', - name: 'Automation', - description: 'Automate your workflow', - }, - { - href: '#scalability', - name: 'Scalability', - description: 'Scale your application effortlessly', - }, - { - href: '#backup', - name: 'Backup', - description: 'Keep your data backed up', - }, - { - href: '#analytics', - name: 'Analytics', - description: 'Track and measure your progress', - }, -] - -const useCases: FeatureLink[] = [ - { - href: '#ux', - name: 'Marketplace', - description: 'Find and buy AI tools', - }, - { - href: '#performance', - name: 'Guides', - description: 'Learn how to use AI tools', - }, - { - href: '#security', - name: 'API Integration', - description: 'Integrate AI tools into your app', - }, - { - href: '#support', - name: 'Partnerships', - description: 'Get help when you need it', - }, -] - -const contentLinks: FeatureLink[] = [ - { - name: 'Announcements', - href: '#link', - }, - { - name: 'Resources', - href: '#link', - }, - { name: 'Blog', href: '#link' }, -] - -const mobileLinks: MobileLink[] = [ - { - groupName: 'Product', - links: features, - }, - { - groupName: 'Solutions', - links: [...useCases, ...contentLinks], - }, - { name: 'Pricing', href: '#' }, - { name: 'Company', href: '#' }, +const NAV_LINKS: NavLink[] = [ + { name: 'Docs', href: '/docs' }, + { name: 'Pricing', href: '/#pricing' }, + { name: 'GitHub', href: 'https://github.com/Codehagen/helpbase', external: true }, ] export function Header() { @@ -109,7 +35,12 @@ export function Header() { role="banner" data-state={isMobileMenuOpen ? 'active' : 'inactive'} {...(isScrolled && { 'data-scrolled': true })}> -
+
@@ -121,35 +52,59 @@ export function Header() { {isLarge && ( -
- {' '} -
+ )} +
- {!isLarge && isMobileMenuOpen && setIsMobileMenuOpen(false)} />} - -
-
- -
+ {!isLarge && isMobileMenuOpen && ( + setIsMobileMenuOpen(false)} /> + )} + +
+ +
@@ -161,123 +116,31 @@ export function Header() { const MobileMenu = ({ closeMenu }: { closeMenu: () => void }) => { return ( ) } - -const NavMenu = () => { - return ( - - - - Product - -
- Features -
    - {features.map((feature, index) => ( - - ))} -
-
-
- Agents Workflow -
    - {useCases.map((useCase, index) => ( - - ))} -
-
-
-
- - - Pricing - - - - - Company - - -
-
- ) -} - -function ListItem({ title, description, href, ...props }: React.ComponentPropsWithoutRef<'li'> & { href: string; title: string; description?: string }) { - return ( -
  • - - -
    {title}
    -

    {description}

    - -
    -
  • - ) -} \ No newline at end of file From 12bd686413d007e6e3c95ebc8fa721893c5d860f Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Sun, 19 Apr 2026 20:44:46 +0200 Subject: [PATCH 02/28] chore(web): install @magicui/terminal + 8 Tailark Pro blocks Adds @magicui registry to apps/web/components.json. Installs: - @magicui/terminal (typing-animation hero primitive) - @tailark-pro/comparator-7 (quick comparison vs roll-your-own vs hosted) - @tailark-pro/how-it-works-3 (left-rail 4-step flow) - @tailark-pro/features-1 (2-col feature with screenshot) - @tailark-pro/bento-2 (3-cell AI-native bento) - @tailark-pro/pricing-2 (3-col open-core tiers) - @tailark-pro/faqs-3 (title-left grouped FAQ) - @tailark-pro/footer-4 (product/resources/company/subscribe) - @tailark-pro/logo-cloud-3 (built-on badges) Raw Tailark block exports are kept so subsequent commits can wrap them with helpbase copy. No page wiring yet. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components.json | 3 +- apps/web/components/bento-2.tsx | 76 ++ apps/web/components/code-block.tsx | 92 +++ apps/web/components/comparator-7.tsx | 186 +++++ apps/web/components/const.ts | 6 + apps/web/components/faqs-3.tsx | 103 +++ apps/web/components/features-1.tsx | 31 + apps/web/components/footer.tsx | 222 +++++- apps/web/components/how-it-works-3.tsx | 83 ++ apps/web/components/logo-cloud.tsx | 260 ++----- apps/web/components/logo.tsx | 69 ++ apps/web/components/map.tsx | 31 + apps/web/components/particles.tsx | 407 ++++++++++ apps/web/components/pricing.tsx | 176 +++++ apps/web/components/ui/accordion.tsx | 7 +- apps/web/components/ui/card.tsx | 70 ++ apps/web/components/ui/chart.tsx | 373 +++++++++ .../ui/illustrations/chart-illustration.tsx | 102 +++ .../ui/illustrations/code-illustration.tsx | 44 ++ .../illustrations/currency-illustration.tsx | 88 +++ .../illustrations/document-illustration.tsx | 32 + .../ui/illustrations/invoice-illustration.tsx | 42 + .../ui/illustrations/map-illustration.tsx | 40 + .../monitoring-barchart-illustration.tsx | 75 ++ .../notification-illustration.tsx | 31 + .../ui/illustrations/reply-illustration.tsx | 26 + .../ui/illustrations/scan-illustration.tsx | 106 +++ .../visualization-illustration.tsx | 43 ++ apps/web/components/ui/infinite-slider.tsx | 91 +++ apps/web/components/ui/input.tsx | 20 + apps/web/components/ui/label.tsx | 24 + apps/web/components/ui/svgs/vs-code.tsx | 131 ++++ apps/web/components/ui/terminal.tsx | 300 ++++++++ apps/web/components/ui/text-scramble.tsx | 86 +++ apps/web/components/ui/tooltip.tsx | 55 ++ apps/web/package.json | 8 + pnpm-lock.yaml | 718 ++++++++++++++++++ 37 files changed, 4027 insertions(+), 230 deletions(-) create mode 100644 apps/web/components/bento-2.tsx create mode 100644 apps/web/components/code-block.tsx create mode 100644 apps/web/components/comparator-7.tsx create mode 100644 apps/web/components/const.ts create mode 100644 apps/web/components/faqs-3.tsx create mode 100644 apps/web/components/features-1.tsx create mode 100644 apps/web/components/how-it-works-3.tsx create mode 100644 apps/web/components/logo.tsx create mode 100644 apps/web/components/map.tsx create mode 100644 apps/web/components/particles.tsx create mode 100644 apps/web/components/pricing.tsx create mode 100644 apps/web/components/ui/card.tsx create mode 100644 apps/web/components/ui/chart.tsx create mode 100644 apps/web/components/ui/illustrations/chart-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/code-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/currency-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/document-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/invoice-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/map-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/monitoring-barchart-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/notification-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/reply-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/scan-illustration.tsx create mode 100644 apps/web/components/ui/illustrations/visualization-illustration.tsx create mode 100644 apps/web/components/ui/infinite-slider.tsx create mode 100644 apps/web/components/ui/input.tsx create mode 100644 apps/web/components/ui/label.tsx create mode 100644 apps/web/components/ui/svgs/vs-code.tsx create mode 100644 apps/web/components/ui/terminal.tsx create mode 100644 apps/web/components/ui/text-scramble.tsx create mode 100644 apps/web/components/ui/tooltip.tsx diff --git a/apps/web/components.json b/apps/web/components.json index ca9a735..5a5b823 100644 --- a/apps/web/components.json +++ b/apps/web/components.json @@ -26,6 +26,7 @@ "headers": { "Authorization": "Bearer ${TAILARK_API_KEY}" } - } + }, + "@magicui": "https://magicui.design/r/{name}.json" } } diff --git a/apps/web/components/bento-2.tsx b/apps/web/components/bento-2.tsx new file mode 100644 index 0000000..ff5eb61 --- /dev/null +++ b/apps/web/components/bento-2.tsx @@ -0,0 +1,76 @@ +import { CurrencyIllustration } from "@/components/ui/illustrations/currency-illustration" +import { ReplyIllustration } from "@/components/ui/illustrations/reply-illustration" +import { NotificationIllustration } from "@/components/ui/illustrations/notification-illustration" +import { Card } from '@/components/ui/card' +import { MapIllustration } from "@/components/ui/illustrations/map-illustration" +import { VisualizationIllustration } from "@/components/ui/illustrations/visualization-illustration" + +export default function FeaturesSection12() { + return ( +
    +

    Features

    +
    +
    + +
    + + +
    +
    +

    Scheduled Reports

    +

    Automate report delivery to stakeholders with customizable scheduling options.

    +
    +
    + +
    + + +
    +
    +

    Collaborative Analysis

    +

    Add comments, share insights, and work together with your team to extract maximum.

    +
    +
    + +
    + + +
    +
    +

    Collaborative Analysis

    +

    Add comments, share insights, and work together with your team to extract maximum.

    +
    +
    + +
    + +
    +
    +

    Interactive Dashboards

    +

    Create custom dashboards with drag-and-drop simplicity. Combine multiple visualization types to get a complete view of your data story.

    +
    +
    + +
    + +
    +
    +

    Scheduled Reports

    +

    Create custom dashboards with drag-and-drop simplicity. Combine multiple visualization types to get a complete view of your data story.

    +
    +
    +
    +
    +
    + ) +} + +const Stripes = () => ( +
    +) \ No newline at end of file diff --git a/apps/web/components/code-block.tsx b/apps/web/components/code-block.tsx new file mode 100644 index 0000000..1565d0e --- /dev/null +++ b/apps/web/components/code-block.tsx @@ -0,0 +1,92 @@ +'use client' + +import { cn } from '@workspace/ui/lib/utils' +import { toJsxRuntime } from 'hast-util-to-jsx-runtime' +import { JSX, useLayoutEffect, useState } from 'react' +import { Fragment, jsx, jsxs } from 'react/jsx-runtime' +import type { BundledLanguage } from 'shiki/bundle/web' + +const highlightCache = new Map() + +let shikiPromise: Promise | null = null + +function getShiki() { + if (!shikiPromise) { + shikiPromise = import('shiki/bundle/web') + } + return shikiPromise +} + +export async function highlight(code: string, lang: BundledLanguage) { + const cacheKey = `${lang}:${code.length}:${code.slice(0, 50)}:${code.slice(-50)}` + + const cached = highlightCache.get(cacheKey) + if (cached) return cached + + const { codeToHast } = await getShiki() + + const hast = await codeToHast(code, { + lang, + themes: { + light: 'github-light', + dark: 'vesper', + }, + }) + + const result = toJsxRuntime(hast, { + Fragment, + jsx, + jsxs, + }) as JSX.Element + + if (highlightCache.size > 100) { + const firstKey = highlightCache.keys().next().value + if (firstKey) highlightCache.delete(firstKey) + } + highlightCache.set(cacheKey, result) + + return result +} + +type Props = { + code: string | null + lang: BundledLanguage + initial?: JSX.Element + preHighlighted?: JSX.Element | null + maxHeight?: number + className?: string + theme?: string + lineNumbers?: boolean // ← added +} + +export default function CodeBlock({ code, lang, initial, maxHeight=940, preHighlighted, theme, className }: Props) { + const [content, setContent] = useState(preHighlighted || initial || null) + + useLayoutEffect(() => { + if (preHighlighted) { + return + } + + let isMounted = true + + if (code) { + highlight(code, lang).then((result) => { + if (isMounted) setContent(result) + }) + } + + return () => { + isMounted = false + } + }, [code, lang, theme, preHighlighted]) + + return content ? ( +
    + {content} +
    + ) : ( +
    Loading...
    + ) +} \ No newline at end of file diff --git a/apps/web/components/comparator-7.tsx b/apps/web/components/comparator-7.tsx new file mode 100644 index 0000000..c79687c --- /dev/null +++ b/apps/web/components/comparator-7.tsx @@ -0,0 +1,186 @@ +import { cn } from '@workspace/ui/lib/utils' +import { TooltipProvider, Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { Stripe } from '@/components/ui/svgs/stripe' +import { Bolt } from '@/components/ui/svgs/bolt' + +const plans = ['stripe', 'bolt'] as const + +type Plan = (typeof plans)[number] + +type Feature = { + name: string + description?: string + plans: Record +} + +const features: Feature[] = [ + { + name: 'Daily Exercises', + description: 'Engage users with daily interactive exercises.', + plans: { + stripe: true, + bolt: true, + }, + }, + { + name: 'Unlimited Storage', + description: 'Store unlimited data with no additional cost.', + plans: { + stripe: true, + bolt: true, + }, + }, + { + name: 'Custom Dashboards', + description: 'Create personalized dashboards for data visualization.', + plans: { + stripe: true, + bolt: false, + }, + }, + { + name: 'Self-paced Learning', + description: 'Empower users to learn at their own pace.', + plans: { + stripe: true, + bolt: true, + }, + }, + { + name: 'Ad-free Experience', + description: 'Provide an uninterrupted, ad-free environment.', + plans: { + stripe: true, + bolt: false, + }, + }, + { + name: 'Team Collaboration', + description: 'Enhance productivity with bolt collaboration tools.', + plans: { + stripe: true, + bolt: false, + }, + }, + { + name: 'Self-paced Learning', + description: 'Empower users to learn at their own pace.', + plans: { + stripe: true, + bolt: true, + }, + }, + { + name: 'Ad-free Experience', + description: 'Provide an uninterrupted, ad-free environment.', + plans: { + stripe: true, + bolt: false, + }, + }, +] + +const renderPlanColumn = (plan: Plan) => { + const header = + plan === 'stripe' ? ( +
    + +
    + ) : ( +
    + +
    + ) + + return ( +
    + {header} + +
    + {features.map((feature, index) => ( +
    +
    {feature.plans[plan] === true ? : feature.plans[plan] === false ? : feature.plans[plan]}
    +
    + ))} +
    +
    +
    + ) +} + +export default function FAQs() { + return ( +
    +
    +
    +
    +
    +

    Quick comparison guide

    +

    Tailark offers a robust and user-friendly link management solution, emerging as the top choice over Cloudflare.

    +
    +
    + +
    +
    +
    +
    Features
    +
    + + {features.map((feature, index) => ( +
    +
    {feature.name}
    {' '} + {feature.description && ( + + + + ? + + {feature.description} + + + )} +
    + ))} +
    + +
    + {plans.map((plan) => ( +
    + {renderPlanColumn(plan)} +
    + ))} +
    +
    +
    +
    +
    + ) +} + +const Indicator = ({ checked = false }: { checked?: boolean }) => { + return {checked ? : '✗'} +} + +const CheckIcon = () => { + return ( + + + + ) +} \ No newline at end of file diff --git a/apps/web/components/const.ts b/apps/web/components/const.ts new file mode 100644 index 0000000..3857414 --- /dev/null +++ b/apps/web/components/const.ts @@ -0,0 +1,6 @@ +const MESCHAC_AVATAR = 'https://avatars.githubusercontent.com/u/47919550?v=4' +const BERNARD_AVATAR = 'https://avatars.githubusercontent.com/u/31113941?v=4' +const THEO_AVATAR = 'https://avatars.githubusercontent.com/u/68236786?v=4' +const GLODIE_AVATAR = 'https://avatars.githubusercontent.com/u/99137927?v=4' + +export { BERNARD_AVATAR, GLODIE_AVATAR, THEO_AVATAR, MESCHAC_AVATAR } \ No newline at end of file diff --git a/apps/web/components/faqs-3.tsx b/apps/web/components/faqs-3.tsx new file mode 100644 index 0000000..542905b --- /dev/null +++ b/apps/web/components/faqs-3.tsx @@ -0,0 +1,103 @@ +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' +import Link from 'next/link' + +const faqItems = [ + { + group: 'General', + items: [ + { + id: 'item-1', + question: 'How long does shipping take?', + answer: 'Standard shipping takes 3-5 business days, depending on your location. Express shipping options are available at checkout for 1-2 business day delivery.', + }, + { + id: 'item-2', + question: 'What payment methods do you accept?', + answer: 'We accept all major credit cards (Visa, Mastercard, American Express), PayPal, Apple Pay, and Google Pay. For enterprise customers, we also offer invoicing options.', + }, + { + id: 'item-3', + question: 'Can I change or cancel my order?', + answer: 'You can modify or cancel your order within 1 hour of placing it. After this window, please contact our customer support team who will assist you with any changes.', + }, + ], + }, + { + group: 'Shipping', + items: [ + { + id: 'item-1', + question: 'Do you ship internationally?', + answer: 'Standard shipping takes 3-5 business days, depending on your location. Express shipping options are available at checkout for 1-2 business day delivery.', + }, + { + id: 'item-2', + question: 'What is your return policy?', + answer: 'We offer a 30-day return policy for most items. Products must be in original condition with tags attached. Some specialty items may have different return terms, which will be noted on the product page.', + }, + { + id: 'item-3', + question: 'Do you ship internationally?', + answer: 'Standard shipping takes 3-5 business days, depending on your location. Express shipping options are available at checkout for 1-2 business day delivery.', + }, + ], + }, +] + +export default function FAQs() { + return ( +
    +
    +
    +
    +

    FAQs

    +

    Your questions answered

    +

    + Can't find what you're looking for? Contact our{' '} + + customer support team + +

    +
    + +
    + {faqItems.map((item) => ( +
    +

    {item.group}

    + + {item.items.map((item) => ( + + {item.question} + +

    {item.answer}

    +
    +
    + ))} +
    +
    + ))} +
    +
    + +

    + Can't find what you're looking for? Contact our{' '} + + customer support team + +

    +
    +
    + ) +} \ No newline at end of file diff --git a/apps/web/components/features-1.tsx b/apps/web/components/features-1.tsx new file mode 100644 index 0000000..28f5ebc --- /dev/null +++ b/apps/web/components/features-1.tsx @@ -0,0 +1,31 @@ +import { ChartIllustration } from "@/components/ui/illustrations/chart-illustration" +import { InvoiceIllustration } from "@/components/ui/illustrations/invoice-illustration" + +export default function FeaturesSection() { + return ( +
    +
    +
    +
    +
    +

    Powerful analytics dashboard

    +

    Track performance metrics with real-time data visualization and customizable reports for informed.

    +
    +
    + +
    +
    +
    +
    +

    Streamlined invoicing system

    +

    Generate, send, and manage professional invoices automatically with integrated payment tracking.

    +
    +
    + +
    +
    +
    +
    +
    + ) +} \ No newline at end of file diff --git a/apps/web/components/footer.tsx b/apps/web/components/footer.tsx index e64770e..10ad8f6 100644 --- a/apps/web/components/footer.tsx +++ b/apps/web/components/footer.tsx @@ -1,38 +1,186 @@ -import Link from "next/link" +import { Logo } from '@/components/logo' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import Link from 'next/link' -export function Footer() { - return ( - - ) -} +const links = [ + { + group: 'Product', + items: [ + { + title: 'Features', + href: '#', + }, + { + title: 'Solution', + href: '#', + }, + { + title: 'Partnerships', + href: '#', + }, + { + title: 'Mobile App', + href: '#', + }, + ], + }, + { + group: 'Resources', + items: [ + { + title: 'Blog', + href: '#', + }, + { + title: 'Documentation', + href: '#', + }, + { + title: 'Support', + href: '#', + }, + ], + }, + { + group: 'Company', + items: [ + { + title: 'About', + href: '#', + }, + { + title: 'Licence', + href: '#', + }, + { + title: 'Privacy', + href: '#', + }, + ], + }, +] + +export default function FooterSection() { + return ( +
    +
    +
    +
    + + + + +

    San Francisco, CA 94102 - United States

    +
    + +
    + + + + + + + + + + +
    +
    +
    +
    +
    + {links.map((link, index) => ( +
    + {link.group} + +
    + {link.items.map((item, index) => ( + + {item.title} + + ))} +
    +
    + ))} +
    + +
    +
    + +
    + + +
    +

    Get the latest product news and behind the scenes updates. Unsubscribe at any time.

    +
    +
    +
    + +
    + © {new Date().getFullYear()} Tailark, All rights reserved +
    +
    + + +
    + All Systems Normal +
    +
    +
    +
    + ) +} \ No newline at end of file diff --git a/apps/web/components/how-it-works-3.tsx b/apps/web/components/how-it-works-3.tsx new file mode 100644 index 0000000..b0fc978 --- /dev/null +++ b/apps/web/components/how-it-works-3.tsx @@ -0,0 +1,83 @@ +import { MonitoringBarChart } from "@/components/ui/illustrations/monitoring-barchart-illustration" +import { ScanIllustration } from "@/components/ui/illustrations/scan-illustration" +import { cn } from '@workspace/ui/lib/utils' +import { CodeIllustration } from "@/components/ui/illustrations/code-illustration" +import Image from 'next/image' + +const GLODIE_AVATAR = 'https://avatars.githubusercontent.com/u/99137927?v=4' + +export default function FeaturesSection() { + return ( +
    +
    +
    + + + + +
    +
    +

    Set up your pipeline in minutes

    +

    Our powerful analytics platform helps you visualize complex data, identify trends, and make data-driven decisions with confidence.

    +
    + +
    +
    +
    + 1 +

    Collaborative Analysis

    +

    Add comments, share insights, and work together with your team to extract maximum. From real-time dashboards to custom reports, we've got your data needs covered.

    +
    + + +
    +
    +
    + 2 +

    Send Invoice

    +

    Add comments, share insights, and work together with your team to extract maximum. From real-time dashboards to custom reports, we've got your data needs covered.

    +
    + + +
    +
    +
    + 3 +

    Send Invoice

    +

    Add comments, share insights, and work together with your team to extract maximum. From real-time dashboards to custom reports, we've got your data needs covered.

    +
    + + + +
    +

    It's the perfect fusion of simplicity and versatility, enabling us to create UIs that are as stunning as they are user-friendly.

    + +
    +
    + Glodie +
    + Glodie Lukose + @glodie +
    +
    +
    +
    +
    +
    +
    +
    + ) +} + +const PlusDecorator = ({ className }: { className?: string }) => ( +
    +) \ No newline at end of file diff --git a/apps/web/components/logo-cloud.tsx b/apps/web/components/logo-cloud.tsx index 6bba9c7..6ff5b73 100644 --- a/apps/web/components/logo-cloud.tsx +++ b/apps/web/components/logo-cloud.tsx @@ -1,5 +1,3 @@ -'use client' -import { GeminiFull } from '@/components/ui/svgs/gemini' import { Beacon } from '@/components/ui/svgs/beacon' import { Bolt } from '@/components/ui/svgs/bolt' import { Cisco } from '@/components/ui/svgs/cisco' @@ -7,198 +5,82 @@ import { Hulu } from '@/components/ui/svgs/hulu' import { OpenAIFull } from '@/components/ui/svgs/open-ai' import { Primevideo } from '@/components/ui/svgs/prime' import { Stripe } from '@/components/ui/svgs/stripe' -import { Supabase } from '@/components/ui/svgs/supabase' -import { Polars } from '@/components/ui/svgs/polars' -import { AnimatePresence, motion } from 'motion/react' -import React, { useEffect, useState } from 'react' -import { Cloudflare } from '@/components/ui/svgs/cloudflare' -import { VercelFull } from '@/components/ui/svgs/vercel' -import { Spotify } from '@/components/ui/svgs/spotify' -import { PayPal } from '@/components/ui/svgs/paypal' -import { LeapWallet } from '@/components/ui/svgs/leap-wallet' -import { Linear } from '@/components/ui/svgs/linear' -import { Slack } from '@/components/ui/svgs/slack' -import { Twilio } from '@/components/ui/svgs/twilio' -import { cn } from '@workspace/ui/lib/utils' +import { VisualStudioCode } from '@/components/ui/svgs/vs-code' +import { InfiniteSlider } from '@/components/ui/infinite-slider' +import { Button } from '@/components/ui/button' +import { ChevronRight } from 'lucide-react' +import Link from 'next/link' -const aiLogos: React.ReactNode[] = [ - , - , - , -] - -const hostingLogos: React.ReactNode[] = [ - , - , - , -] - -const paymentsLogos: React.ReactNode[] = [ - , - , - , -] - -const streamingLogos: React.ReactNode[] = [ - , - , - , -] - -const otherLogos: React.ReactNode[] = [ - , - , - , -] - -const toolsLogos: React.ReactNode[] = [ - , - , - , -] - -const logoGroups = [aiLogos, hostingLogos, paymentsLogos, streamingLogos, otherLogos, toolsLogos] - -export function LogoCloud() { - const [logoIndices, setLogoIndices] = useState([0, 0, 0, 0, 0, 0]) - - useEffect(() => { - let wrapperIndex = 0 - const interval = setInterval(() => { - setLogoIndices((prev) => { - const groupLogos = logoGroups[wrapperIndex] - if (!groupLogos) return prev - const newIndices = [...prev] - newIndices[wrapperIndex] = ((newIndices[wrapperIndex] ?? 0) + 1) % groupLogos.length - return newIndices - }) - wrapperIndex = (wrapperIndex + 1) % logoGroups.length - }, 2000) +export default function LogoCloud() { + return ( +
    +
    +
    +
    +

    You're in good company

    +

    Tailark is trusted by innovative companies worldwide to deliver exceptional products and services that drive business growth.

    +
    - return () => clearInterval(interval) - }, []) +
    +
    +
    + + + + + + + + - return ( -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - - - + + +
    +
    ) -} - -const LogoWrapper = ({ logos, group, logoIndex = 0, className }: { logos: React.ReactNode[]; group?: string; logoIndex?: number; className?: string }) => { - return ( -
    - - - {logos[logoIndex]} - - -
    - ) } \ No newline at end of file diff --git a/apps/web/components/logo.tsx b/apps/web/components/logo.tsx new file mode 100644 index 0000000..3d192b2 --- /dev/null +++ b/apps/web/components/logo.tsx @@ -0,0 +1,69 @@ +import { cn } from '@workspace/ui/lib/utils' + +export const Logo = ({ className, uniColor }: { className?: string; uniColor?: boolean }) => { + return ( + + + + + + + + ) +} + +export const LogoIcon = ({ className, uniColor }: { className?: string; uniColor?: boolean }) => { + return ( + + + + + + + ) +} \ No newline at end of file diff --git a/apps/web/components/map.tsx b/apps/web/components/map.tsx new file mode 100644 index 0000000..de2fbbd --- /dev/null +++ b/apps/web/components/map.tsx @@ -0,0 +1,31 @@ +'use client' +import DottedMap from 'dotted-map' + +const map = new DottedMap({ height: 55, grid: 'vertical' }) + +const points = map.getPoints() + +const svgOptions = { + backgroundColor: 'var(--color-background)', + color: 'currentColor', + radius: 0.15, +} + +export const Map = () => { + const viewBox = `0 0 120 60` + return ( + + {points.map((point, index) => ( + + ))} + + ) +} \ No newline at end of file diff --git a/apps/web/components/particles.tsx b/apps/web/components/particles.tsx new file mode 100644 index 0000000..85b881a --- /dev/null +++ b/apps/web/components/particles.tsx @@ -0,0 +1,407 @@ +'use client' +import { useEffect, useMemo, useState } from 'react' +import Particles, { initParticlesEngine } from '@tsparticles/react' +import { type Container, type ISourceOptions, MoveDirection } from '@tsparticles/engine' +import { loadSlim } from '@tsparticles/slim' + +export const LightDarkParticles = ({ id }: { id: string }) => { + const [init, setInit] = useState(true) + + useEffect(() => { + initParticlesEngine(async (engine) => { + await loadSlim(engine) + }).then(() => { + setInit(true) + }) + }, []) + + const particlesLoaded = async (container?: Container): Promise => { + console.log(container) + } + + const options: ISourceOptions = useMemo( + () => ({ + background: { + color: { + value: 'transparent', + }, + }, + fullScreen: { + enable: false, + zIndex: 10, + }, + fpsLimit: 120, + interactivity: { + events: { + onClick: { + enable: true, + mode: 'push', + }, + onHover: { + enable: false, + mode: 'repulse', + }, + resize: { enable: true }, + }, + modes: { + push: { + quantity: 4, + }, + repulse: { + distance: 200, + duration: 0.4, + }, + }, + }, + particles: { + bounce: { + horizontal: { + value: 1, + }, + vertical: { + value: 1, + }, + }, + collisions: { + absorb: { + speed: 2, + }, + bounce: { + horizontal: { + value: 1, + }, + vertical: { + value: 1, + }, + }, + enable: false, + maxSpeed: 50, + mode: 'bounce', + overlap: { + enable: true, + retries: 0, + }, + }, + color: { + value: '#ffffff', + animation: { + h: { + count: 0, + enable: false, + speed: 1, + decay: 0, + delay: 0, + sync: true, + offset: 0, + }, + s: { + count: 0, + enable: false, + speed: 1, + decay: 0, + delay: 0, + sync: true, + offset: 0, + }, + l: { + count: 0, + enable: false, + speed: 1, + decay: 0, + delay: 0, + sync: true, + offset: 0, + }, + }, + }, + effect: { + close: true, + fill: true, + options: {}, + type: undefined, + }, + groups: {}, + move: { + angle: { + offset: 0, + value: 90, + }, + attract: { + distance: 200, + enable: false, + rotate: { + x: 3000, + y: 3000, + }, + }, + center: { + x: 50, + y: 50, + mode: 'percent', + radius: 0, + }, + decay: 0, + distance: {}, + direction: MoveDirection.none, // Set move direction to right + drift: 0, + enable: true, + gravity: { + acceleration: 9.81, + enable: false, + inverse: false, + maxSpeed: 50, + }, + path: { + clamp: true, + delay: { + value: 0, + }, + enable: false, + options: {}, + }, + outModes: { + default: 'out', + }, + random: false, + size: false, + speed: { + min: 0.1, + max: 1, + }, + spin: { + acceleration: 0, + enable: false, + }, + straight: false, + trail: { + enable: false, + length: 10, + fill: {}, + }, + vibrate: false, + warp: false, + }, + number: { + density: { + enable: true, + width: 400, + height: 400, + }, + limit: { + mode: 'delete', + value: 0, + }, + value: 120, + }, + opacity: { + value: { + min: 0.1, + max: 1, + }, + animation: { + count: 0, + enable: true, + speed: 4, + decay: 0, + delay: 0, + sync: false, + mode: 'auto', + startValue: 'random', + destroy: 'none', + }, + }, + reduceDuplicates: false, + shadow: { + blur: 0, + color: { + value: '#000', + }, + enable: false, + offset: { + x: 0, + y: 0, + }, + }, + shape: { + close: true, + fill: true, + options: {}, + type: 'circle', + }, + size: { + value: { + min: 0.25, + max: 1.5, + }, + animation: { + count: 0, + enable: false, + speed: 5, + decay: 0, + delay: 0, + sync: false, + mode: 'auto', + startValue: 'random', + destroy: 'none', + }, + }, + stroke: { + width: 0.25, + }, + zIndex: { + value: 0, + opacityRate: 1, + sizeRate: 1, + velocityRate: 1, + }, + destroy: { + bounds: {}, + mode: 'none', + split: { + count: 1, + factor: { + value: 3, + }, + rate: { + value: { + min: 4, + max: 9, + }, + }, + sizeOffset: true, + }, + }, + roll: { + darken: { + enable: false, + value: 0, + }, + enable: false, + enlighten: { + enable: false, + value: 0, + }, + mode: 'vertical', + speed: 25, + }, + tilt: { + value: 0, + animation: { + enable: false, + speed: 0, + decay: 0, + sync: false, + }, + direction: 'clockwise', + enable: false, + }, + twinkle: { + lines: { + enable: false, + frequency: 0.05, + opacity: 1, + }, + particles: { + enable: false, + frequency: 0.05, + opacity: 1, + }, + }, + wobble: { + distance: 5, + enable: false, + speed: { + angle: 50, + move: 10, + }, + }, + life: { + count: 0, + delay: { + value: 0, + sync: false, + }, + duration: { + value: 0, + sync: false, + }, + }, + rotate: { + value: 0, + animation: { + enable: false, + speed: 0, + decay: 0, + sync: false, + }, + direction: 'clockwise', + path: false, + }, + orbit: { + animation: { + count: 0, + enable: false, + speed: 1, + decay: 0, + delay: 0, + sync: false, + }, + enable: false, + opacity: 1, + rotation: { + value: 45, + }, + width: 1, + }, + links: { + blink: false, + color: { + value: '#ffffff', + }, + consent: false, + distance: 80, + enable: true, + frequency: 1, + opacity: 0.4, + shadow: { + blur: 5, + color: { + value: '#000', + }, + enable: false, + }, + triangles: { + enable: false, + frequency: 1, + }, + width: 0.5, + warp: false, + maxConnections: 2, + }, + repulse: { + value: 0, + enabled: false, + distance: 1, + duration: 1, + factor: 1, + speed: 1, + }, + }, + detectRetina: true, + }), + [] + ) + + if (init) { + return ( + + ) + } + + return <> +} \ No newline at end of file diff --git a/apps/web/components/pricing.tsx b/apps/web/components/pricing.tsx new file mode 100644 index 0000000..df60507 --- /dev/null +++ b/apps/web/components/pricing.tsx @@ -0,0 +1,176 @@ +'use client' +import { Button } from '@/components/ui/button' +import { Check } from 'lucide-react' +import Link from 'next/link' +import { CardTitle, CardDescription } from '@/components/ui/card' +import { useState } from 'react' +import NumberFlow from '@number-flow/react' + +export default function Pricing() { + const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'annually'>('annually') + const annualReduction = 0.75 + + const prices = { + pro: { + monthly: 19, + annually: 19 * annualReduction, + }, + startup: { + monthly: 49, + annually: 49 * annualReduction, + }, + } + + return ( +
    +
    +
    +

    Pricing that scale with your business

    +

    Choose the perfect plan for your needs and start optimizing your workflow today

    + +
    +
    +
    + + +
    +
    + Save 25% On Annual Billing +
    +
    +
    +
    +
    +
    +
    +
    + Free +
    For developers trying out Tailark for the first time
    +
    + +
    + +
    Per month
    +
    + + +
      + {['Basic Analytics Dashboard', '5GB Cloud Storage', 'Email and Chat Support'].map((item, index) => ( +
    • + + {item} +
    • + ))} +
    +
    +
    +
    + Pro + Ideal for developers who need more features and support +
    + +
    + +
    Per month
    +
    + + +
      + {['Everything in Free Plan plus:', '5GB Cloud Storage', 'Email and Chat Support', 'Access to Community Forum', 'Single User Access', 'Access to Basic Templates', 'Mobile App Access', '1 Custom Report Per Month', 'Monthly Product Updates', 'Standard Security Features'].map((item, index) => ( +
    • + + {item} +
    • + ))} +
    +
    +
    +
    + Startup + For startups that need more advanced features and support. +
    + +
    + +
    Per month
    +
    + + +
      + {['Everything in Pro Plan plus:', '5GB Cloud Storage', 'Email and Chat Support', 'Multi-User Access', '1 Custom Report Per Month', 'Monthly Product Updates', 'Standard Security Features', 'Access to Advanced Templates', 'Access to Community Forum', 'Mobile App Access'].map((item, index) => ( +
    • + + {item} +
    • + ))} +
    +
    +
    +
    +
    +
    +
    + ) +} \ No newline at end of file diff --git a/apps/web/components/ui/accordion.tsx b/apps/web/components/ui/accordion.tsx index bfb6ba1..4aa2a6d 100644 --- a/apps/web/components/ui/accordion.tsx +++ b/apps/web/components/ui/accordion.tsx @@ -4,7 +4,8 @@ import * as React from "react" import { Accordion as AccordionPrimitive } from "radix-ui" import { cn } from "@workspace/ui/lib/utils" -import { ChevronDown, ChevronUp } from "lucide-react" +import { HugeiconsIcon } from "@hugeicons/react" +import { ArrowDown01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons" function Accordion({ className, @@ -51,8 +52,8 @@ function AccordionTrigger({ {...props} > {children} - - + + ) diff --git a/apps/web/components/ui/card.tsx b/apps/web/components/ui/card.tsx new file mode 100644 index 0000000..583f57f --- /dev/null +++ b/apps/web/components/ui/card.tsx @@ -0,0 +1,70 @@ +import * as React from 'react' +import { cn } from '@workspace/ui/lib/utils' +import { Slot } from '@radix-ui/react-slot' + +export interface CardProps extends React.HTMLAttributes { + asChild?: boolean +} + +function Card({ className, asChild = false, ...props }: CardProps) { + const Comp = asChild ? Slot : 'div' + return ( + + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
    + ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
    + ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
    + ) +} + +function CardContent({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
    + ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
    + ) +} + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } \ No newline at end of file diff --git a/apps/web/components/ui/chart.tsx b/apps/web/components/ui/chart.tsx new file mode 100644 index 0000000..d8704e0 --- /dev/null +++ b/apps/web/components/ui/chart.tsx @@ -0,0 +1,373 @@ +"use client" + +import * as React from "react" +import * as RechartsPrimitive from "recharts" +import type { TooltipValueType } from "recharts" + +import { cn } from "@workspace/ui/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +const INITIAL_DIMENSION = { width: 320, height: 200 } as const +type TooltipNameType = number | string + +export type ChartConfig = Record< + string, + { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +> + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +function ChartContainer({ + id, + className, + children, + config, + initialDimension = INITIAL_DIMENSION, + ...props +}: React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] + initialDimension?: { + width: number + height: number + } +}) { + const uniqueId = React.useId() + const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` + + return ( + +
    + + + {children} + +
    +
    + ) +} + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme ?? config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( + + + + + + + + {/* animated paths */} + + + + + + + + + + + + + + + + + + + + + + +
    + {[ + { name: 'Vercel', icon: Vercel }, + { name: 'Supabase', icon: Supabase }, + { name: 'Firebase', icon: Firebase }, + ].map((node, index) => ( +
    +
    +
    + {node.name}
    Usage +
    + +
    + +
    +
    +
    +
    + {[1, 2].map((row) => ( +
    +
    +
    +
    + ))} + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ))} +
    + +
    +
    + +
    +
    + +
    +
    PDF
    + +
    +
    INVOICE
    + +
    + {[1, 2].map((row) => ( +
    +
    +
    +
    + ))} + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + ) +} + +export default FlowIllustration \ No newline at end of file diff --git a/apps/web/components/illustrations/models.tsx b/apps/web/components/illustrations/models.tsx new file mode 100644 index 0000000..f2700b9 --- /dev/null +++ b/apps/web/components/illustrations/models.tsx @@ -0,0 +1,63 @@ +import { Gemini } from '@/components/ui/svgs/gemini' +import { PerplexityAi } from '@/components/ui/svgs/perplexity-ai' +import { MistralAi } from '@/components/ui/svgs/mistral-ai' +import { Openai } from '@/components/ui/svgs/openai' +import { Deepseek } from '@/components/ui/svgs/deepseek' +import { QwenLight as Qwen } from '@/components/ui/svgs/qwen' +import { Cohere } from '@/components/ui/svgs/cohere' +import { Play } from 'lucide-react' + +type model = { + name: string + icon: React.ReactNode +} + +export const ModelsIllustration = () => { + const upModels: model[] = [ + { name: 'Gemini', icon: }, + { name: 'Perplexity', icon: }, + { name: 'Deepseek', icon: }, + ] + + const bottomModels: model[] = [ + { name: 'Cohere AI', icon: }, + { name: 'Open AI', icon: }, + { name: 'Qwen', icon: }, + ] + + return ( +
    +
    + +
    +
    + {upModels.map((model, index) => ( +
    + {model.icon} + {model.name} +
    + ))} +
    + + Mistral Chat +
    + {bottomModels.map((model, index) => ( +
    + {model.icon} + {model.name} +
    + ))} +
    +
    + ) +} + +export default ModelsIllustration \ No newline at end of file diff --git a/apps/web/components/illustrations/workflow.tsx b/apps/web/components/illustrations/workflow.tsx new file mode 100644 index 0000000..1173e4f --- /dev/null +++ b/apps/web/components/illustrations/workflow.tsx @@ -0,0 +1,52 @@ +import { Linear } from '@/components/ui/svgs/linear' +import { Vercel } from '@/components/ui/svgs/vercel' +import { CheckCircle2, GitBranch } from 'lucide-react' + +export const WorkflowIllustration = () => { + return ( +
    +
    +
    + + Workflow completed +
    +
    +
    +
    +
    +
    + + + Issue created 12s ago + +
    +
    + +
    +
    +
    + + + Branch created 3s ago + +
    +
    + +
    +
    +
    + + + Preview deployed now + +
    +
    +
    +
    +
    + ) +} + +export default WorkflowIllustration \ No newline at end of file diff --git a/apps/web/components/ui/svgs/cohere.tsx b/apps/web/components/ui/svgs/cohere.tsx new file mode 100644 index 0000000..9f783ae --- /dev/null +++ b/apps/web/components/ui/svgs/cohere.tsx @@ -0,0 +1,20 @@ +import type { SVGProps } from "react"; + +const Cohere = (props: SVGProps) => ( + + + + + +); + +export { Cohere }; \ No newline at end of file diff --git a/apps/web/components/ui/svgs/deepseek.tsx b/apps/web/components/ui/svgs/deepseek.tsx new file mode 100644 index 0000000..73f61f3 --- /dev/null +++ b/apps/web/components/ui/svgs/deepseek.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from "react"; + +const Deepseek = (props: SVGProps) => ( + + + +); + +export { Deepseek }; \ No newline at end of file diff --git a/apps/web/components/ui/svgs/firebase.tsx b/apps/web/components/ui/svgs/firebase.tsx new file mode 100644 index 0000000..5243eae --- /dev/null +++ b/apps/web/components/ui/svgs/firebase.tsx @@ -0,0 +1,24 @@ +import type { SVGProps } from "react"; + +const Firebase = (props: SVGProps) => ( + + + + + + +); + +export { Firebase }; \ No newline at end of file diff --git a/apps/web/components/ui/svgs/mistral-ai.tsx b/apps/web/components/ui/svgs/mistral-ai.tsx new file mode 100644 index 0000000..d3d76af --- /dev/null +++ b/apps/web/components/ui/svgs/mistral-ai.tsx @@ -0,0 +1,57 @@ +import type { SVGProps } from 'react' + +const MistralAi = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + +) + +export { MistralAi } \ No newline at end of file diff --git a/apps/web/components/ui/svgs/openai.tsx b/apps/web/components/ui/svgs/openai.tsx new file mode 100644 index 0000000..8ffb1ed --- /dev/null +++ b/apps/web/components/ui/svgs/openai.tsx @@ -0,0 +1,9 @@ +import type { SVGProps } from "react"; + +const Openai = (props: SVGProps) => ( + + + +); + +export { Openai }; \ No newline at end of file diff --git a/apps/web/components/ui/svgs/perplexity-ai.tsx b/apps/web/components/ui/svgs/perplexity-ai.tsx new file mode 100644 index 0000000..3af0b66 --- /dev/null +++ b/apps/web/components/ui/svgs/perplexity-ai.tsx @@ -0,0 +1,31 @@ +import type { SVGProps } from 'react' + +const PerplexityAi = (props: SVGProps) => ( + + + + + +) + +export { PerplexityAi } \ No newline at end of file diff --git a/apps/web/components/ui/svgs/qwen.tsx b/apps/web/components/ui/svgs/qwen.tsx new file mode 100644 index 0000000..490c917 --- /dev/null +++ b/apps/web/components/ui/svgs/qwen.tsx @@ -0,0 +1,27 @@ +import type { SVGProps } from 'react' + +const QwenDark = (props: SVGProps) => ( + + Qwen + + +) + +const QwenLight = (props: SVGProps) => ( + + Qwen + + +) + +export { QwenDark, QwenLight } \ No newline at end of file diff --git a/scripts/sync-templates.mjs b/scripts/sync-templates.mjs index eb25184..f1d8f73 100644 --- a/scripts/sync-templates.mjs +++ b/scripts/sync-templates.mjs @@ -141,6 +141,9 @@ const HOSTED_TIER_EXCLUDES = [ "components/ui/navigation-menu.tsx", "components/ui/illustrations/", "components/ui/svgs/", + // Tailark Pro illustrations installed via shadcn registry land here. + // Used exclusively by the marketing Tailark blocks above. + "components/illustrations/", "hooks/use-media.ts", ] From 19e6146c0207eaa02376cbfbfd5622180ee786d1 Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Sun, 19 Apr 2026 23:03:06 +0200 Subject: [PATCH 24/28] style(design): swap 4 more illustrations with Tailark Pro picks (round 2) Second-pass audit swapped the remaining weak-fit illustrations: - features MDX card: DocumentIllustation \u2192 DocumentMdIllustration (literal 'MD' badge on a document \u2014 matches '.mdx file in repo') - bento Structured agent output: ReplyIllustration \u2192 AgentTasksIllustration (shows '1/3 tasks done' with Fetch/Analyze/Generate list \u2014 matches 'typed tool calls: list articles, read a specific slug, search') - bento Docs sync with code: VisualizationIllustration \u2192 DocumentAnalysisIllustration (stylized source document \u2014 matches 'sync reads your source and proposes MDX edits'; the registry ships an animate-scan overlay that requires custom keyframes we haven't added, so the doc renders statically for now, which still reads well) - bento Hosted tier: MapIllustration \u2192 UptimeIllustration (clean 'Uptime 99.9%' bar chart \u2014 matches 'helpbase deploy... no servers to manage' better than a map) Installed via: pnpm dlx shadcn@latest add @tailark-pro/{document-md, agent-tasks,document-analysis,uptime} components/illustrations/ was already in HOSTED_TIER_EXCLUDES from the first round, so no template drift. Tests 155/155, typecheck clean, localhost verified. Kept CodeIllustration (Install step) and DocumentIllustation (Preview step) \u2014 they fit their sections adequately and swapping would be churn without meaningful upgrade. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/bento-2.tsx | 18 ++--- apps/web/components/features-1.tsx | 4 +- .../components/illustrations/agent-tasks.tsx | 43 ++++++++++++ .../illustrations/document-analysis.tsx | 68 +++++++++++++++++++ .../components/illustrations/document-md.tsx | 39 +++++++++++ apps/web/components/illustrations/uptime.tsx | 19 ++++++ 6 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 apps/web/components/illustrations/agent-tasks.tsx create mode 100644 apps/web/components/illustrations/document-analysis.tsx create mode 100644 apps/web/components/illustrations/document-md.tsx create mode 100644 apps/web/components/illustrations/uptime.tsx diff --git a/apps/web/components/bento-2.tsx b/apps/web/components/bento-2.tsx index 825e059..6479dbd 100644 --- a/apps/web/components/bento-2.tsx +++ b/apps/web/components/bento-2.tsx @@ -1,8 +1,8 @@ -import { ReplyIllustration } from "@/components/ui/illustrations/reply-illustration" +import { AgentTasksIllustration } from "@/components/illustrations/agent-tasks" import { ModelsIllustration } from "@/components/illustrations/models" import { Card } from '@/components/ui/card' -import { MapIllustration } from "@/components/ui/illustrations/map-illustration" -import { VisualizationIllustration } from "@/components/ui/illustrations/visualization-illustration" +import { UptimeIllustration } from "@/components/illustrations/uptime" +import { DocumentAnalysisIllustration } from "@/components/illustrations/document-analysis" export default function AiNativeBento() { return ( @@ -46,9 +46,9 @@ export default function AiNativeBento() {
    -
    +
    - +

    Structured agent output

    @@ -58,8 +58,8 @@ export default function AiNativeBento() {
    -
    - +
    +

    Docs that stay in sync with code

    @@ -69,8 +69,8 @@ export default function AiNativeBento() {
    -
    - +
    +

    Hosted tier, if you want it

    diff --git a/apps/web/components/features-1.tsx b/apps/web/components/features-1.tsx index f782f9a..0100448 100644 --- a/apps/web/components/features-1.tsx +++ b/apps/web/components/features-1.tsx @@ -1,5 +1,5 @@ import { FlowIllustration } from "@/components/illustrations/flow" -import { DocumentIllustation } from "@/components/ui/illustrations/document-illustration" +import { DocumentMdIllustration } from "@/components/illustrations/document-md" export default function FeaturesOwnIt() { return ( @@ -26,7 +26,7 @@ export default function FeaturesOwnIt() {

    - +
    diff --git a/apps/web/components/illustrations/agent-tasks.tsx b/apps/web/components/illustrations/agent-tasks.tsx new file mode 100644 index 0000000..13e616a --- /dev/null +++ b/apps/web/components/illustrations/agent-tasks.tsx @@ -0,0 +1,43 @@ +import { CircleDashed } from 'lucide-react' + +export const AgentTasksIllustration = () => { + return ( +
    +
    create, agent-tasks illustration
    + +
    + Thought for 4s +
    + +
    +
    + 1 / 3 tasks done +
    +
    +
      +
    • + 1 + Fetch user data +
    • +
    • + + Analyze purchase history +
    • +
    • + + Generate recommendations +
    • +
    +
    +
    + +
    + Read streaming-response.tsx +
    +
    + ) +} + +export default AgentTasksIllustration \ No newline at end of file diff --git a/apps/web/components/illustrations/document-analysis.tsx b/apps/web/components/illustrations/document-analysis.tsx new file mode 100644 index 0000000..445bd1a --- /dev/null +++ b/apps/web/components/illustrations/document-analysis.tsx @@ -0,0 +1,68 @@ +import { cn } from '@workspace/ui/lib/utils' + +export const DocumentAnalysisIllustration = () => { + return ( +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + ) +} + +export const CardDecorator = ({ className }: { className?: string }) => ( + <> + + + + + +) + +export default DocumentAnalysisIllustration \ No newline at end of file diff --git a/apps/web/components/illustrations/document-md.tsx b/apps/web/components/illustrations/document-md.tsx new file mode 100644 index 0000000..64a3b4f --- /dev/null +++ b/apps/web/components/illustrations/document-md.tsx @@ -0,0 +1,39 @@ +export const DocumentMdIllustration = () => { + return ( +
    +
    MD
    +
    +
    +
    +
    #
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    ##
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ) +} + +export default DocumentMdIllustration \ No newline at end of file diff --git a/apps/web/components/illustrations/uptime.tsx b/apps/web/components/illustrations/uptime.tsx new file mode 100644 index 0000000..d2daf89 --- /dev/null +++ b/apps/web/components/illustrations/uptime.tsx @@ -0,0 +1,19 @@ +export const UptimeIllustration = () => ( +
    +
    + Uptime + 99.9% +
    +
    + {Array.from({ length: 40 }).map((_, index) => ( +
    + ))} +
    +
    +) + +export default UptimeIllustration \ No newline at end of file From e04c99c9ff36c7a0eac0c31bbdf2525e72759574 Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Sun, 19 Apr 2026 23:10:32 +0200 Subject: [PATCH 25/28] style(design): custom MDX + Preview illustrations (LlmsTxtPreview pattern) Round 2 picked document-md and a generic doc illustration for 'MDX all the way down' and 'Preview', respectively. Both rendered as small icons with barely any on-topic content \u2014 the MDX badge alone doesn't say 'MDX = markdown + React components', and a generic doc doesn't say 'shareable preview URL'. User flagged both. Built two custom illustrations matching the LlmsTxtPreview pattern (the page's strongest illustration, since it shows real manifest content instead of a pictogram): MdxSourcePreview (features \u2192 MDX card): Shows content/getting-started.mdx with real MDX source: frontmatter, markdown heading + body, a JSX component with children. The card now literally demonstrates the copy's claim that you can import React components into .mdx. PreviewUrlCard (how-it-works \u2192 Preview step): Shows a 'Preview ready' card with docs-pr-42.helpbase.dev URL, a globe icon, a Copy link button, and a 'Deploy check passed' indicator with pulse-animated status dot. Reads like Vercel's preview deployment card \u2014 exactly the mental model the copy is reaching for. Removed the unused document-md.tsx (shadcn-installed in round 2, now orphaned). components/illustrations/ is in HOSTED_TIER_EXCLUDES, so no template drift. Tests 155/155, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/features-1.tsx | 6 +-- apps/web/components/how-it-works-3.tsx | 4 +- .../components/illustrations/document-md.tsx | 39 ----------------- .../illustrations/mdx-source-preview.tsx | 38 +++++++++++++++++ .../illustrations/preview-url-card.tsx | 42 +++++++++++++++++++ 5 files changed, 85 insertions(+), 44 deletions(-) delete mode 100644 apps/web/components/illustrations/document-md.tsx create mode 100644 apps/web/components/illustrations/mdx-source-preview.tsx create mode 100644 apps/web/components/illustrations/preview-url-card.tsx diff --git a/apps/web/components/features-1.tsx b/apps/web/components/features-1.tsx index 0100448..96055e7 100644 --- a/apps/web/components/features-1.tsx +++ b/apps/web/components/features-1.tsx @@ -1,5 +1,5 @@ import { FlowIllustration } from "@/components/illustrations/flow" -import { DocumentMdIllustration } from "@/components/illustrations/document-md" +import { MdxSourcePreview } from "@/components/illustrations/mdx-source-preview" export default function FeaturesOwnIt() { return ( @@ -25,8 +25,8 @@ export default function FeaturesOwnIt() { Every article is a plain .mdx file in help-center/content. Import React components, version control every change, diff in PR review.

    -
    - +
    +
    diff --git a/apps/web/components/how-it-works-3.tsx b/apps/web/components/how-it-works-3.tsx index 37b14e5..3728f1b 100644 --- a/apps/web/components/how-it-works-3.tsx +++ b/apps/web/components/how-it-works-3.tsx @@ -1,6 +1,6 @@ import { cn } from '@workspace/ui/lib/utils' import { CodeIllustration } from "@/components/ui/illustrations/code-illustration" -import { DocumentIllustation } from "@/components/ui/illustrations/document-illustration" +import { PreviewUrlCard } from "@/components/illustrations/preview-url-card" import { WorkflowIllustration } from "@/components/illustrations/workflow" const steps = [ @@ -12,7 +12,7 @@ const steps = [ { title: "Preview", body: "pnpm dev runs it locally. helpbase deploy --preview pushes a draft to a shareable URL without touching your production site.", - visual: , + visual: , }, { title: "Deploy", diff --git a/apps/web/components/illustrations/document-md.tsx b/apps/web/components/illustrations/document-md.tsx deleted file mode 100644 index 64a3b4f..0000000 --- a/apps/web/components/illustrations/document-md.tsx +++ /dev/null @@ -1,39 +0,0 @@ -export const DocumentMdIllustration = () => { - return ( -
    -
    MD
    -
    -
    -
    -
    #
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    ##
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - ) -} - -export default DocumentMdIllustration \ No newline at end of file diff --git a/apps/web/components/illustrations/mdx-source-preview.tsx b/apps/web/components/illustrations/mdx-source-preview.tsx new file mode 100644 index 0000000..31dd286 --- /dev/null +++ b/apps/web/components/illustrations/mdx-source-preview.tsx @@ -0,0 +1,38 @@ +export const MdxSourcePreview = () => ( +
    +
    + content/getting-started.mdx +
    +
    +
    ---
    +
    + title:{" "} + Getting started +
    +
    + order:{" "} + 1 +
    +
    ---
    +
    +
    # Install helpbase
    +
    +
    + Run one command in your repo to +
    +
    + create a full Next.js help center. +
    +
    +
    {``}
    +
    + Edit this file to add your own content. +
    +
    {``}
    +
    +
    +) + +export default MdxSourcePreview diff --git a/apps/web/components/illustrations/preview-url-card.tsx b/apps/web/components/illustrations/preview-url-card.tsx new file mode 100644 index 0000000..e16e2be --- /dev/null +++ b/apps/web/components/illustrations/preview-url-card.tsx @@ -0,0 +1,42 @@ +import { Check, Copy, Globe } from "lucide-react" + +export const PreviewUrlCard = () => ( +
    +
    + + + + + + Preview ready + + + 12s ago + +
    + +
    + + + docs-pr-42.helpbase.dev + +
    + +
    + +
    + + Deploy check passed +
    +
    +
    +) + +export default PreviewUrlCard From d17013298f86fa74d928df57850ee0907ea2792a Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 08:31:44 +0200 Subject: [PATCH 26/28] style(design): DR3-001 \u2014 shorten comparator feature names The fixed h-14 row height meant any feature label that wrapped past 2 lines overflowed into the next row \u2014 'MCP server (agent-consumable docs)' stacked visually on top of 'llms.txt + structured agent output', so readers couldn't tell which column value belonged to which feature. Shortened 5 labels so they fit in \u2264 2 lines at every viewport: Time to first help center \u2192 Time to first site MCP server (agent-consumable docs) \u2192 MCP server llms.txt + structured agent output \u2192 llms.txt + typed output Deploy anywhere (Vercel / Fly / self-host) \u2192 Deploy anywhere AI tools can read the docs \u2192 Agents can read it Detail that was trimmed from the labels moves into the tooltip descriptions (the ? icons) so hover still surfaces the full context. The descriptions were already there \u2014 just now carrying more weight. Verified against localhost:3000: every feature row aligns with its three plan values. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/comparator-7.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/components/comparator-7.tsx b/apps/web/components/comparator-7.tsx index 0445fda..7c2cea6 100644 --- a/apps/web/components/comparator-7.tsx +++ b/apps/web/components/comparator-7.tsx @@ -21,7 +21,7 @@ const planLabels: Record = { const features: Feature[] = [ { - name: 'Time to first help center', + name: 'Time to first site', description: 'From zero to a live docs site on your domain.', plans: { rollYourOwn: '2-3 days', hostedSaas: '1 day', helpbase: '3 min' }, }, @@ -31,18 +31,18 @@ const features: Feature[] = [ plans: { rollYourOwn: true, hostedSaas: false, helpbase: true }, }, { - name: 'MCP server (agent-consumable docs)', + name: 'MCP server', description: 'Model Context Protocol endpoint Claude / Cursor / ChatGPT can query.', plans: { rollYourOwn: 'DIY', hostedSaas: 'Limited', helpbase: 'Built in' }, }, { - name: 'llms.txt + structured agent output', + name: 'llms.txt + typed output', description: 'Discoverability manifest + machine-readable content, by default.', plans: { rollYourOwn: false, hostedSaas: false, helpbase: true }, }, { - name: 'Deploy anywhere (Vercel / Fly / self-host)', - description: 'No platform lock-in. Ship your docs on whatever infra you already use.', + name: 'Deploy anywhere', + description: 'No platform lock-in. Ship your docs on whatever infra you already use \u2014 Vercel, Fly, self-host, your call.', plans: { rollYourOwn: true, hostedSaas: false, helpbase: true }, }, { @@ -56,7 +56,7 @@ const features: Feature[] = [ plans: { rollYourOwn: false, hostedSaas: true, helpbase: 'Hosted tier' }, }, { - name: 'AI tools can read the docs', + name: 'Agents can read it', description: 'Your editor autocompletes from your real docs instead of guessing.', plans: { rollYourOwn: 'DIY', hostedSaas: 'Limited', helpbase: true }, }, From c6d30a84350dcf0b19a336099df207f3924223f1 Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 08:32:54 +0200 Subject: [PATCH 27/28] style(design): DR3-002 \u2014 custom InstallCommandPreview replaces PHP curl stub CodeIllustration ships with a hardcoded PHP curl example ( --- apps/web/components/how-it-works-3.tsx | 4 +-- .../illustrations/install-command-preview.tsx | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 apps/web/components/illustrations/install-command-preview.tsx diff --git a/apps/web/components/how-it-works-3.tsx b/apps/web/components/how-it-works-3.tsx index 3728f1b..60be0fb 100644 --- a/apps/web/components/how-it-works-3.tsx +++ b/apps/web/components/how-it-works-3.tsx @@ -1,5 +1,5 @@ import { cn } from '@workspace/ui/lib/utils' -import { CodeIllustration } from "@/components/ui/illustrations/code-illustration" +import { InstallCommandPreview } from "@/components/illustrations/install-command-preview" import { PreviewUrlCard } from "@/components/illustrations/preview-url-card" import { WorkflowIllustration } from "@/components/illustrations/workflow" @@ -7,7 +7,7 @@ const steps = [ { title: "Install", body: "Run pnpm dlx create-helpbase in your repo. You get a Next.js app with shadcn/ui, MDX content, an MCP server, and an llms.txt. Every file is yours.", - visual: , + visual: , }, { title: "Preview", diff --git a/apps/web/components/illustrations/install-command-preview.tsx b/apps/web/components/illustrations/install-command-preview.tsx new file mode 100644 index 0000000..deed81a --- /dev/null +++ b/apps/web/components/illustrations/install-command-preview.tsx @@ -0,0 +1,35 @@ +export const InstallCommandPreview = () => ( +
    +
    + + + +
    + +
    +
    + $ + pnpm dlx create-helpbase +
    +
    + Help center created at{" "} + ./help-center +
    +
    + MDX, shadcn/ui, MCP server, llms.txt +
    +
    + Installing dependencies… +
    +
    + Ready.{" "} + Next:{" "} + cd help-center && pnpm dev +
    +
    +
    +) + +export default InstallCommandPreview From 5968da12882c13916363e8e1125d41dfe2dbd3eb Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 08:34:49 +0200 Subject: [PATCH 28/28] style(design): DR3-003 \u2014 retheme FlowIllustration output from invoice to docs The Tailark registry ships flow-default as a cloud-usage-metering illustration: platforms feed 'Usage' data to a central node that produces an INVOICE.pdf. We imported it for the 'Zero vendor runtime' card to carry the 'deploy anywhere' narrative, but the invoice output read like billing, not like docs \u2014 wrong mental model for the copy. Two edits: - Dropped the 'Usage' subtitle from each platform card. Was rendered as a two-line 'Vercel / Usage' label; now just 'Vercel' (same for Supabase, Firebase). Matches 'deploy to any of these' not 'meter usage from these'. - Output node: 'INVOICE' title + rose 'PDF' badge \u2192 'help-center' title + emerald 'MDX' badge. Now the flow reads: platforms \u2194 helpbase \u2194 help-center/MDX. Aligned with the card's narrative that your repo lives in MDX and ships to any platform. The animated beam paths and hub structure are untouched \u2014 the visual vocabulary stays, just the text labels now say what we mean. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/illustrations/flow.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/components/illustrations/flow.tsx b/apps/web/components/illustrations/flow.tsx index 9331db9..1008e7e 100644 --- a/apps/web/components/illustrations/flow.tsx +++ b/apps/web/components/illustrations/flow.tsx @@ -144,7 +144,7 @@ export const FlowIllustration = () => { className="bg-illustration ring-border-illustration shadow-black/6.5 row-span-3 grid w-28 grid-rows-subgrid gap-3 rounded-xl p-3 shadow-md ring-1">
    - {node.name}
    Usage + {node.name}
    @@ -190,10 +190,10 @@ export const FlowIllustration = () => {
    -
    PDF
    +
    MDX
    -
    INVOICE
    +
    help-center
    {[1, 2].map((row) => (