mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-11 22:53:00 +00:00
Add Showcase section to marketing site
Gallery of workflow recipes with index grid and detail pages. Three sample entries: PR Review Bot, Test Generator, Docs Sync. Add Roadmap link to shared Nav component and homepage nav. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dcad7059d4
commit
f80f52920b
9 changed files with 497 additions and 4 deletions
|
|
@ -12,6 +12,7 @@
|
|||
<li><a href="https://docs.fabro.sh/getting-started/quick-start" class="text-ice-300 hover:text-ice-50 transition-colors">Quick Start</a></li>
|
||||
<li><a href="https://docs.fabro.sh" class="text-ice-300 hover:text-ice-50 transition-colors">Docs</a></li>
|
||||
<li><a href="/blog" class="text-ice-300 hover:text-ice-50 transition-colors">Blog</a></li>
|
||||
<li><a href="/showcase" class="text-ice-300 hover:text-ice-50 transition-colors">Showcase</a></li>
|
||||
<li><a href="/roadmap" class="text-ice-300 hover:text-ice-50 transition-colors">Roadmap</a></li>
|
||||
<li><a href="https://docs.fabro.sh/changelog" class="text-ice-300 hover:text-ice-50 transition-colors">Changelog</a></li>
|
||||
<li><a href="https://github.com/fabro-sh/fabro/releases" class="text-ice-300 hover:text-ice-50 transition-colors">Releases</a></li>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
interface Props {
|
||||
currentPage?: "blog" | "roadmap";
|
||||
currentPage?: "blog" | "roadmap" | "showcase";
|
||||
}
|
||||
|
||||
const { currentPage } = Astro.props;
|
||||
|
|
@ -19,6 +19,8 @@ const linkClass = (page?: string) =>
|
|||
</a>
|
||||
<a href="https://docs.fabro.sh" class={`hidden md:inline-flex text-sm font-medium transition-colors ${linkClass()}`}>Docs</a>
|
||||
<a href="/blog" class={`hidden md:inline-flex text-sm font-medium transition-colors ${linkClass("blog")}`}>Blog</a>
|
||||
<a href="/showcase" class={`hidden md:inline-flex text-sm font-medium transition-colors ${linkClass("showcase")}`}>Showcase</a>
|
||||
<a href="/roadmap" class={`hidden md:inline-flex text-sm font-medium transition-colors ${linkClass("roadmap")}`}>Roadmap</a>
|
||||
<a href="https://docs.fabro.sh/changelog" class={`hidden md:inline-flex text-sm font-medium transition-colors ${linkClass()}`}>Changelog</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-5">
|
||||
|
|
@ -45,6 +47,8 @@ const linkClass = (page?: string) =>
|
|||
<div class="px-6 py-5 space-y-4">
|
||||
<a href="https://docs.fabro.sh" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Docs</a>
|
||||
<a href="/blog" class={`block text-sm font-medium transition-colors ${linkClass("blog")}`}>Blog</a>
|
||||
<a href="/showcase" class={`block text-sm font-medium transition-colors ${linkClass("showcase")}`}>Showcase</a>
|
||||
<a href="/roadmap" class={`block text-sm font-medium transition-colors ${linkClass("roadmap")}`}>Roadmap</a>
|
||||
<a href="https://docs.fabro.sh/changelog" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Changelog</a>
|
||||
<hr class="border-navy-800/60" />
|
||||
<div class="flex items-center gap-5">
|
||||
|
|
|
|||
|
|
@ -22,4 +22,21 @@ const blog = defineCollection({
|
|||
}),
|
||||
});
|
||||
|
||||
export const collections = { roadmap, blog };
|
||||
const showcase = defineCollection({
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/showcase" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
thumbnail: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
languages: z.array(z.enum(["python", "rust", "typescript", "ruby"])),
|
||||
github: z.string(),
|
||||
models: z.array(z.string()),
|
||||
skills: z.array(z.string()),
|
||||
prompt: z.string(),
|
||||
workflow: z.string(),
|
||||
sortOrder: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { roadmap, blog, showcase };
|
||||
|
|
|
|||
46
apps/marketing/src/content/showcase/docs-sync.md
Normal file
46
apps/marketing/src/content/showcase/docs-sync.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Docs Sync"
|
||||
description: "Keeps documentation in sync with code changes by detecting drift and auto-updating affected pages."
|
||||
thumbnail: "/showcase/docs-sync.png"
|
||||
tags: ["documentation", "automation", "ci-cd"]
|
||||
languages: ["typescript"]
|
||||
github: "https://github.com/fabro-sh/fabro/tree/main/examples/docs-sync"
|
||||
models: ["claude-sonnet-4-5"]
|
||||
skills: ["code-review", "git", "documentation"]
|
||||
prompt: "Compare the current codebase against the documentation. Identify any docs pages that are out of date with the code — changed APIs, renamed functions, removed features, or new features without docs. Update each affected page to match the current code, preserving the existing writing style and structure."
|
||||
workflow: |
|
||||
digraph DocsSync {
|
||||
graph [
|
||||
goal="Detect and fix documentation drift from code changes"
|
||||
model_stylesheet="
|
||||
* { model: claude-sonnet-4-5; }
|
||||
"
|
||||
]
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
diff [label="Get Changes", prompt="Identify code changes since the last docs sync."]
|
||||
scan [label="Scan Docs", prompt="Find documentation pages that reference changed code."]
|
||||
update [label="Update Docs", prompt="Rewrite affected doc sections to match current code."]
|
||||
review [shape=hexagon, label="Review Updates"]
|
||||
|
||||
start -> diff -> scan -> update -> review
|
||||
review -> exit [label="Approve"]
|
||||
review -> update [label="Revise"]
|
||||
}
|
||||
sortOrder: 3
|
||||
---
|
||||
|
||||
The Docs Sync workflow detects when documentation has drifted from the codebase and automatically updates affected pages — with a human review gate before changes are committed.
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Get Changes** — Identifies code changes since the last documentation sync using git history.
|
||||
2. **Scan Docs** — Searches documentation pages for references to changed functions, APIs, types, and features.
|
||||
3. **Update Docs** — Rewrites affected sections to match the current code, preserving the original writing style and page structure.
|
||||
4. **Human Review** — Updated pages are presented for review. Approve to commit, or send back for revision.
|
||||
|
||||
## Keeping docs honest
|
||||
|
||||
Documentation drift is one of the most common sources of developer frustration. This workflow runs on every merge to main, catching drift before it reaches users. Because it understands both the code and the docs, it can make precise, targeted updates rather than generic rewrites.
|
||||
45
apps/marketing/src/content/showcase/pr-review-bot.md
Normal file
45
apps/marketing/src/content/showcase/pr-review-bot.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: "PR Review Bot"
|
||||
description: "Automated code review that catches bugs, style issues, and security concerns before human reviewers see the PR."
|
||||
thumbnail: "/showcase/pr-review-bot.png"
|
||||
tags: ["code-review", "ci-cd", "github"]
|
||||
languages: ["typescript", "python"]
|
||||
github: "https://github.com/fabro-sh/fabro/tree/main/examples/pr-review-bot"
|
||||
models: ["claude-sonnet-4-5", "claude-haiku-4-5"]
|
||||
skills: ["code-review", "git", "github"]
|
||||
prompt: "Review this pull request for bugs, security issues, and style violations. Focus on logic errors and potential runtime failures. Summarize findings as inline comments on the diff, then produce a top-level review with an overall assessment and a clear approve/request-changes verdict."
|
||||
workflow: |
|
||||
digraph PRReview {
|
||||
graph [
|
||||
goal="Review a pull request for bugs, security, and style"
|
||||
model_stylesheet="
|
||||
* { model: claude-haiku-4-5; }
|
||||
.review { model: claude-sonnet-4-5; }
|
||||
"
|
||||
]
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
fetch [label="Fetch Diff", prompt="Fetch the PR diff and changed file contents."]
|
||||
triage [label="Triage Files", prompt="Categorize changed files by risk level."]
|
||||
review [label="Deep Review", class="review", prompt="Review high-risk files for bugs, security issues, and style."]
|
||||
comment [label="Post Comments", prompt="Post inline comments and a summary review on the PR."]
|
||||
|
||||
start -> fetch -> triage -> review -> comment -> exit
|
||||
}
|
||||
sortOrder: 1
|
||||
---
|
||||
|
||||
The PR Review Bot workflow automates the first pass of code review on every pull request. It fetches the diff, triages files by risk level, performs a deep review on high-risk changes using a frontier model, and posts structured feedback directly on the PR.
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Fetch Diff** — Pulls the PR diff and full contents of changed files using the GitHub API.
|
||||
2. **Triage Files** — A fast model categorizes each changed file as high, medium, or low risk based on the type of change (new logic vs. formatting, test files vs. production code).
|
||||
3. **Deep Review** — A frontier model examines high-risk files for logic errors, security vulnerabilities, race conditions, and style violations.
|
||||
4. **Post Comments** — Inline comments are posted on specific lines, and a top-level review summary gives an overall verdict.
|
||||
|
||||
## Cost optimization
|
||||
|
||||
By using a model stylesheet, the workflow routes expensive frontier-model calls only to the deep review stage. File fetching and triaging use a fast, cheap model — keeping the total cost per review under $0.10 for most PRs.
|
||||
52
apps/marketing/src/content/showcase/test-generator.md
Normal file
52
apps/marketing/src/content/showcase/test-generator.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: "Test Generator"
|
||||
description: "Generates comprehensive test suites from source code, covering edge cases and error paths that humans often miss."
|
||||
thumbnail: "/showcase/test-generator.png"
|
||||
tags: ["testing", "code-generation", "automation"]
|
||||
languages: ["typescript", "rust"]
|
||||
github: "https://github.com/fabro-sh/fabro/tree/main/examples/test-generator"
|
||||
models: ["claude-sonnet-4-5", "claude-haiku-4-5"]
|
||||
skills: ["code-review", "testing", "code-generation"]
|
||||
prompt: "Analyze the source files in this project and generate a comprehensive test suite. For each public function or method, write tests covering: happy path, edge cases, error conditions, and boundary values. Use the project's existing test framework and conventions. Run the tests and fix any failures before finishing."
|
||||
workflow: |
|
||||
digraph TestGenerator {
|
||||
graph [
|
||||
goal="Generate and validate a test suite for source code"
|
||||
model_stylesheet="
|
||||
* { model: claude-haiku-4-5; }
|
||||
.analysis { model: claude-sonnet-4-5; }
|
||||
.coding { model: claude-sonnet-4-5; }
|
||||
"
|
||||
]
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
analyze [label="Analyze Source", class="analysis", prompt="Read source files and identify all public interfaces, edge cases, and error paths."]
|
||||
plan [label="Plan Tests", prompt="Create a test plan covering happy paths, edge cases, and error conditions."]
|
||||
approve [shape=hexagon, label="Approve Plan"]
|
||||
generate [label="Generate Tests", class="coding", prompt="Write the test suite following the plan and project conventions."]
|
||||
run [label="Run Tests", prompt="Execute the test suite and collect results."]
|
||||
fix [label="Fix Failures", class="coding", prompt="Fix any failing tests, ensuring they test the right behavior."]
|
||||
|
||||
start -> analyze -> plan -> approve
|
||||
approve -> generate [label="Approve"]
|
||||
approve -> plan [label="Revise"]
|
||||
generate -> run -> fix -> run
|
||||
run -> exit [label="All pass"]
|
||||
}
|
||||
sortOrder: 2
|
||||
---
|
||||
|
||||
The Test Generator workflow reads your source code, plans a comprehensive test suite, and writes tests that actually pass — with a human checkpoint to approve the plan before generation begins.
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Analyze Source** — A frontier model reads the codebase and identifies all public functions, methods, and types that need test coverage.
|
||||
2. **Plan Tests** — Generates a structured test plan covering happy paths, edge cases, boundary values, and error conditions.
|
||||
3. **Human Approval** — The plan is presented for review. You can approve it or send it back for revision.
|
||||
4. **Generate & Validate** — Tests are written following your project's conventions, then executed. Any failures are automatically fixed in a retry loop.
|
||||
|
||||
## Why a workflow beats a single prompt
|
||||
|
||||
A single "write tests" prompt often produces tests that don't compile or test the wrong things. By separating analysis, planning, and generation into distinct stages — and adding a human gate — this workflow produces tests that are both comprehensive and correct.
|
||||
|
|
@ -52,7 +52,8 @@ const cssExample = `<span class="text-ice-300">/* All nodes default to fast + ch
|
|||
</a>
|
||||
<a href="https://docs.fabro.sh" class="hidden md:inline-flex text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Docs</a>
|
||||
<a href="/blog" class="hidden md:inline-flex text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Blog</a>
|
||||
<!-- <a href="/roadmap" class="hidden md:inline-flex text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Roadmap</a> -->
|
||||
<a href="/showcase" class="hidden md:inline-flex text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Showcase</a>
|
||||
<a href="/roadmap" class="hidden md:inline-flex text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Roadmap</a>
|
||||
<a href="https://docs.fabro.sh/changelog" class="hidden md:inline-flex text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Changelog</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-5">
|
||||
|
|
@ -81,7 +82,8 @@ const cssExample = `<span class="text-ice-300">/* All nodes default to fast + ch
|
|||
<div class="px-6 py-5 space-y-4">
|
||||
<a href="https://docs.fabro.sh" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Docs</a>
|
||||
<a href="/blog" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Blog</a>
|
||||
<!-- <a href="/roadmap" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Roadmap</a> -->
|
||||
<a href="/showcase" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Showcase</a>
|
||||
<a href="/roadmap" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Roadmap</a>
|
||||
<a href="https://docs.fabro.sh/changelog" class="block text-sm font-medium text-ice-100 hover:text-ice-50 transition-colors">Changelog</a>
|
||||
<hr class="border-navy-800/60" />
|
||||
<div class="flex items-center gap-5">
|
||||
|
|
@ -684,6 +686,7 @@ const cssExample = `<span class="text-ice-300">/* All nodes default to fast + ch
|
|||
<li><a href="https://docs.fabro.sh/getting-started/quick-start" class="text-ice-300 hover:text-ice-50 transition-colors">Quick Start</a></li>
|
||||
<li><a href="https://docs.fabro.sh" class="text-ice-300 hover:text-ice-50 transition-colors">Docs</a></li>
|
||||
<li><a href="/blog" class="text-ice-300 hover:text-ice-50 transition-colors">Blog</a></li>
|
||||
<li><a href="/showcase" class="text-ice-300 hover:text-ice-50 transition-colors">Showcase</a></li>
|
||||
<li><a href="/roadmap" class="text-ice-300 hover:text-ice-50 transition-colors">Roadmap</a></li>
|
||||
<li><a href="https://docs.fabro.sh/changelog" class="text-ice-300 hover:text-ice-50 transition-colors">Changelog</a></li>
|
||||
<li><a href="https://github.com/fabro-sh/fabro/releases" class="text-ice-300 hover:text-ice-50 transition-colors">Releases</a></li>
|
||||
|
|
|
|||
207
apps/marketing/src/pages/showcase/[slug].astro
Normal file
207
apps/marketing/src/pages/showcase/[slug].astro
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import Nav from "../../components/Nav.astro";
|
||||
import Footer from "../../components/Footer.astro";
|
||||
import PageScripts from "../../components/PageScripts.astro";
|
||||
import { getCollection, render } from "astro:content";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const entries = await getCollection("showcase");
|
||||
return entries.map((entry) => ({
|
||||
params: { slug: entry.id },
|
||||
props: { entry },
|
||||
}));
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
const { Content } = await render(entry);
|
||||
|
||||
const langIcons: Record<string, { label: string; path: string }> = {
|
||||
python: {
|
||||
label: "Python",
|
||||
path: "M12 1.5c-5.1 0-4.8 2.2-4.8 2.2l.006 2.3H12.4v.7H4.8S1.5 6.3 1.5 11.5s2.9 5 2.9 5h1.7V14.1s-.1-2.9 2.8-2.9h4.9s2.8 0 2.8-2.7V3.8s.4-2.3-4.6-2.3zm-2.7 1.3a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zM12 22.5c5.1 0 4.8-2.2 4.8-2.2l-.006-2.3H11.6v-.7h7.6s3.3.4 3.3-4.8-2.9-5-2.9-5h-1.7v2.4s.1 2.9-2.8 2.9h-4.9s-2.8 0-2.8 2.7v4.7s-.4 2.3 4.6 2.3zm2.7-1.3a.9.9 0 1 1 0-1.8.9.9 0 0 1 0 1.8z",
|
||||
},
|
||||
rust: {
|
||||
label: "Rust",
|
||||
path: "M23.8 14.1l-1.2-.7a10.5 10.5 0 0 0 0-2.8l1.2-.7a.3.3 0 0 0 .1-.4 12 12 0 0 0-2.6-4.5.3.3 0 0 0-.4 0l-1.2.7a10.2 10.2 0 0 0-2.4-1.4V3a.3.3 0 0 0-.2-.3 12 12 0 0 0-5.2 0 .3.3 0 0 0-.2.3v1.3a10.2 10.2 0 0 0-2.4 1.4l-1.2-.7a.3.3 0 0 0-.4 0A12 12 0 0 0 5.1 9.5a.3.3 0 0 0 .1.4l1.2.7a10.5 10.5 0 0 0 0 2.8l-1.2.7a.3.3 0 0 0-.1.4 12 12 0 0 0 2.6 4.5.3.3 0 0 0 .4 0l1.2-.7c.7.6 1.5 1 2.4 1.4V21a.3.3 0 0 0 .2.3 12 12 0 0 0 5.2 0 .3.3 0 0 0 .2-.3v-1.3c.9-.4 1.7-.8 2.4-1.4l1.2.7a.3.3 0 0 0 .4 0 12 12 0 0 0 2.6-4.5.3.3 0 0 0-.1-.4zM12 16.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9z",
|
||||
},
|
||||
typescript: {
|
||||
label: "TypeScript",
|
||||
path: "M1.5 1.5h21v21h-21V1.5zm10.2 10.2v-1.8h6.6v1.8h-2.3v6.6h-2v-6.6h-2.3zm-4.1-1.8h2v4.5c0 .7.1 1.2.3 1.5.4.5 1 .8 1.8.8s1.4-.3 1.8-.8c.2-.3.3-.8.3-1.5V9.9h2v4.7c0 1-.2 1.8-.7 2.4-.7.9-1.8 1.3-3.4 1.3s-2.7-.4-3.4-1.3c-.5-.6-.7-1.4-.7-2.4V9.9z",
|
||||
},
|
||||
ruby: {
|
||||
label: "Ruby",
|
||||
path: "M20.6 18.3L21.7 5l-5.5 1.5-2.5-3.5-3 3.2L4.3 3 3 10.5l3.3 2-3 4.2 5.3.8 1.5 4 4.5-2.8 4 3.6 2-4z",
|
||||
},
|
||||
};
|
||||
---
|
||||
|
||||
<Layout title={`${entry.data.title} — Showcase — Fabro`} description={entry.data.description}>
|
||||
<Nav currentPage="showcase" />
|
||||
|
||||
<main>
|
||||
<article class="relative pt-24 sm:pt-28 pb-24">
|
||||
<div class="relative mx-auto max-w-3xl px-6">
|
||||
<!-- Back link -->
|
||||
<a href="/showcase" class="reveal inline-flex items-center gap-2 text-sm text-ice-300/60 hover:text-teal-300 transition-colors mb-10 group">
|
||||
<svg class="h-4 w-4 transition-transform group-hover:-translate-x-1" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
||||
<polyline points="15,18 9,12 15,6" />
|
||||
</svg>
|
||||
Showcase
|
||||
</a>
|
||||
|
||||
<!-- Hero header -->
|
||||
<header class="mb-10 border-b border-navy-800/40 pb-8">
|
||||
<h1 class="reveal reveal-d1 font-display text-3xl font-bold tracking-tight text-ice-50 sm:text-4xl leading-[1.15]">
|
||||
{entry.data.title}
|
||||
</h1>
|
||||
<p class="reveal reveal-d2 mt-3 text-lg text-ice-300">
|
||||
{entry.data.description}
|
||||
</p>
|
||||
|
||||
{/* Language icons + tags */}
|
||||
<div class="reveal reveal-d2 mt-5 flex flex-wrap items-center gap-3">
|
||||
{entry.data.languages.map((lang) => {
|
||||
const icon = langIcons[lang];
|
||||
return icon ? (
|
||||
<span class="flex items-center gap-1.5 text-sm text-ice-300/60" title={icon.label}>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d={icon.path} />
|
||||
</svg>
|
||||
{icon.label}
|
||||
</span>
|
||||
) : null;
|
||||
})}
|
||||
<span class="text-ice-300/20">|</span>
|
||||
{entry.data.tags.map((tag) => (
|
||||
<span class="rounded-full border border-navy-800 bg-navy-900/60 px-3 py-1 text-xs text-ice-300/70">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* GitHub button */}
|
||||
<div class="reveal reveal-d3 mt-6">
|
||||
<a
|
||||
href={entry.data.github}
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-navy-600 px-4 py-2 text-sm font-medium text-ice-300 transition-all hover:border-teal-700/60 hover:text-ice-50"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
|
||||
</svg>
|
||||
View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Workflow section -->
|
||||
<section class="reveal reveal-d3 mb-10">
|
||||
<h2 class="font-display text-lg font-semibold text-ice-50 mb-4">Workflow</h2>
|
||||
<div class="rounded-xl border border-navy-800/60 bg-navy-950/80 p-6 overflow-x-auto">
|
||||
<pre class="font-mono text-sm leading-relaxed"><code class="showcase-dot">{entry.data.workflow}</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Models & Skills -->
|
||||
<section class="reveal reveal-d4 mb-10">
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<h2 class="font-display text-lg font-semibold text-ice-50 mb-3">Models</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{entry.data.models.map((model) => (
|
||||
<span class="rounded-lg border border-teal-700/30 bg-teal-500/10 px-3 py-1.5 text-xs font-mono text-teal-300">
|
||||
{model}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-display text-lg font-semibold text-ice-50 mb-3">Skills</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{entry.data.skills.map((skill) => (
|
||||
<span class="rounded-lg border border-navy-800 bg-navy-900/60 px-3 py-1.5 text-xs font-mono text-ice-300/70">
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Prompt section with expand/collapse -->
|
||||
<section class="reveal reveal-d4 mb-10">
|
||||
<h2 class="font-display text-lg font-semibold text-ice-50 mb-4">Prompt</h2>
|
||||
<div class="rounded-xl border border-navy-800/60 bg-navy-900/30 p-6 relative">
|
||||
<div
|
||||
id="prompt-container"
|
||||
class="grid overflow-hidden transition-[grid-template-rows] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||
style="grid-template-rows: 0fr;"
|
||||
>
|
||||
<div class="min-h-0">
|
||||
<p class="font-mono text-sm leading-relaxed text-ice-300/80 whitespace-pre-wrap">{entry.data.prompt}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p id="prompt-preview" class="font-mono text-sm leading-relaxed text-ice-300/80 line-clamp-3">{entry.data.prompt}</p>
|
||||
<button
|
||||
id="prompt-toggle"
|
||||
class="group mt-4 flex items-center gap-2 text-sm text-ice-300/50 hover:text-teal-300 transition-colors cursor-pointer"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg
|
||||
id="prompt-chevron"
|
||||
class="h-4 w-4 transition-transform duration-300"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<polyline points="6,9 12,15 18,9" />
|
||||
</svg>
|
||||
<span id="prompt-toggle-label">Show full prompt</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- About (rendered markdown body) -->
|
||||
<section class="reveal reveal-d5">
|
||||
<h2 class="font-display text-lg font-semibold text-ice-50 mb-4">About</h2>
|
||||
<div class="prose">
|
||||
<Content />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<PageScripts />
|
||||
|
||||
<script>
|
||||
// Prompt expand/collapse toggle
|
||||
const toggle = document.getElementById("prompt-toggle");
|
||||
const container = document.getElementById("prompt-container");
|
||||
const preview = document.getElementById("prompt-preview");
|
||||
const chevron = document.getElementById("prompt-chevron");
|
||||
const label = document.getElementById("prompt-toggle-label");
|
||||
|
||||
if (toggle && container && preview && chevron && label) {
|
||||
toggle.addEventListener("click", () => {
|
||||
const isExpanded = toggle.getAttribute("aria-expanded") === "true";
|
||||
toggle.setAttribute("aria-expanded", String(!isExpanded));
|
||||
container.style.gridTemplateRows = isExpanded ? "0fr" : "1fr";
|
||||
preview.style.display = isExpanded ? "" : "none";
|
||||
chevron.style.transform = isExpanded ? "" : "rotate(180deg)";
|
||||
label.textContent = isExpanded ? "Show full prompt" : "Hide prompt";
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
/* DOT syntax coloring for workflow code block */
|
||||
.showcase-dot {
|
||||
color: var(--color-ice-300);
|
||||
}
|
||||
</style>
|
||||
118
apps/marketing/src/pages/showcase/index.astro
Normal file
118
apps/marketing/src/pages/showcase/index.astro
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import Nav from "../../components/Nav.astro";
|
||||
import Footer from "../../components/Footer.astro";
|
||||
import PageScripts from "../../components/PageScripts.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
const entries = (await getCollection("showcase")).sort(
|
||||
(a, b) => a.data.sortOrder - b.data.sortOrder
|
||||
);
|
||||
|
||||
const langIcons: Record<string, { label: string; path: string }> = {
|
||||
python: {
|
||||
label: "Python",
|
||||
path: "M12 1.5c-5.1 0-4.8 2.2-4.8 2.2l.006 2.3H12.4v.7H4.8S1.5 6.3 1.5 11.5s2.9 5 2.9 5h1.7V14.1s-.1-2.9 2.8-2.9h4.9s2.8 0 2.8-2.7V3.8s.4-2.3-4.6-2.3zm-2.7 1.3a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zM12 22.5c5.1 0 4.8-2.2 4.8-2.2l-.006-2.3H11.6v-.7h7.6s3.3.4 3.3-4.8-2.9-5-2.9-5h-1.7v2.4s.1 2.9-2.8 2.9h-4.9s-2.8 0-2.8 2.7v4.7s-.4 2.3 4.6 2.3zm2.7-1.3a.9.9 0 1 1 0-1.8.9.9 0 0 1 0 1.8z",
|
||||
},
|
||||
rust: {
|
||||
label: "Rust",
|
||||
path: "M23.8 14.1l-1.2-.7a10.5 10.5 0 0 0 0-2.8l1.2-.7a.3.3 0 0 0 .1-.4 12 12 0 0 0-2.6-4.5.3.3 0 0 0-.4 0l-1.2.7a10.2 10.2 0 0 0-2.4-1.4V3a.3.3 0 0 0-.2-.3 12 12 0 0 0-5.2 0 .3.3 0 0 0-.2.3v1.3a10.2 10.2 0 0 0-2.4 1.4l-1.2-.7a.3.3 0 0 0-.4 0A12 12 0 0 0 5.1 9.5a.3.3 0 0 0 .1.4l1.2.7a10.5 10.5 0 0 0 0 2.8l-1.2.7a.3.3 0 0 0-.1.4 12 12 0 0 0 2.6 4.5.3.3 0 0 0 .4 0l1.2-.7c.7.6 1.5 1 2.4 1.4V21a.3.3 0 0 0 .2.3 12 12 0 0 0 5.2 0 .3.3 0 0 0 .2-.3v-1.3c.9-.4 1.7-.8 2.4-1.4l1.2.7a.3.3 0 0 0 .4 0 12 12 0 0 0 2.6-4.5.3.3 0 0 0-.1-.4zM12 16.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9z",
|
||||
},
|
||||
typescript: {
|
||||
label: "TypeScript",
|
||||
path: "M1.5 1.5h21v21h-21V1.5zm10.2 10.2v-1.8h6.6v1.8h-2.3v6.6h-2v-6.6h-2.3zm-4.1-1.8h2v4.5c0 .7.1 1.2.3 1.5.4.5 1 .8 1.8.8s1.4-.3 1.8-.8c.2-.3.3-.8.3-1.5V9.9h2v4.7c0 1-.2 1.8-.7 2.4-.7.9-1.8 1.3-3.4 1.3s-2.7-.4-3.4-1.3c-.5-.6-.7-1.4-.7-2.4V9.9z",
|
||||
},
|
||||
ruby: {
|
||||
label: "Ruby",
|
||||
path: "M20.6 18.3L21.7 5l-5.5 1.5-2.5-3.5-3 3.2L4.3 3 3 10.5l3.3 2-3 4.2 5.3.8 1.5 4 4.5-2.8 4 3.6 2-4z",
|
||||
},
|
||||
};
|
||||
|
||||
const gradients = [
|
||||
"from-teal-500/20 to-cyan-500/10",
|
||||
"from-violet-500/20 to-fuchsia-500/10",
|
||||
"from-amber-500/20 to-orange-500/10",
|
||||
"from-emerald-500/20 to-teal-500/10",
|
||||
"from-rose-500/20 to-pink-500/10",
|
||||
];
|
||||
---
|
||||
|
||||
<Layout title="Showcase — Fabro" description="Projects and recipes built with Fabro workflows.">
|
||||
<Nav currentPage="showcase" />
|
||||
|
||||
<main>
|
||||
<!-- Header -->
|
||||
<section class="relative pt-24 sm:pt-28 pb-10">
|
||||
<div class="absolute inset-0 noise-overlay"></div>
|
||||
<div class="pointer-events-none absolute top-8 -left-40 h-[400px] w-[400px] rounded-full bg-teal-500/8 blur-[120px]"></div>
|
||||
|
||||
<div class="relative mx-auto max-w-4xl px-6">
|
||||
<h1 class="reveal font-display text-4xl font-bold tracking-tight text-ice-50 sm:text-5xl leading-[1.1]">
|
||||
Showcase
|
||||
</h1>
|
||||
<p class="reveal reveal-d1 mt-3 text-lg text-ice-300">
|
||||
Reproducible workflow recipes built with Fabro. Clone, customize, and run.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Card grid -->
|
||||
<section class="relative pb-24">
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
{entries.map((entry, i) => (
|
||||
<a
|
||||
href={`/showcase/${entry.id}`}
|
||||
class={`reveal reveal-d${Math.min(i + 2, 5)} glow-card group block rounded-xl border border-navy-800/60 bg-navy-900/30 overflow-hidden transition-all duration-400 hover:border-teal-700/40 hover:shadow-[0_0_40px_-8px_rgba(53,127,158,0.12)]`}
|
||||
>
|
||||
{/* Gradient thumbnail area */}
|
||||
<div class={`relative h-32 bg-gradient-to-br ${gradients[i % gradients.length]} flex items-center justify-center`}>
|
||||
<span class="font-display text-5xl font-bold text-white/20 select-none">
|
||||
{entry.data.title.charAt(0)}
|
||||
</span>
|
||||
<div class="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-teal-500/20 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Card body */}
|
||||
<div class="p-6">
|
||||
<h2 class="font-display text-lg font-semibold text-ice-50 group-hover:text-teal-300 transition-colors">
|
||||
{entry.data.title}
|
||||
</h2>
|
||||
<p class="mt-2 text-sm leading-relaxed text-ice-300/70 line-clamp-2">
|
||||
{entry.data.description}
|
||||
</p>
|
||||
|
||||
{/* Language icons */}
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
{entry.data.languages.map((lang) => {
|
||||
const icon = langIcons[lang];
|
||||
return icon ? (
|
||||
<span class="flex items-center gap-1.5 text-xs text-ice-300/50" title={icon.label}>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d={icon.path} />
|
||||
</svg>
|
||||
{icon.label}
|
||||
</span>
|
||||
) : null;
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div class="mt-3 flex flex-wrap gap-1.5">
|
||||
{entry.data.tags.map((tag) => (
|
||||
<span class="rounded-full border border-navy-800 bg-navy-900/60 px-3 py-1 text-xs text-ice-300/70">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<PageScripts />
|
||||
</Layout>
|
||||
Loading…
Add table
Reference in a new issue