How we cracked demos
Demos used to run long. We fixed it with one rule and a timer.
- Two minutes each
- Show, don't tell
The best demo is the thing working.
Styles for markdown and MDX.
Demos used to run long. We fixed it with one rule and a timer.
The best demo is the thing working.
Copy into src/components/ph/ .
.ph-prose {
display: flex;
flex-direction: column;
gap: 1rem;
font-size: 1.0625rem;
line-height: 1.6;
letter-spacing: -0.01em;
}
.ph-prose :where(h1, h2, h3, h4) {
margin-top: 1rem;
font-family: var(--ph-font-pixel);
line-height: 1.05;
letter-spacing: 0;
}
.ph-prose :where(h1) {
font-size: 2.25rem;
}
.ph-prose :where(h2) {
font-size: 1.75rem;
}
.ph-prose :where(h3) {
font-size: 1.375rem;
}
.ph-prose :where(a) {
color: inherit;
text-decoration-line: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
text-decoration-color: var(--rule);
transition: background-color 150ms var(--ph-ease);
}
.ph-prose :where(a:hover) {
background-color: color-mix(in oklab, var(--ph-yellow) 40%, transparent);
}
.ph-prose :where(ul, ol) {
padding-left: 1.5rem;
}
.ph-prose :where(ul) {
list-style: square;
}
.ph-prose :where(ol) {
list-style: decimal;
}
.ph-prose :where(li) {
margin-block: 0.25rem;
}
.ph-prose :where(ul > li)::marker {
color: var(--ph-orange);
}
.ph-prose :where(ol > li)::marker {
color: var(--ph-orange);
font-family: var(--ph-font-mono);
font-weight: 700;
}
.ph-prose :where(:not(pre) > code) {
padding: 0.05em 0.3em;
background: var(--muted);
font-family: var(--ph-font-mono);
font-size: 0.92em;
}
.ph-prose :where(pre) {
overflow-x: auto;
padding: 0.75rem 1rem;
border: 1px solid var(--border);
background: var(--card);
font-family: var(--ph-font-mono);
font-size: 0.9rem;
line-height: 1.5;
}
.ph-prose :where(blockquote) {
padding-left: 1rem;
border-left: 2px solid var(--foreground);
font-style: italic;
}
.ph-prose :where(img, video, iframe) {
max-width: 100%;
height: auto;
}
.ph-prose :where(figure) {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.ph-prose :where(figcaption) {
font-size: 0.8125rem;
font-style: italic;
text-align: center;
}
.ph-prose :where(hr) {
border: 0;
border-top: 1px solid var(--border);
margin-block: 1rem;
}
.dark .ph-prose :where(ul > li, ol > li)::marker {
color: var(--ph-yellow);
}
---
import "./prose.css";
type Props = {
as?: "article" | "div" | "section";
class?: string;
};
const { as: Tag = "article", class: className } = Astro.props;
---
<Tag class:list={["ph-prose", className]}>
<slot />
</Tag>
Copy into components/ph/ (or src/components/ph/).
.ph-prose {
display: flex;
flex-direction: column;
gap: 1rem;
font-size: 1.0625rem;
line-height: 1.6;
letter-spacing: -0.01em;
}
.ph-prose :where(h1, h2, h3, h4) {
margin-top: 1rem;
font-family: var(--ph-font-pixel);
line-height: 1.05;
letter-spacing: 0;
}
.ph-prose :where(h1) {
font-size: 2.25rem;
}
.ph-prose :where(h2) {
font-size: 1.75rem;
}
.ph-prose :where(h3) {
font-size: 1.375rem;
}
.ph-prose :where(a) {
color: inherit;
text-decoration-line: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
text-decoration-color: var(--rule);
transition: background-color 150ms var(--ph-ease);
}
.ph-prose :where(a:hover) {
background-color: color-mix(in oklab, var(--ph-yellow) 40%, transparent);
}
.ph-prose :where(ul, ol) {
padding-left: 1.5rem;
}
.ph-prose :where(ul) {
list-style: square;
}
.ph-prose :where(ol) {
list-style: decimal;
}
.ph-prose :where(li) {
margin-block: 0.25rem;
}
.ph-prose :where(ul > li)::marker {
color: var(--ph-orange);
}
.ph-prose :where(ol > li)::marker {
color: var(--ph-orange);
font-family: var(--ph-font-mono);
font-weight: 700;
}
.ph-prose :where(:not(pre) > code) {
padding: 0.05em 0.3em;
background: var(--muted);
font-family: var(--ph-font-mono);
font-size: 0.92em;
}
.ph-prose :where(pre) {
overflow-x: auto;
padding: 0.75rem 1rem;
border: 1px solid var(--border);
background: var(--card);
font-family: var(--ph-font-mono);
font-size: 0.9rem;
line-height: 1.5;
}
.ph-prose :where(blockquote) {
padding-left: 1rem;
border-left: 2px solid var(--foreground);
font-style: italic;
}
.ph-prose :where(img, video, iframe) {
max-width: 100%;
height: auto;
}
.ph-prose :where(figure) {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.ph-prose :where(figcaption) {
font-size: 0.8125rem;
font-style: italic;
text-align: center;
}
.ph-prose :where(hr) {
border: 0;
border-top: 1px solid var(--border);
margin-block: 1rem;
}
.dark .ph-prose :where(ul > li, ol > li)::marker {
color: var(--ph-yellow);
}
import type { ReactNode } from "react";
import { cn } from "./cn";
import "./prose.css";
type ProseProps = {
as?: "article" | "div" | "section";
children: ReactNode;
className?: string;
};
export function Prose({ as: Tag = "article", children, className }: ProseProps) {
return <Tag className={cn("ph-prose", className)}>{children}</Tag>;
}
export const cn = (...parts: (string | false | null | undefined)[]) => parts.filter(Boolean).join(" ");