Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default defineConfig({
starlight({
title: 'OpenFlowKit Docs',
description: 'Documentation for OpenFlowKit — the local-first, AI-powered diagramming tool.',
favicon: '/favicon.svg',
logo: {
src: './src/assets/Logo_openflowkit.svg',
alt: 'OpenFlowKit',
Expand Down
4 changes: 4 additions & 0 deletions docs-site/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs-site/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://docs.openflowkit.com/sitemap-index.xml
35 changes: 35 additions & 0 deletions docs-site/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,41 @@ const referenceLinks = [
</div>
</section>

<section class="docs-home-section">
<div class="docs-home-section-copy">
<p class="docs-home-section-label">What OpenFlowKit Is</p>
<h2>A local-first diagramming tool for editable technical workflows</h2>
<p>
OpenFlowKit is built for architecture diagrams, flowcharts, system design, structured
imports, and AI-assisted diagram drafting. The docs are written to help you choose the
right starting point quickly instead of reading the full navigation tree end to end.
</p>
</div>
<div class="docs-home-grid">
<article class="docs-home-card">
<h3>Use it when diagrams need to stay editable</h3>
<p>
OpenFlowKit is a better fit when you want to keep iterating in the canvas, work from
code-backed definitions, or export technical diagrams without flattening the workflow.
</p>
</article>
<article class="docs-home-card">
<h3>Use AI as an assistant, not the whole workflow</h3>
<p>
The AI docs explain how prompting, refinement, and settings work so teams can use AI
intentionally instead of treating it like a one-shot image generator.
</p>
</article>
<article class="docs-home-card">
<h3>Start from the job you need done</h3>
<p>
The fastest route is usually by workflow: architecture mapping, code-backed diagrams,
imports, exports, or editor fundamentals.
</p>
</article>
</div>
</section>

<section class="docs-home-section">
<div class="docs-home-section-copy">
<p class="docs-home-section-label">Start Here</p>
Expand Down
1 change: 1 addition & 0 deletions public/robots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ User-agent: *
Allow: /

Sitemap: https://openflowkit.com/sitemap.xml
Sitemap: https://docs.openflowkit.com/sitemap-index.xml
10 changes: 0 additions & 10 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,4 @@
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://app.openflowkit.com/</loc>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://docs.openflowkit.com/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
63 changes: 57 additions & 6 deletions scripts/generate-sitemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,62 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const OUTPUT_FILE = path.resolve(__dirname, '../public/sitemap.xml');
const PAGES_DIR = path.resolve(__dirname, '../web/src/pages');
const SITE_URL = 'https://openflowkit.com';

const ROUTES = [
{ loc: 'https://openflowkit.com/', changefreq: 'weekly', priority: '1.0' },
{ loc: 'https://app.openflowkit.com/', changefreq: 'daily', priority: '0.9' },
{ loc: 'https://docs.openflowkit.com/', changefreq: 'weekly', priority: '0.8' },
];
const ROUTE_SUFFIXES = new Set(['.astro', '.md', '.mdx']);

function toRoute(relativePath) {
const normalizedPath = relativePath.replace(/\\/g, '/');
const withoutExtension = normalizedPath.replace(/\.(astro|md|mdx)$/u, '');

if (withoutExtension === 'index') {
return '/';
}

if (withoutExtension.endsWith('/index')) {
return `/${withoutExtension.slice(0, -'/index'.length)}/`;
}

return `/${withoutExtension}/`;
}

function collectRoutes(directory, relativeDirectory = '') {
const entries = fs.readdirSync(directory, { withFileTypes: true });

return entries.flatMap((entry) => {
const entryRelativePath = path.posix.join(relativeDirectory, entry.name);
const entryAbsolutePath = path.join(directory, entry.name);

if (entry.isDirectory()) {
return collectRoutes(entryAbsolutePath, entryRelativePath);
}

const extension = path.extname(entry.name);
const isStaticPage =
ROUTE_SUFFIXES.has(extension) &&
!entryRelativePath.startsWith('api/') &&
!entry.name.startsWith('[');

if (!isStaticPage) {
return [];
}

return [toRoute(entryRelativePath)];
});
}

function buildRoutes() {
const discoveredRoutes = collectRoutes(PAGES_DIR);

return Array.from(new Set(discoveredRoutes))
.sort((left, right) => left.localeCompare(right))
.map((route) => ({
loc: new URL(route, SITE_URL).toString(),
changefreq: route === '/' ? 'weekly' : 'monthly',
priority: route === '/' ? '1.0' : '0.7',
}));
}

function renderUrl({ loc, changefreq, priority }) {
return [
Expand All @@ -24,10 +74,11 @@ function renderUrl({ loc, changefreq, priority }) {
}

function generateSitemap() {
const routes = buildRoutes();
const sitemap = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...ROUTES.map(renderUrl),
...routes.map(renderUrl),
'</urlset>',
'',
].join('\n');
Expand Down
55 changes: 40 additions & 15 deletions web/src/components/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,45 @@ import '../styles/global.css';
interface Props {
title: string;
description: string;
canonicalPath?: string;
schema?: Record<string, unknown> | Array<Record<string, unknown>>;
}

const { title, description } = Astro.props;
const { title, description, canonicalPath = '/', schema } = Astro.props;
const siteUrl = 'https://openflowkit.com';
const canonicalUrl = new URL(canonicalPath, siteUrl).toString();
const ogImage = `${siteUrl}/og-image.png`;
const baseSchemas = [
{
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'OpenFlowKit',
description,
url: canonicalUrl,
applicationCategory: 'DesignApplication',
operatingSystem: 'Web',
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
license: 'https://opensource.org/licenses/MIT',
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'OpenFlowKit',
url: siteUrl,
logo: `${siteUrl}/Logo_openflowkit.svg`,
sameAs: ['https://github.com/Vrun-design/openflowkit'],
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'OpenFlowKit',
url: siteUrl,
description:
'Open-source, local-first AI diagramming for architecture diagrams, flowcharts, system design, and export workflows.',
},
];
const extraSchemas = schema ? (Array.isArray(schema) ? schema : [schema]) : [];
const schemas = [...baseSchemas, ...extraSchemas];
---

<!doctype html>
Expand All @@ -18,30 +52,21 @@ const ogImage = `${siteUrl}/og-image.png`;
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={siteUrl} />
<link rel="canonical" href={canonicalUrl} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={siteUrl} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:site_name" content="OpenFlowKit" />
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "OpenFlowKit",
"description": description,
"url": siteUrl,
"applicationCategory": "DesignApplication",
"operatingSystem": "Web",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
"license": "https://opensource.org/licenses/MIT"
})} />
<link rel="icon" type="image/svg+xml" href="/Logo_openflowkit.svg" />
<script type="application/ld+json" set:html={JSON.stringify(schemas)} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
Expand Down
96 changes: 96 additions & 0 deletions web/src/components/landing/AnswerEngineSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';

const proofPoints = [
{
title: 'Local-first by default',
description:
'Your diagrams, workspace state, and AI-related browser data stay on-device unless you choose to remove them.',
},
{
title: 'Useful for real architecture work',
description:
'OpenFlowKit supports freeform diagramming, architecture mapping, AI-assisted drafting, structured imports, and handoff exports.',
},
{
title: 'Built for editable output',
description:
'You can keep iterating in the canvas, export to Mermaid, or hand work off through SVG, PNG, and Figma-friendly paths.',
},
];

const faqs = [
{
question: 'What is OpenFlowKit?',
answer:
'OpenFlowKit is an open-source, local-first diagramming tool for architecture diagrams, flowcharts, and AI-assisted visual workflows.',
},
{
question: 'Who is OpenFlowKit for?',
answer:
'It is designed for engineers, architects, technical founders, and product teams who need editable diagrams instead of static AI image output.',
},
{
question: 'Does OpenFlowKit require an account?',
answer:
'No. You can open the app and start diagramming without creating an account.',
},
{
question: 'How does AI work in OpenFlowKit?',
answer:
'AI is optional. You bring your own API key in the settings modal, then use AI to draft, refine, or expand diagrams inside the editor.',
},
];

export function AnswerEngineSection(): React.ReactElement {
return (
<section className="py-24 md:py-28 bg-white border-y border-brand-border/60">
<div className="container mx-auto px-6">
<div className="max-w-6xl mx-auto grid gap-12 lg:grid-cols-[1.1fr_0.9fr]">
<div>
<p className="text-xs font-mono uppercase tracking-[0.28em] text-brand-primary mb-4">
Why OpenFlowKit
</p>
<h2 className="text-4xl md:text-6xl font-bold tracking-tight text-brand-dark leading-[0.95] mb-6">
A local-first AI diagramming tool built for editable technical work.
</h2>
<p className="text-lg md:text-xl text-brand-secondary leading-relaxed max-w-2xl">
OpenFlowKit helps teams turn rough system ideas into diagrams they can keep
editing, exporting, and sharing. It is designed for architecture flows, system
design, process mapping, and other diagram-heavy workflows where static output is
not enough.
</p>

<div className="mt-10 grid gap-5 sm:grid-cols-3">
{proofPoints.map((item) => (
<article
key={item.title}
className="rounded-3xl border border-brand-border bg-brand-canvas p-6 shadow-[0_14px_40px_rgba(15,23,42,0.05)]"
>
<h3 className="text-lg font-semibold text-brand-dark mb-2">{item.title}</h3>
<p className="text-sm leading-6 text-brand-secondary">{item.description}</p>
</article>
))}
</div>
</div>

<div className="rounded-[32px] border border-brand-border bg-[#0f172a] p-8 text-white shadow-2xl">
<p className="text-xs font-mono uppercase tracking-[0.28em] text-white/60 mb-4">
Frequently Asked
</p>
<div className="space-y-5">
{faqs.map((item) => (
<article
key={item.question}
className="rounded-2xl border border-white/10 bg-white/5 p-5 backdrop-blur"
>
<h3 className="text-base font-semibold mb-2">{item.question}</h3>
<p className="text-sm leading-6 text-white/75">{item.answer}</p>
</article>
))}
</div>
</div>
</div>
</div>
</section>
);
}
10 changes: 5 additions & 5 deletions web/src/components/landing/ArchitectureExploded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ export function ArchitectureExploded(): React.ReactElement {

{/* Layer 4 Legend */}
<div className="flex items-start gap-4 lg:gap-3 group">
<div className="w-10 h-10 lg:w-8 lg:h-8 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center shrink-0 text-purple-400 group-hover:scale-110 group-hover:bg-purple-500/20 transition-all duration-300 shadow-[0_0_15px_rgba(168,85,247,0)] group-hover:shadow-[0_0_15px_rgba(168,85,247,0.3)]">
<WandSparkles className="w-5 h-5 lg:w-4 lg:h-4" />
<div className="w-8 h-8 lg:w-8 lg:h-8 xl:w-10 xl:h-10 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center shrink-0 text-purple-400 group-hover:scale-110 group-hover:bg-purple-500/20 transition-all duration-300 shadow-[0_0_15px_rgba(168,85,247,0)] group-hover:shadow-[0_0_15px_rgba(168,85,247,0.3)]">
<WandSparkles className="w-4 h-4 xl:w-5 xl:h-5" />
</div>
<div>
<h4 className="text-white text-sm lg:text-xs font-bold mb-1 group-hover:text-purple-400 transition-colors">4. LLM Bridge</h4>
<p className="text-sm lg:text-[10px] text-white/40 leading-relaxed">
Your private BYOK pipeline parsing natural language into strictly-typed TypeScript commands.
<h4 className="text-white text-xs lg:text-sm xl:text-base font-bold mb-0.5 group-hover:text-purple-400 transition-colors">4. LLM Bridge</h4>
<p className="text-xs text-white/40 leading-snug">
Private BYOK prompts translated into typed editor commands.
</p>
</div>
</div>
Expand Down
Loading
Loading