diff --git a/website/src/components/markdown.tsx b/website/src/components/markdown.tsx
index 15d5152..6e823a8 100644
--- a/website/src/components/markdown.tsx
+++ b/website/src/components/markdown.tsx
@@ -478,11 +478,13 @@ export function ChartPlaceholder({ height = 200, label }: { height?: number; lab
Comparison table
========================================================================= */
-export function PropsTable({
+export function ComparisonTable({
title,
+ headers,
rows,
}: {
title?: string;
+ headers: [string, string, string];
rows: Array<[string, string, string]>;
}) {
return (
@@ -511,7 +513,7 @@ export function PropsTable({
>
- {["Prop", "Type", "Default"].map((header) => {
+ {headers.map((header) => {
return (
- {rows.map(([prop, type, def]) => {
+ {rows.map(([feature, them, us]) => {
return (
-
+
- {prop}
+ {feature}
- {type}
+ {them}
- {def}
+ {us}
);
diff --git a/website/src/root.tsx b/website/src/root.tsx
index 9c87729..7609b4d 100644
--- a/website/src/root.tsx
+++ b/website/src/root.tsx
@@ -11,19 +11,10 @@ import {
export function Layout({ children }: { children: React.ReactNode }) {
return (
-
+
- {/*
- Apply .dark class based on OS prefers-color-scheme.
- Inline in to prevent FOUC. Listener handles live OS changes.
- */}
-
-
Extension icon green = connected. Gray = not attached to this tab.
@@ -186,7 +206,6 @@ export default function LivelinePage() {
screenshots when spatial layout matters — grids, dashboards, maps.
-
Accessibility tree as text. 5–20KB vs 100KB+ for screenshots.
@@ -217,7 +236,6 @@ export default function LivelinePage() {
and visual modes freely.
-
Vimium-style labels. Screenshot + snapshot in one call.
@@ -286,7 +304,6 @@ export default function LivelinePage() {
grep across all loaded scripts.
-
Breakpoints, stepping, variable inspection — from the CLI.
@@ -351,7 +368,6 @@ export default function LivelinePage() {
cancel without saving with cancelRecording.
-
Native tab capture. 30–60fps. Survives navigation.
@@ -364,8 +380,9 @@ export default function LivelinePage() {
How Playwriter compares to other browser automation approaches.
-
-
-
-
-
- Control Chrome on a remote machine over the internet using tunnels.
- Run the relay on the host, expose it through a tunnel, and connect
- from anywhere.
+ Control Chrome on any machine from anywhere over the internet.
+ The relay runs on the host alongside Chrome.
+ A{" "}
+ traforo {" "}
+ tunnel exposes it through Cloudflare, giving you a secure public URL.
+ No VPN, no firewall rules, no port forwarding.
{dedent`
- # On the host machine
+ # On the host machine — start relay with tunnel
npx -y traforo -p 19988 -t my-machine -- npx -y playwriter serve --token
- # From anywhere
+ # From anywhere — set env vars and use normally
export PLAYWRITER_HOST=https://my-machine-tunnel.traforo.dev
export PLAYWRITER_TOKEN=
playwriter -s 1 -e "await page.goto('https://example.com')"
@@ -430,9 +443,11 @@ export default function LivelinePage() {
Also works on a LAN without tunnels — just set{" "}
- PLAYWRITER_HOST=192.168.1.10. Use cases
- include controlling a headless Mac mini, providing remote user
- support, and multi-machine automation.
+ PLAYWRITER_HOST=192.168.1.10. Works for MCP
+ too — set PLAYWRITER_HOST and{" "}
+ PLAYWRITER_TOKEN in your MCP client env config.
+ Use cases: headless Mac mini, remote user support,
+ multi-machine automation, dev from a VM or devcontainer.
diff --git a/website/src/styles/globals.css b/website/src/styles/globals.css
index dcd1a23..ab8e28c 100644
--- a/website/src/styles/globals.css
+++ b/website/src/styles/globals.css
@@ -1,5 +1,5 @@
@import "tailwindcss";
-@custom-variant dark (&:where(.dark, .dark *));
+@custom-variant dark (@media (prefers-color-scheme: dark));
@plugin "@tailwindcss/typography";
@@ -38,9 +38,8 @@
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.871 0.006 286.286);
- /* Dark mode overrides — @variant dark inside :root compiles to
- :root:where(.dark, .dark *), following whatever strategy is
- configured in @custom-variant dark above */
+ /* Dark mode overrides — uses prefers-color-scheme media query
+ via @custom-variant dark above */
@variant dark {
--background: oklch(0.21 0.006 285.885);
--foreground: oklch(0.985 0 0);
@@ -137,11 +136,21 @@
}
::-webkit-scrollbar-thumb {
- background: var(--color-muted);
+ background: rgba(0, 0, 0, 0.15);
border-radius: var(--radius-md);
border: none;
}
::-webkit-scrollbar-thumb:hover {
- background: var(--color-muted-foreground);
+ background: rgba(0, 0, 0, 0.3);
+}
+
+@variant dark {
+ ::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.15);
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+ }
}
diff --git a/website/src/styles/liveline-prism.css b/website/src/styles/liveline-prism.css
index 3e087df..bf23213 100644
--- a/website/src/styles/liveline-prism.css
+++ b/website/src/styles/liveline-prism.css
@@ -136,93 +136,95 @@ pre[class*="language-"] {
/* ======================================================================
Dark mode - GitHub Dark inspired palette.
- Plain CSS (not processed by Tailwind), uses .dark ancestor selector
- matching @custom-variant dark in globals.css.
+ Uses @variant dark from globals.css so the strategy is defined in
+ one place (@custom-variant dark in globals.css).
====================================================================== */
-.dark code[class*="language-"],
-.dark pre[class*="language-"] {
- color: #e6edf3;
-}
+@variant dark {
+ code[class*="language-"],
+ pre[class*="language-"] {
+ color: #e6edf3;
+ }
-.dark .token.keyword,
-.dark .token.module,
-.dark .token.imports,
-.dark .token.control-flow {
- color: #ff7b72;
-}
+ .token.keyword,
+ .token.module,
+ .token.imports,
+ .token.control-flow {
+ color: #ff7b72;
+ }
-.dark .token.string,
-.dark .token.template-string,
-.dark .token.attr-value {
- color: #a5d6ff;
-}
+ .token.string,
+ .token.template-string,
+ .token.attr-value {
+ color: #a5d6ff;
+ }
-.dark .token.function,
-.dark .token.class-name {
- color: #d2a8ff;
-}
+ .token.function,
+ .token.class-name {
+ color: #d2a8ff;
+ }
-.dark .token.property,
-.dark .token.parameter {
- color: #ffa657;
-}
+ .token.property,
+ .token.parameter {
+ color: #ffa657;
+ }
-.dark .token.tag .token.tag {
- color: #7ee787;
-}
+ .token.tag .token.tag {
+ color: #7ee787;
+ }
-.dark .token.tag .token.class-name {
- color: #7ee787;
-}
+ .token.tag .token.class-name {
+ color: #7ee787;
+ }
-.dark .token.tag .token.attr-name,
-.dark .token.attr-name {
- color: #79c0ff;
-}
+ .token.tag .token.attr-name,
+ .token.attr-name {
+ color: #79c0ff;
+ }
-.dark .token.number {
- color: #79c0ff;
-}
+ .token.number {
+ color: #79c0ff;
+ }
-.dark .token.operator {
- color: #ff7b72;
-}
+ .token.operator {
+ color: #ff7b72;
+ }
-.dark .token.punctuation {
- color: #e6edf3;
-}
+ .token.punctuation {
+ color: #e6edf3;
+ }
-.dark .token.boolean,
-.dark .token.constant,
-.dark .token.builtin {
- color: #79c0ff;
-}
+ .token.boolean,
+ .token.constant,
+ .token.builtin {
+ color: #79c0ff;
+ }
-.dark .token.comment,
-.dark .token.prolog,
-.dark .token.doctype,
-.dark .token.cdata {
- color: #8b949e;
- font-style: italic;
-}
+ .token.comment,
+ .token.prolog,
+ .token.doctype,
+ .token.cdata {
+ color: #8b949e;
+ font-style: italic;
+ }
-.dark .token.tag .token.punctuation {
- color: #e6edf3;
-}
+ .token.tag .token.punctuation {
+ color: #e6edf3;
+ }
-.dark .token.spread {
- color: #ff7b72;
-}
+ .token.spread {
+ color: #ff7b72;
+ }
-.dark .token.plain-text {
- color: #e6edf3;
-}
+ .token.plain-text {
+ color: #e6edf3;
+ }
-.dark .token.regex {
- color: #a5d6ff;
-}
+ .token.regex {
+ color: #a5d6ff;
+ }
-.dark .language-bash .token.function {
- color: #79c0ff;
+ .language-bash .token.function {
+ color: #79c0ff;
+ }
}
diff --git a/website/src/styles/liveline.css b/website/src/styles/liveline.css
index ebe035b..20c26ec 100644
--- a/website/src/styles/liveline.css
+++ b/website/src/styles/liveline.css
@@ -113,9 +113,10 @@
--ll-fade-12: rgba(255, 255, 255, 0);
}
-/* Dark mode overrides — plain CSS, not processed by Tailwind.
- Uses .dark ancestor selector matching @custom-variant dark in globals.css */
-.dark .liveline-page {
+/* Dark mode overrides — uses @variant dark from globals.css
+ so the strategy is defined in one place */
+@variant dark {
+.liveline-page {
--ll-text-primary: rgba(255, 255, 255, 0.9);
--ll-text-secondary: rgba(255, 255, 255, 0.45);
--ll-text-tertiary: rgba(255, 255, 255, 0.25);
@@ -161,6 +162,7 @@
--ll-fade-11: rgba(17, 17, 17, 0.002);
--ll-fade-12: rgba(17, 17, 17, 0);
}
+}
@supports (color: color(display-p3 1 1 1)) {
.liveline-page {
@@ -258,12 +260,14 @@
z-index: -1;
}
-.dark .ll-inline-code {
- color: rgba(255, 255, 255, 0.75);
-}
+@variant dark {
+ .ll-inline-code {
+ color: rgba(255, 255, 255, 0.75);
+ }
-.dark .ll-inline-code::before {
- background: rgba(255, 255, 255, 0.08);
+ .ll-inline-code::before {
+ background: rgba(255, 255, 255, 0.08);
+ }
}
/* ========================================================================