+
{part.title}
-
+
{part.body}
@@ -72,7 +76,7 @@ function ToolLine({
);
return (
);
@@ -81,14 +85,12 @@ function ToolLine({
function ToolPicture({ clock }: ClockProps) {
const phase = useTransform(clock, (time) => (time % 4.8) / 4.8);
const backgroundColor = useTransform(
- phase,
- [0, 0.1, 0.22, 0.38, 0.5, 1],
- ["#fff", "#fff", "#ededed", "#ededed", "#fff", "#fff"],
+ useTransform(phase, [0, 0.1, 0.22, 0.38, 0.5, 1], [0, 0, 1, 1, 0, 0]),
+ mixTones("--color-tone-100", "--color-tone-93"),
);
const borderColor = useTransform(
- phase,
- [0, 0.1, 0.22, 0.4, 0.55, 1],
- ["#ddd", "#ddd", "#888", "#888", "#ddd", "#ddd"],
+ useTransform(phase, [0, 0.1, 0.22, 0.4, 0.55, 1], [0, 0, 1, 1, 0, 0]),
+ mixTones("--color-tone-87", "--color-tone-53"),
);
const arrowX = useTransform(phase, [0, 0.2, 0.35, 0.5, 1], [0, 0, 3, 0, 0]);
const doneOpacity = useTransform(phase, [0, 0.52, 0.62, 0.84, 1], [0, 0, 1, 1, 0]);
@@ -96,22 +98,22 @@ function ToolPicture({ clock }: ClockProps) {
query_logs()
✓ Done
-
+
→
-
+
{[100, 70, 100].map((width, index) => (
))}
@@ -147,15 +149,15 @@ function SkillDocument({
const zIndex = useTransform(depth, (value) => (value > 2.65 ? 4 : 3 - Math.round(value)));
return (
-
+
SKILL.md
{index + 1} / 3
# {skill.title}
-
+
1. {skill.steps[0]}
2. {skill.steps[1]}
@@ -184,7 +186,7 @@ function SkillsPicture({ clock }: ClockProps) {
aria-label="Shuffle skills"
onHoverStart={next}
onClick={next}
- className="flex h-full w-full cursor-pointer items-center justify-center outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[#777]"
+ className="flex h-full w-full cursor-pointer items-center justify-center outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-tone-47"
>
{skills.map((skill, index) => (
@@ -212,7 +214,7 @@ function ChartBar({
);
return (
);
@@ -227,18 +229,18 @@ function UiPicture({ clock }: ClockProps) {
return (
-
+
{[0, 1, 2].map((dot) => (
-
+
))}
-
+
app.demo-org.executor.website
-
Active users
+
Active users
{text}
@@ -276,7 +278,7 @@ function StorageRow({
);
return (
{row.key}
@@ -290,9 +292,9 @@ function StoragePicture({ clock }: ClockProps) {
return (
-
+
key
value
@@ -301,7 +303,7 @@ function StoragePicture({ clock }: ClockProps) {
))}
-
@@ -322,19 +324,12 @@ function TriggerSource({
readonly label: string;
}) {
const local = useTriggerPhase(phase, index);
- const backgroundColor = useTransform(
- local,
- [0, 0.08, 0.18, 0.35, 0.52, 2],
- ["#fff", "#fff", "#ededed", "#ededed", "#fff", "#fff"],
- );
- const borderColor = useTransform(
- local,
- [0, 0.08, 0.18, 0.35, 0.52, 2],
- ["#ddd", "#ddd", "#999", "#999", "#ddd", "#ddd"],
- );
+ const active = useTransform(local, [0, 0.08, 0.18, 0.35, 0.52, 2], [0, 0, 1, 1, 0, 0]);
+ const backgroundColor = useTransform(active, mixTones("--color-tone-100", "--color-tone-93"));
+ const borderColor = useTransform(active, mixTones("--color-tone-87", "--color-tone-60"));
return (
{label}
@@ -367,7 +362,7 @@ function TriggerPulse({
index === 0 ? triggerPoint(value).y : 70 - triggerPoint(value).y,
);
const opacity = useTransform(local, [0, 0.12, 0.17, 0.54, 0.62, 2], [0, 0, 1, 1, 0, 0]);
- return ;
+ return ;
}
function TriggersPicture({ clock }: ClockProps) {
@@ -382,24 +377,27 @@ function TriggersPicture({ clock }: ClockProps) {
-
+
ƒ
✓
-
+
Your app
diff --git a/apps/marketing/src/components/app-starting-points.astro b/apps/marketing/src/components/app-starting-points.astro
index fd739da745..a35425e204 100644
--- a/apps/marketing/src/components/app-starting-points.astro
+++ b/apps/marketing/src/components/app-starting-points.astro
@@ -3,25 +3,25 @@ import posthog from "../assets/logos/posthog.svg?url";
import github from "../assets/logos/github.svg?url";
---
-
-
+
+
-
An existing tool
-
Bring an MCP or API you already use.
+
An existing tool
+
Bring an MCP or API you already use.
-
#
-
A skill
-
Teach your agents how you like things done.
+
#
+
A skill
+
Teach your agents how you like things done.
-
{`{ }`}
-
Something new
-
Describe it. Your agent writes the code.
+
{`{ }`}
+
Something new
+
Describe it. Your agent writes the code.
diff --git a/apps/marketing/src/components/app-structure.astro b/apps/marketing/src/components/app-structure.astro
index 040fd7a818..2805f71c78 100644
--- a/apps/marketing/src/components/app-structure.astro
+++ b/apps/marketing/src/components/app-structure.astro
@@ -11,23 +11,23 @@ const folders = [
---
-
+
-
-
-
-
daily-brief
+
+
{rootFiles.map((file) => )}
{folders.map((folder) => (
-
+
-
+
{folder.name}
- {folder.files.map((file) => )}
+ {folder.files.map((file) => )}
))}
{appFiles.filter((file) => file.path === "package.json").map((file) => )}
@@ -35,40 +35,40 @@ const folders = [
-
-
Open file
-
+
+ Open file
+
{appFiles.map((file) => {file.path} )}
{appFiles.map((file, index) => (
-
-
-
{file.path}
-
{file.source.split("\n").length} lines
+
+
+
{file.path}
+
{file.source.split("\n").length} lines
-
-
+
One app. Ordinary files.
Choose a file to look inside.
diff --git a/apps/marketing/src/components/custom-tool.astro b/apps/marketing/src/components/custom-tool.astro
index 11607cafd8..a33a90cc68 100644
--- a/apps/marketing/src/components/custom-tool.astro
+++ b/apps/marketing/src/components/custom-tool.astro
@@ -7,35 +7,35 @@ import cursor from "../assets/logos/cursor.svg?url";
import { homepageStory } from "../content/site-copy";
---
-
+
-
Ask your agent
-
“{homepageStory.build.prompt}”
-
Your agent builds a reusable tool and deploys it to Executor.
+
Ask your agent
+
“{homepageStory.build.prompt}”
+
Your agent builds a reusable tool and deploys it to Executor.
-
-
-
↗
+
+
+ ↗
Daily brief
- Deployed
+ Deployed
-
Uses your accounts
-
+
Uses your accounts
+
PostHog
-
GitHub
+
GitHub
-
-
A tool your agents can call
-
daily_brief.get_changes()
-
Signups and open issues, compared with the last check.
+
+
A tool your agents can call
+
daily_brief.get_changes()
+
Signups and open issues, compared with the last check.
-
- {[claude, codex, cursor].map((icon) => )}
+
+ {[[claude], [codex, "logo-mono"], [cursor, "logo-mono"]].map(([icon, mono]) => )}
Build it with one agent. Use it and change it with another.
diff --git a/apps/marketing/src/components/daily-brief-demo.tsx b/apps/marketing/src/components/daily-brief-demo.tsx
index e5589a0a4c..558d0fd89e 100644
--- a/apps/marketing/src/components/daily-brief-demo.tsx
+++ b/apps/marketing/src/components/daily-brief-demo.tsx
@@ -46,33 +46,33 @@ export function DailyBriefDemo() {
return (
-
-
You
+
+
You
“{homepageStory.automate.prompt}”
-
-
+
+
↗
- Daily brief
- Your app
+ Daily brief
+ Your app
-
- Saved reports
+ Saved reports
{reports.map((item) => (
setReport(item)}
- className="flex flex-1 cursor-pointer items-center gap-2 rounded-[5px] px-3 py-2.5 text-left text-[12px] text-[#999] hover:bg-[#f7f7f7] hover:text-[#444] focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[#888] aria-pressed:bg-[#f0f0ee] aria-pressed:text-[#333] @min-[620px]:flex-none"
+ className="flex flex-1 cursor-pointer items-center gap-2 rounded-[5px] px-3 py-2.5 text-left text-[12px] text-tone-60 hover:bg-tone-97 hover:text-tone-27 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-tone-53 aria-pressed:bg-sage-hover aria-pressed:text-tone-20 @min-[620px]:flex-none"
>
-
+
{report.day} · 09:01
{report.date}
-
+
{report.headline}
-
New signups
-
+
New signups
+
{report.signups}
-
{report.change} from last report
+
{report.change} from last report
-
+
{report.bars.map((height, index) => (
))}
- Open issues
- {report.openIssues}
-
+ Open issues
+ {report.openIssues}
+
{report.fewerIssues} fewer than last time
-
+
{report.summary}
-
+
✓ Read PostHog + GitHub
✓ Write brief
✓ Save report
@@ -148,7 +145,7 @@ export function DailyBriefDemo() {
-
+
Your app keeps running. Your data stays with it.
Example data
diff --git a/apps/marketing/src/components/source-file.astro b/apps/marketing/src/components/source-file.astro
index 81165e88d7..acf490adad 100644
--- a/apps/marketing/src/components/source-file.astro
+++ b/apps/marketing/src/components/source-file.astro
@@ -13,11 +13,11 @@ const symbol = file.language === "md" ? "M↓" : file.language === "json" ? "{}"
type="button"
data-open-file={file.path}
aria-pressed={active}
- class="group flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-[5px] px-2.5 text-left text-[13px] text-[#666] transition-colors hover:bg-[#f0f0f0] hover:text-[#222] focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[#888] aria-pressed:bg-[#ededed] aria-pressed:text-[#171717]"
+ class="group flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-[5px] px-2.5 text-left text-[13px] text-tone-40 transition-colors hover:bg-tone-94 hover:text-tone-13 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-tone-53 aria-pressed:bg-tone-93 aria-pressed:text-tone-9"
>
{symbol}
{name}
diff --git a/apps/marketing/src/components/variants/copy-prompt.astro b/apps/marketing/src/components/variants/copy-prompt.astro
index d8339ec78e..a5fb5bb2b3 100644
--- a/apps/marketing/src/components/variants/copy-prompt.astro
+++ b/apps/marketing/src/components/variants/copy-prompt.astro
@@ -3,6 +3,7 @@
// carrying the three agent marks. Copies `setupPrompt`; the click handler
// lives in pages/index.astro.
import { setupPrompt } from "../../content/site-copy";
+import { logoClass } from "../../lib/logos";
import { appOrigin, siteOrigin } from "../../content/site-origin.ts";
const logos = import.meta.glob("../../assets/logos/*.svg", {
@@ -26,8 +27,8 @@ const marks = ["claude", "codex", "cursor"];
data-copy={setupPrompt({ site: siteOrigin, app: appOrigin })}
data-event="marketing_setup_prompt_copied"
>
-
- {marks.map((n) => )}
+
+ {marks.map((n) => )}
Build with your agent
diff --git a/apps/marketing/src/components/variants/doc-body.astro b/apps/marketing/src/components/variants/doc-body.astro
index 9b0522b1c0..d647f30af0 100644
--- a/apps/marketing/src/components/variants/doc-body.astro
+++ b/apps/marketing/src/components/variants/doc-body.astro
@@ -25,11 +25,11 @@ interface Props {
{/* ── Intro ── */}
{tagline}
- Your {introductionItems.map((item, i) => (
+
Your {introductionItems.map((item, i) => (
<>
{i === introductionItems.length - 1 && "and "}
{"comingSoon" in item ? (
-
+
{item.label}
Coming soon
@@ -38,7 +38,7 @@ interface Props {
>
))}all in one place.
@@ -85,7 +85,7 @@ interface Props {
Cloud
Deploy your apps. We handle the hosting.
-
Deploy an app
+
Deploy an app
Desktop{release.channel === "beta" && Beta }
@@ -98,7 +98,7 @@ interface Props {
))}
Other platforms ▾
-
+
diff --git a/apps/marketing/src/components/variants/rail-b.astro b/apps/marketing/src/components/variants/rail-b.astro
index 1828e84406..21f2beb9f2 100644
--- a/apps/marketing/src/components/variants/rail-b.astro
+++ b/apps/marketing/src/components/variants/rail-b.astro
@@ -47,7 +47,7 @@ const pad = (index: number) => String(index + 1).padStart(2, "0");
-
+
@@ -116,7 +116,7 @@ const pad = (index: number) => String(index + 1).padStart(2, "0");
- X
+ X
@RhysSullivan
diff --git a/apps/marketing/src/components/variants/testimonials.astro b/apps/marketing/src/components/variants/testimonials.astro
index 665d0f1f0a..4a29bf22e6 100644
--- a/apps/marketing/src/components/variants/testimonials.astro
+++ b/apps/marketing/src/components/variants/testimonials.astro
@@ -61,7 +61,7 @@ const composeUrl = `https://x.com/intent/post?text=${encodeURIComponent(
id="testimonials-dialog"
aria-labelledby="testimonials-title"
aria-describedby="testimonials-description"
- class="testi-dialog fixed inset-0 m-auto h-[min(860px,88dvh)] max-h-none w-[min(1120px,calc(100%_-_3rem))] max-w-none translate-y-4 scale-[0.98] overflow-hidden rounded-3xl border border-white/70 bg-surface-2 p-0 text-ink opacity-0 shadow-[0_24px_100px_#00000026] transition-[opacity,translate,scale,display,overlay] transition-discrete duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] open:translate-y-0 open:scale-100 open:opacity-100 starting:open:translate-y-4 starting:open:scale-[0.98] starting:open:opacity-0 backdrop:bg-black/20 backdrop:opacity-0 backdrop:backdrop-blur-sm backdrop:transition-[opacity,display,overlay] backdrop:transition-discrete backdrop:duration-300 open:backdrop:opacity-100 starting:open:backdrop:opacity-0 motion-reduce:transform-none motion-reduce:transition-none motion-reduce:backdrop:transition-none max-[639.98px]:h-[94dvh] max-[639.98px]:w-[calc(100%_-_1rem)] max-[639.98px]:rounded-2xl"
+ class="testi-dialog fixed inset-0 m-auto h-[min(860px,88dvh)] max-h-none w-[min(1120px,calc(100%_-_3rem))] max-w-none translate-y-4 scale-[0.98] overflow-hidden rounded-3xl border border-tone-100/70 bg-surface-2 p-0 text-ink opacity-0 shadow-[0_24px_100px_#00000026] transition-[opacity,translate,scale,display,overlay] transition-discrete duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] open:translate-y-0 open:scale-100 open:opacity-100 starting:open:translate-y-4 starting:open:scale-[0.98] starting:open:opacity-0 backdrop:bg-black/20 backdrop:opacity-0 backdrop:backdrop-blur-sm backdrop:transition-[opacity,display,overlay] backdrop:transition-discrete backdrop:duration-300 open:backdrop:opacity-100 starting:open:backdrop:opacity-0 motion-reduce:transform-none motion-reduce:transition-none motion-reduce:backdrop:transition-none max-[639.98px]:h-[94dvh] max-[639.98px]:w-[calc(100%_-_1rem)] max-[639.98px]:rounded-2xl"
>
diff --git a/apps/marketing/src/components/workflow-durable.tsx b/apps/marketing/src/components/workflow-durable.tsx
index cddfce83c5..1de14895b2 100644
--- a/apps/marketing/src/components/workflow-durable.tsx
+++ b/apps/marketing/src/components/workflow-durable.tsx
@@ -71,7 +71,10 @@ function TaskCard({
[0.35, 0.35, 1, 1, 0.35, 0.35],
);
const done = useTransform(age, [0, 0.57, 0.65, 1.9, 2.1, stages.length], [0, 0, 1, 1, 0, 0]);
- const borderColor = useTransform(done, [0, 1], ["#e4e4e4", "#ccc"]);
+ const borderColor = useTransform(
+ done,
+ (amount) => `color-mix(in oklab, var(--color-tone-80) ${amount * 100}%, var(--color-tone-89))`,
+ );
const status = useTransform(age, (value): string => {
if (value < 0.44) return "Waiting";
if (value < 0.57) return "Running…";
@@ -80,18 +83,18 @@ function TaskCard({
});
return (
- {stage.stamp}
+ {stage.stamp}
- {stage.title}
- {status}
+ {stage.title}
+ {status}
-
+
✓ {stage.completed}
@@ -102,7 +105,12 @@ function SleepClock({ progress }: { readonly progress: MotionValue }) {
const rotate = useTransform(progress, [0, 1], [0, 360]);
return (
-
+
);
}
@@ -134,29 +142,29 @@ function SleepStep({
);
return (
-
+
sleep
-
+
-
+
-
+
-
+
{stage.sleep}
diff --git a/apps/marketing/src/layouts/Layout.astro b/apps/marketing/src/layouts/Layout.astro
index cf4c98f4dd..18feb96a16 100644
--- a/apps/marketing/src/layouts/Layout.astro
+++ b/apps/marketing/src/layouts/Layout.astro
@@ -27,7 +27,15 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? Astro.url).toString(
-
+
+
{noindex ? : }
diff --git a/apps/marketing/src/lib/logos.ts b/apps/marketing/src/lib/logos.ts
new file mode 100644
index 0000000000..b8c3023f74
--- /dev/null
+++ b/apps/marketing/src/lib/logos.ts
@@ -0,0 +1,20 @@
+// Logos drawn in a single near-black colour. Dark mode inverts them through
+// the `.logo-mono` class in styles/global.css; coloured logos stay as they are.
+const monoLogos = new Set([
+ "axiom",
+ "chatgpt",
+ "codex",
+ "copilot",
+ "cursor",
+ "github",
+ "grok",
+ "hermes",
+ "mcp",
+ "opencode",
+ "windsurf",
+ "x",
+ "zed",
+]);
+
+export const logoClass = (name: string): string | undefined =>
+ monoLogos.has(name) ? "logo-mono" : undefined;
diff --git a/apps/marketing/src/pages/pricing.astro b/apps/marketing/src/pages/pricing.astro
index 537813d600..e018dca410 100644
--- a/apps/marketing/src/pages/pricing.astro
+++ b/apps/marketing/src/pages/pricing.astro
@@ -61,7 +61,7 @@ const cards = [
{amount}
{unit ? {unit} : null}
- _&]:w-full max-[719.98px]:[.doc\\_\\_actions_>_&]:justify-center max-[719.98px]:[.doc\\_\\_actions_>_&]:text-center max-[719.98px]:[.doc\\_\\_way-a_&]:w-full max-[719.98px]:[.doc\\_\\_way-a_&]:justify-center max-[719.98px]:[.doc\\_\\_way-a_&]:text-center [.pricing\\_\\_card_>_&]:self-start [.pricing\\_\\_card_>_&]:mb-[1.4rem]" : "btn-secondary inline-flex items-center gap-[0.4rem] bg-surface text-ink border border-rule rounded-[8px] py-[0.7rem] px-[1.25rem] [font-family:inherit] font-medium text-[14.5px] tracking-[-0.005em] cursor-pointer [transition:background_0.15s_ease,_border-color_0.15s_ease] hover:[background:var(--color-surface-2)] hover:[border-color:var(--color-ink-3)] [.doc\\_\\_way-a_&]:text-[13.5px] [.doc\\_\\_way-a_&]:py-[0.55rem] [.doc\\_\\_way-a_&]:px-[0.95rem] [.doc\\_\\_section_a&]:no-underline max-[719.98px]:[.doc\\_\\_way-a_&]:w-full max-[719.98px]:[.doc\\_\\_way-a_&]:justify-center max-[719.98px]:[.doc\\_\\_way-a_&]:text-center [.pricing\\_\\_card_>_&]:self-start [.pricing\\_\\_card_>_&]:mb-[1.4rem]"}>{label}
+ _&]:w-full max-[719.98px]:[.doc\\_\\_actions_>_&]:justify-center max-[719.98px]:[.doc\\_\\_actions_>_&]:text-center max-[719.98px]:[.doc\\_\\_way-a_&]:w-full max-[719.98px]:[.doc\\_\\_way-a_&]:justify-center max-[719.98px]:[.doc\\_\\_way-a_&]:text-center [.pricing\\_\\_card_>_&]:self-start [.pricing\\_\\_card_>_&]:mb-[1.4rem]" : "btn-secondary inline-flex items-center gap-[0.4rem] bg-surface text-ink border border-rule rounded-[8px] py-[0.7rem] px-[1.25rem] [font-family:inherit] font-medium text-[14.5px] tracking-[-0.005em] cursor-pointer [transition:background_0.15s_ease,_border-color_0.15s_ease] hover:[background:var(--color-surface-2)] hover:[border-color:var(--color-ink-3)] [.doc\\_\\_way-a_&]:text-[13.5px] [.doc\\_\\_way-a_&]:py-[0.55rem] [.doc\\_\\_way-a_&]:px-[0.95rem] [.doc\\_\\_section_a&]:no-underline max-[719.98px]:[.doc\\_\\_way-a_&]:w-full max-[719.98px]:[.doc\\_\\_way-a_&]:justify-center max-[719.98px]:[.doc\\_\\_way-a_&]:text-center [.pricing\\_\\_card_>_&]:self-start [.pricing\\_\\_card_>_&]:mb-[1.4rem]"}>{label}
{tier.featuresLabel ? {tier.featuresLabel}
: null}
{tier.features.map((f) => (
diff --git a/apps/marketing/src/styles/dark-theme.css b/apps/marketing/src/styles/dark-theme.css
new file mode 100644
index 0000000000..035e66f5ad
--- /dev/null
+++ b/apps/marketing/src/styles/dark-theme.css
@@ -0,0 +1,59 @@
+/* The dark palette, shared by the marketing build (styles/global.css) and the
+ documentation (apps/docs/styles.css). Blume's theme script and the site's
+ own head script set data-theme on from the visitor's choice, stored
+ under "blume-theme", or from the system setting when there is none. */
+:root[data-theme="dark"] {
+ color-scheme: dark;
+
+ --color-surface: #0a0a0a;
+ --color-surface-2: #111111;
+ --color-ink: #ededed;
+ --color-ink-2: #a1a1a1;
+ --color-ink-3: #878787;
+ --color-rule: #232323;
+ --color-rule-strong: #3a3a3a;
+ --color-accent: #ededed;
+ --color-accent-hover: #cccccc;
+ --color-ink-hover: #d4d4d4;
+
+ --color-tone-100: #0a0a0a;
+ --color-tone-99: #0d0d0d;
+ --color-tone-98: #111111;
+ --color-tone-97: #161616;
+ --color-tone-94: #1a1a1a;
+ --color-tone-93: #1f1f1f;
+ --color-tone-91: #262626;
+ --color-tone-90: #292929;
+ --color-tone-89: #2e2e2e;
+ --color-tone-87: #333333;
+ --color-tone-80: #444444;
+ --color-tone-77: #4a4a4a;
+ --color-tone-73: #555555;
+ --color-tone-67: #666666;
+ --color-tone-60: #7a7a7a;
+ --color-tone-53: #8f8f8f;
+ --color-tone-47: #9a9a9a;
+ --color-tone-40: #a1a1a1;
+ --color-tone-33: #b4b4b4;
+ --color-tone-27: #c4c4c4;
+ --color-tone-20: #d4d4d4;
+ --color-tone-13: #e5e5e5;
+ --color-tone-9: #ededed;
+
+ --color-sage-wash: #1a1f1a;
+ --color-sage-hover: #171a17;
+ --color-sage-bar: #2a2d29;
+ --color-sage-mark: #93a28e;
+ --color-sage: #86a690;
+ --color-sage-text: #8fae98;
+ --color-sage-bar-strong: #93a592;
+ --color-file-ts: #77a5d4;
+ --color-file-tsx: #74b3b9;
+ --color-glyph-md: #a6a69a;
+ --color-glyph-json: #93a6ac;
+}
+
+/* Single-colour black logos and the brand mark. */
+:root[data-theme="dark"] .logo-mono {
+ filter: invert(1);
+}
diff --git a/apps/marketing/src/styles/global.css b/apps/marketing/src/styles/global.css
index 719d597bf1..4b346803cc 100644
--- a/apps/marketing/src/styles/global.css
+++ b/apps/marketing/src/styles/global.css
@@ -1,5 +1,6 @@
@import "tailwindcss";
@import "./site-frame.css";
+@import "./dark-theme.css";
@source "../**/*.astro";
@@ -21,6 +22,52 @@
--color-accent: #0a0a0a;
--color-accent-hover: #333333;
+ --color-ink-hover: #1f1f24;
+
+ /* Neutral ramp for illustrations, named by lightness in the light theme.
+ Dark mode remaps each step, so a tone keeps its place in the hierarchy. */
+ --color-tone-100: #ffffff;
+ --color-tone-99: #fdfdfd;
+ --color-tone-98: #fafafa;
+ --color-tone-97: #f7f7f7;
+ --color-tone-94: #f0f0f0;
+ --color-tone-93: #ededed;
+ --color-tone-91: #e8e8e8;
+ --color-tone-90: #e5e5e5;
+ --color-tone-89: #e3e3e3;
+ --color-tone-87: #dddddd;
+ --color-tone-80: #cccccc;
+ --color-tone-77: #c4c4c4;
+ --color-tone-73: #bbbbbb;
+ --color-tone-67: #aaaaaa;
+ --color-tone-60: #999999;
+ --color-tone-53: #888888;
+ --color-tone-47: #777777;
+ --color-tone-40: #666666;
+ --color-tone-33: #555555;
+ --color-tone-27: #444444;
+ --color-tone-20: #333333;
+ --color-tone-13: #222222;
+ --color-tone-9: #171717;
+
+ /* Sage accents for "deployed" and report states. */
+ --color-sage-wash: #eef0ec;
+ --color-sage-hover: #f0f0ee;
+ --color-sage-bar: #deded9;
+ --color-sage-mark: #7d8978;
+ --color-sage: #78917f;
+ --color-sage-text: #6f8978;
+ --color-sage-bar-strong: #818d80;
+ --color-file-ts: #5987b3;
+ --color-file-tsx: #5c989e;
+ --color-glyph-md: #a3a398;
+ --color-glyph-json: #8b9ba0;
+}
+
+/* Astro's emits both themes; switch to the dark one. */
+:root[data-theme="dark"] .astro-code,
+:root[data-theme="dark"] .astro-code span {
+ color: var(--shiki-dark) !important;
}
@layer base {