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
4 changes: 2 additions & 2 deletions src/components/SeoHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ interface SeoHeadProps {
}

export default function SeoHead({
title = 'Windmill | Build, orchestrate, and monitor internal software at scale',
description = 'Open-source workflow engine to build workflows, data pipelines and internal tools at scale. Self-hostable, for developers and AI, with enterprise security.',
title = 'Windmill | Code-first orchestration platform for internal software',
description = 'Built for teams to create and collaborate on internal software. Loved by engineers for full code flexibility and control over their infrastructure. Open-source and self-hostable.',
url = 'https://www.windmill.dev/',
image = 'https://www.windmill.dev/img/og_preview.png'
}: SeoHeadProps) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/products/LazyVideo.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useRef, useEffect } from 'react';

export default function LazyVideo({ src, className }) {
export default function LazyVideo({ src, className, loop = true }) {
const ref = useRef(null);

useEffect(() => {
Expand All @@ -26,7 +26,7 @@ export default function LazyVideo({ src, className }) {
<video
ref={ref}
className={className}
loop
loop={loop}
muted
playsInline
src={src}
Expand Down
115 changes: 115 additions & 0 deletions src/landing/CodeFirstSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { useState } from 'react';
import Link from '@docusaurus/Link';

const snippets = [
{
label: 'Workspace repo',
caption: 'Your workspace as a file tree',
code: `f/
├── etl/
│ ├── stripe_to_postgres.flow.yaml
│ └── stripe_to_postgres/
│ ├── fetch_invoices.ts
│ └── transform.py
├── devops/
│ └── rotate_iam_keys.ts
├── apps/
│ └── billing_dashboard.app/
├── resources/
│ ├── postgres_prod.resource.yaml
│ └── slack_webhook.resource.yaml
└── schedules/
└── stripe_sync.schedule.yaml`,
},
{
label: 'Worker groups',
caption: 'Worker groups and autoscaling',
code: `worker_configs:
default:
worker_tags:
- deno
- python3
- bun
- go
- bash

gpu:
worker_tags: [gpu-task]
dedicated_worker: "f/gpu_scripts/inference"
autoscaling:
enabled: true
min_workers: 0
max_workers: 4`,
},
{
label: 'Global settings',
caption: 'Instance-level configuration as code',
code: `global_settings:
base_url: "https://windmill.example.com"
retention_period_secs: 2592000
job_default_timeout: 900
expose_metrics: true

oauths:
google:
id: "google-client-id"
secret: "google-client-secret"

otel:
otel_exporter_otlp_endpoint: "http://otel:4317"
tracing_enabled: true
metrics_enabled: true`,
},
];

export default function CodeFirstSection() {
const [active, setActive] = useState(0);
const snippet = snippets[active];

return (
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-16">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
<div>
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white mb-4">
From scripts to infrastructure, everything lives in code
</h2>
<p className="text-lg text-gray-600 dark:text-gray-300 mb-6">
Your entire workspace is <Link to="/platform/deployment-versioning" className="text-blue-600 dark:text-blue-400 hover:underline">versioned in Git</Link>. Teams can build from the UI or locally, while engineers manage infrastructure, deployments and environments with full code flexibility.
</p>
<Link
to="/docs/advanced/git_sync"
className="text-sm text-blue-600 dark:text-blue-400 font-medium hover:underline !no-underline"
>
Learn about infrastructure as code →
</Link>
</div>
<div>
<div className="flex gap-1 mb-3">
{snippets.map((s, i) => (
<button
key={s.label}
onClick={() => setActive(i)}
className={`px-4 py-1.5 text-xs font-medium rounded-lg transition-colors cursor-pointer ${
i === active
? 'bg-[#0d1117] text-white'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
}`}
style={{ border: 'none' }}
>
{s.label}
</button>
))}
</div>
<div className="rounded-2xl overflow-hidden bg-[#0d1117] p-6" style={{ height: '450px' }}>
<pre className="text-sm leading-relaxed text-gray-300 m-0 bg-transparent overflow-x-auto">
<code>{snippet.code}</code>
</pre>
</div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400 mt-3 text-center">
{snippet.caption}
</p>
</div>
</div>
</div>
);
}
187 changes: 166 additions & 21 deletions src/landing/CorePrinciple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,48 +35,193 @@ interface FeatureCardProps {
actionLink: string;
actionUrl?: string;
lottieData?: unknown;
illustration?: React.ReactNode;
}

function FeatureCard({ title, description, actionLink, actionUrl, lottieData }: FeatureCardProps) {
function FeatureCard({ title, description, actionLink, actionUrl, lottieData, illustration }: FeatureCardProps) {
return (
<div className="flex flex-col h-full rounded-lg bg-gray-50 dark:bg-gray-800/50 backdrop-blur-sm p-6 shadow-lg border border-gray-200 dark:border-gray-700/50 group">
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-3">{title}</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4 flex-grow text-base leading-relaxed">
<div className="rounded-xl bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-5 flex flex-col h-full group">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">{title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 flex-grow">
{description}
</p>
{illustration ? (
<div className="rounded-lg overflow-hidden mb-4 aspect-video">
{illustration}
</div>
) : lottieData ? (
<div className="rounded-lg bg-gray-100 dark:bg-gray-800 p-4 min-h-[180px] flex items-center justify-center overflow-hidden mb-4">
<div className="w-full h-full flex items-center justify-center rounded-md">
<BrowserOnly fallback={<div className="w-full h-full" />}>
{() => <StaticLottie animationData={lottieData} />}
</BrowserOnly>
</div>
</div>
) : null}
<a
href={actionUrl || '#'}
className="text-sm text-blue-600 dark:text-blue-400 flex flex-row items-center gap-2 group-hover:ml-2 transition-all mb-4 hover:text-blue-700 dark:hover:text-blue-300"
className="text-sm text-blue-600 dark:text-blue-400 flex flex-row items-center gap-2 group-hover:ml-2 transition-all hover:text-blue-700 dark:hover:text-blue-300"
target={actionUrl && actionUrl.startsWith('http') ? '_blank' : undefined}
rel={actionUrl && actionUrl.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{actionLink}
<ArrowRight size={24} />
</a>
{lottieData && (
<div className="mt-auto bg-gray-100 dark:bg-gray-700/80 rounded-md p-4 min-h-[180px] flex items-center justify-center overflow-hidden">
<div className="w-full h-full flex items-center justify-center rounded-md">
<BrowserOnly fallback={<div className="w-full h-full" />}>
{() => <StaticLottie animationData={lottieData} />}
</BrowserOnly>
</div>
</div>
)}
</div>
);
}

function NoLockInIllustration() {
return (
<svg viewBox="0 0 400 225" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<defs>
<radialGradient id="noLockHalo" cx="50%" cy="55%" r="55%">
<stop offset="0%" stopColor="#3B82F6" stopOpacity="0.55" />
<stop offset="55%" stopColor="#1E40AF" stopOpacity="0.2" />
<stop offset="100%" stopColor="#0B1220" stopOpacity="0" />
</radialGradient>
<linearGradient id="noLockBody" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#DBEAFE" />
<stop offset="100%" stopColor="#60A5FA" />
</linearGradient>
</defs>
<rect width="400" height="225" fill="#0B1220" />
<circle cx="200" cy="112" r="180" fill="url(#noLockHalo)" />

<g transform="translate(120 65)">
<rect width="160" height="95" rx="8" fill="url(#noLockBody)" />
<rect x="8" y="8" width="144" height="79" rx="4" fill="#0B1220" opacity="0.7" />
<text
x="80"
y="56"
textAnchor="middle"
fontFamily="ui-monospace, SFMono-Regular, monospace"
fontSize="22"
fontWeight="700"
fill="#93C5FD"
>
{'</>'}
</text>
</g>
<rect x="100" y="160" width="200" height="6" rx="3" fill="url(#noLockBody)" />
<rect x="180" y="160" width="40" height="8" rx="2" fill="#1E40AF" opacity="0.6" />
</svg>
);
}

function SecurityIllustration() {
const logos = [
'/third_party_logos/openid.webp',
'/third_party_logos/vault.webp',
'/third_party_logos/aws_kms.webp'
];
const tileSize = 100;
const gap = 20;
const totalW = logos.length * tileSize + (logos.length - 1) * gap;
const startX = (400 - totalW) / 2;
const startY = (225 - tileSize) / 2;
return (
<svg viewBox="0 0 400 225" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<defs>
<radialGradient id="securityHalo" cx="50%" cy="55%" r="55%">
<stop offset="0%" stopColor="#3B82F6" stopOpacity="0.55" />
<stop offset="55%" stopColor="#1E40AF" stopOpacity="0.2" />
<stop offset="100%" stopColor="#0B1220" stopOpacity="0" />
</radialGradient>
</defs>
<rect width="400" height="225" fill="#0B1220" />
<circle cx="200" cy="112" r="180" fill="url(#securityHalo)" />
{logos.map((href, i) => {
const x = startX + i * (tileSize + gap);
return (
<g key={i} transform={`translate(${x} ${startY})`}>
<rect width={tileSize} height={tileSize} rx={tileSize * 0.2} fill="#F8FAFC" stroke="#60A5FA" strokeOpacity="0.35" strokeWidth="1" />
<image
href={href}
x={tileSize * 0.15}
y={tileSize * 0.15}
width={tileSize * 0.7}
height={tileSize * 0.7}
preserveAspectRatio="xMidYMid meet"
/>
</g>
);
})}
</svg>
);
}

function IntegrationsIllustration() {
const logos = [
'/third_party_logos/github.svg',
'/third_party_logos/slack.svg',
'/third_party_logos/notion.svg',
'/third_party_logos/postgres.svg',
'/third_party_logos/openai.svg',
'/third_party_logos/stripe.svg',
'/third_party_logos/snowflake.svg',
'/third_party_logos/linear.svg',
'/third_party_logos/hubspot.svg',
'/third_party_logos/discord.svg',
'/third_party_logos/mongodb.svg',
'/third_party_logos/redis.svg',
'/third_party_logos/anthropic.svg',
'/third_party_logos/aws.svg',
'/third_party_logos/gsheets.svg'
];
const cols = 5;
const rows = 3;
const tileSize = 38;
const gap = 12;
const totalW = cols * tileSize + (cols - 1) * gap;
const totalH = rows * tileSize + (rows - 1) * gap;
const startX = (400 - totalW) / 2;
const startY = (225 - totalH) / 2;
return (
<svg viewBox="0 0 400 225" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<defs>
<radialGradient id="integrationsHalo" cx="50%" cy="55%" r="55%">
<stop offset="0%" stopColor="#3B82F6" stopOpacity="0.55" />
<stop offset="55%" stopColor="#1E40AF" stopOpacity="0.2" />
<stop offset="100%" stopColor="#0B1220" stopOpacity="0" />
</radialGradient>
</defs>
<rect width="400" height="225" fill="#0B1220" />
<circle cx="200" cy="112" r="180" fill="url(#integrationsHalo)" />
{logos.map((href, i) => {
const col = i % cols;
const row = Math.floor(i / cols);
const x = startX + col * (tileSize + gap);
const y = startY + row * (tileSize + gap);
return (
<g key={i} transform={`translate(${x} ${y})`}>
<rect width={tileSize} height={tileSize} rx={tileSize * 0.2} fill="#F8FAFC" stroke="#60A5FA" strokeOpacity="0.35" strokeWidth="1" />
<image
href={href}
x={tileSize * 0.18}
y={tileSize * 0.18}
width={tileSize * 0.64}
height={tileSize * 0.64}
preserveAspectRatio="xMidYMid meet"
/>
</g>
);
})}
</svg>
);
}

export default function CorePrinciple() {
return (
<LandingSection bgClass="py-16">
<div className="w-full">
<div className="mb-12 text-left">
<h1 className="tracking-tight leading-tight text-left font-bold text-transparent bg-clip-text bg-gradient-to-br from-blue-500 to-blue-700 dark:from-blue-400 dark:to-blue-600">
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white mb-4">
Our core principles
</h1>
<span className="text-lg text-gray-700 max-w-3xl dark:text-gray-200">
</h2>
<p className="text-lg text-gray-600 dark:text-gray-300">
The foundational beliefs that guide how we build Windmill.
</span>
</p>
</div>

{/* 3 cards in a row */}
Expand All @@ -86,21 +231,21 @@ export default function CorePrinciple() {
description="Open source and self-hostable. Your code, your data, your infrastructure. Normal code in mainstream languages, no custom or proprietary SDKs. Run it locally, generate it with LLMs, port it anytime."
actionLink="View on GitHub"
actionUrl="https://github.com/windmill-labs/"
lottieData={polyGlott}
illustration={<NoLockInIllustration />}
/>
<FeatureCard
title="Do not reinvent the wheel"
description="Focus on what matters: your business logic. Every possible integration and trigger with external systems is already built-in with enterprise-grade reliability. PostgreSQL, Snowflake, Kafka, and 100+ more."
actionLink="Explore integrations"
actionUrl="https://www.windmill.dev/docs/integrations/integrations_on_windmill"
lottieData={thirdparty}
illustration={<IntegrationsIllustration />}
/>
<FeatureCard
title="Security and reliability at scale"
description="Granular RBAC, SSO, Secret Management, and comprehensive Audit Logs. Battle-tested reliability at scale in regulated industries with air-gapped support."
actionLink="Explore enterprise features"
actionUrl="https://www.windmill.dev/docs/misc/enterprise_onboarding"
lottieData={secrets}
illustration={<SecurityIllustration />}
/>
</div>
</div>
Expand Down
Loading
Loading