feat: binary glow effects, refined drawings and calls to action

Dark surfaces now carry light: frames glow along their edge and shed binary
digits (BitAura), dark sections open with a lit seam (BitSeam), the career
path is a line of light and "My work" became a dark stage. Buttons carry a
travelling light and a spot that follows the pointer (BitGlow).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 13:07:09 +02:00
co-authored by Claude Opus 5.5
parent e9f05b02fc
commit e89a248ca5
21 changed files with 1532 additions and 606 deletions
@@ -15,6 +15,10 @@ const LINE = "#475569";
const SOFT = "#1e293b";
const MARK = "#fde68a";
const LIVE = "#10b981";
/** A lit surface: a light tint instead of a solid white block. */
const TINT = "rgba(248, 250, 252, 0.14)";
const RIM = "#64748b";
const FINE = 1.1;
const MONO = {
fontFamily: "var(--font-jetbrains-mono), ui-monospace, monospace",
// Set on the svg: larger on phones, where the drawing is scaled down.
@@ -70,8 +74,11 @@ const Packet: React.FC<{
pause?: number;
}> = ({ xs, ys, delay = 0, duration = 1.2, pause = 1.4 }) => (
<motion.circle
r={5}
r={3.5}
fill={MARK}
stroke={MARK}
strokeOpacity={0.28}
strokeWidth={6}
initial={{ cx: xs[0], cy: ys[0], opacity: 0 }}
animate={{
cx: [...xs],
@@ -96,18 +103,29 @@ const Person: React.FC<{ x: number; label: string; filled?: boolean }> = ({
<g>
<circle
cx={x}
cy={128}
r={20}
fill={filled ? INK : PAPER}
cy={130}
r={15}
fill={filled ? TINT : "none"}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
<path
d={`M ${x - 34} 196 Q ${x - 34} 158 ${x} 158 Q ${x + 34} 158 ${x + 34} 196`}
fill={filled ? INK : PAPER}
d={`M ${x - 28} 194 C ${x - 28} 172 ${x - 16} 160 ${x} 160 C ${x + 16} 160 ${x + 28} 172 ${x + 28} 194`}
fill={filled ? TINT : "none"}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
strokeLinecap="round"
/>
<line
x1={x - 36}
x2={x + 36}
y1={194}
y2={194}
stroke={LINE}
strokeWidth={1}
strokeLinecap="round"
/>
{filled && <circle cx={x + 12} cy={119} r={3.5} fill={MARK} />}
<text x={x} y={222} textAnchor="middle" style={MONO}>
{label}
</text>
@@ -129,31 +147,41 @@ const Talk: React.FC = () => (
x={b.x}
y={b.y}
width={b.w}
height={26}
rx={13}
fill={b.side === "left" ? SOFT : INK}
height={24}
rx={12}
fill={b.side === "left" ? SOFT : TINT}
stroke={b.side === "left" ? LINE : INK}
strokeWidth={b.side === "left" ? 1 : FINE}
/>
<line
x1={b.x + 14}
x2={b.x + b.w - 14}
y1={b.y + 13}
y2={b.y + 13}
stroke={b.side === "left" ? "#94a3b8" : PAPER}
strokeWidth={2}
y1={b.y + 12}
y2={b.y + 12}
stroke={b.side === "left" ? "#94a3b8" : INK}
strokeWidth={1.5}
strokeLinecap="round"
/>
</motion.g>
))}
<motion.g {...appear(1.7)}>
<rect x={258} y={200} width={72} height={26} rx={13} fill={INK} />
<rect
x={270}
y={200}
width={60}
height={24}
rx={12}
fill={TINT}
stroke={INK}
strokeWidth={FINE}
/>
{[0, 1, 2].map((dot) => (
<motion.circle
key={dot}
cx={280 + dot * 14}
cy={213}
r={3}
fill={PAPER}
cx={288 + dot * 12}
cy={212}
r={2.25}
fill={INK}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{ duration: 1.1, delay: dot * 0.18, repeat: Infinity }}
/>
@@ -186,15 +214,16 @@ const WeeklyHours: React.FC = () => (
return (
<motion.rect
key={h}
x={52 + d * 72}
y={64 + h * 22}
width={52}
height={16}
rx={4}
x={56 + d * 72}
y={66 + h * 21}
width={44}
height={10}
rx={5}
strokeWidth={1}
initial={{ fill: PAPER, stroke: LINE }}
animate={{
fill: reserved ? INK : PAPER,
stroke: reserved ? INK : LINE,
fill: reserved ? "#e2e8f0" : PAPER,
stroke: reserved ? "#e2e8f0" : LINE,
}}
transition={{
duration: 0.25,
@@ -242,14 +271,14 @@ const WorkList: React.FC = () => (
rx={4}
fill={done ? INK : PAPER}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
{done && (
<motion.path
d={`M ${76} ${y + 16} l 3.5 3.5 l 6 -7`}
fill="none"
stroke={PAPER}
strokeWidth={2}
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
{...draw(0.3 + row * 0.45)}
@@ -261,7 +290,7 @@ const WorkList: React.FC = () => (
y1={y + 16}
y2={y + 16}
stroke={done ? INK : LINE}
strokeWidth={2}
strokeWidth={1.5}
strokeLinecap="round"
/>
{done && (
@@ -269,7 +298,7 @@ const WorkList: React.FC = () => (
<path
d={`M 322 ${y + 16} h 46`}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
markerEnd="url(#arrow)"
/>
<Packet
@@ -319,7 +348,7 @@ const WorkList: React.FC = () => (
/** Developer, step 4: two weeks notice to the end of a month. */
const Notice: React.FC = () => (
<g>
<line x1={50} x2={430} y1={150} y2={150} stroke={LINE} strokeWidth={2} />
<line x1={50} x2={430} y1={150} y2={150} stroke={LINE} strokeWidth={1.5} />
{Array.from({ length: 5 }, (_, w) => (
<g key={w}>
<line
@@ -328,7 +357,7 @@ const Notice: React.FC = () => (
y1={140}
y2={160}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
<text x={50 + w * 95} y={182} textAnchor="middle" style={MONO}>
{w === 4 ? "end of month" : `week ${w + 1}`}
@@ -337,9 +366,9 @@ const Notice: React.FC = () => (
))}
<motion.rect
x={240}
y={132}
height={36}
rx={6}
y={139}
height={22}
rx={5}
fill={MARK}
initial={{ width: 0, opacity: 0.35 }}
animate={{ width: 190 }}
@@ -350,13 +379,13 @@ const Notice: React.FC = () => (
d="M 240 118 v -14 h 190 v 14"
fill="none"
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
<text x={335} y={92} textAnchor="middle" style={{ ...MONO, fill: INK }}>
two weeks notice
</text>
</motion.g>
<motion.circle cx={430} cy={150} r={7} fill={INK} {...appear(1.4, 0)} />
<motion.circle cx={430} cy={150} r={4.5} fill={INK} {...appear(1.4, 0)} />
</g>
);
@@ -398,7 +427,7 @@ const RepairWeek: React.FC = () => (
x2={to[0]}
y2={to[1]}
stroke="#f59e0b"
strokeWidth={1.5}
strokeWidth={FINE}
strokeDasharray="3 5"
/>
<motion.line
@@ -407,7 +436,7 @@ const RepairWeek: React.FC = () => (
x2={to[0]}
y2={to[1]}
stroke={INK}
strokeWidth={2}
strokeWidth={1.5}
{...draw(0.4 + i * 0.22)}
/>
</g>
@@ -418,9 +447,9 @@ const RepairWeek: React.FC = () => (
key={i}
cx={x}
cy={y}
r={7}
r={5}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
initial={{ fill: PAPER }}
animate={{ fill: LIVE }}
transition={{ duration: 0.4, delay: 1 + i * 0.28 }}
@@ -449,7 +478,7 @@ const Summary: React.FC = () => (
rx={10}
fill={PAPER}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
{["found", "changed", "next"].map((label, s) => (
<g key={label}>
@@ -469,7 +498,7 @@ const Summary: React.FC = () => (
y1={84 + s * 62 + l * 11}
y2={84 + s * 62 + l * 11}
stroke={LINE}
strokeWidth={3}
strokeWidth={2}
strokeLinecap="round"
{...draw(0.35 + s * 0.6 + l * 0.12)}
/>
@@ -491,14 +520,21 @@ const Summary: React.FC = () => (
/** Rescue, step 4: the client decides how to continue. */
const Decide: React.FC = () => (
<g>
<circle cx={90} cy={150} r={26} fill={INK} />
<circle
cx={90}
cy={150}
r={26}
fill={TINT}
stroke={INK}
strokeWidth={FINE}
/>
<text
x={90}
y={154}
textAnchor="middle"
style={{
...MONO,
fill: PAPER,
fill: INK,
fontSize: "calc(var(--scene-label, 10px) - 1px)",
}}
>
@@ -514,7 +550,7 @@ const Decide: React.FC = () => (
d={`M 116 150 C 190 150 180 ${option.y} 250 ${option.y}`}
fill="none"
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
{...draw(0.3 + i * 0.4)}
/>
<Packet
@@ -561,9 +597,9 @@ const Browser: React.FC<{
rx={12}
fill={PAPER}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
<line x1={70} x2={410} y1={58} y2={58} stroke={INK} strokeWidth={1.5} />
<line x1={70} x2={410} y1={58} y2={58} stroke={INK} strokeWidth={FINE} />
{[0, 1, 2].map((d) => (
<circle key={d} cx={88 + d * 12} cy={44} r={3} fill={LINE} />
))}
@@ -617,6 +653,56 @@ const BLOCKS: readonly { x: number; y: number; w: number; h: number }[] = [
{ x: 298, y: 174, w: 94, h: 62 },
];
/** What a finished page holds: a headline, a button, and a line of text per card. */
const PageDetail: React.FC<{ delay: number }> = ({ delay }) => (
<motion.g {...appear(delay, 0)}>
<line
x1={102}
x2={206}
y1={118}
y2={118}
stroke={INK}
strokeWidth={3}
strokeLinecap="round"
/>
<line
x1={102}
x2={176}
y1={129}
y2={129}
stroke={INK}
strokeWidth={3}
strokeLinecap="round"
/>
<rect x={102} y={140} width={40} height={11} rx={5.5} fill={INK} />
{[BLOCKS[2], BLOCKS[3], BLOCKS[4], BLOCKS[5]].map(
(block) =>
block && (
<g key={`${block.x}-${block.y}`}>
<line
x1={block.x + 10}
x2={block.x + block.w - 24}
y1={block.y + block.h - 20}
y2={block.y + block.h - 20}
stroke={RIM}
strokeWidth={2}
strokeLinecap="round"
/>
<line
x1={block.x + 10}
x2={block.x + block.w - 44}
y1={block.y + block.h - 12}
y2={block.y + block.h - 12}
stroke={LINE}
strokeWidth={2}
strokeLinecap="round"
/>
</g>
),
)}
</motion.g>
);
/** Website, step 2: a first design as outlines. */
const Wireframe: React.FC = () => (
<g>
@@ -662,10 +748,14 @@ const Built: React.FC = () => (
height={b.h}
rx={5}
initial={{ fill: PAPER, stroke: LINE }}
animate={{ fill: i === 1 ? INK : SOFT, stroke: i === 1 ? INK : LINE }}
animate={{
fill: i === 1 ? TINT : SOFT,
stroke: i === 1 ? INK : LINE,
}}
transition={{ duration: 0.35, delay: 0.2 + i * 0.18 }}
/>
))}
<PageDetail delay={1.3} />
</Browser>
<motion.text
x={240}
@@ -691,10 +781,11 @@ const ChangeByEmail: React.FC = () => (
width={b.w}
height={b.h}
rx={5}
fill={i === 1 ? INK : SOFT}
fill={i === 1 ? TINT : SOFT}
stroke={i === 1 ? INK : LINE}
/>
))}
<PageDetail delay={0} />
<motion.rect
x={BLOCKS[4]!.x}
y={BLOCKS[4]!.y}
@@ -702,8 +793,6 @@ const ChangeByEmail: React.FC = () => (
height={BLOCKS[4]!.h}
rx={5}
fill={MARK}
stroke={INK}
strokeWidth={1.5}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.4, delay: 1.3 }}
@@ -718,20 +807,20 @@ const ChangeByEmail: React.FC = () => (
rx={5}
fill={PAPER}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
<path
d="M 16 190 l 23 16 l 23 -16"
fill="none"
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
</motion.g>
<motion.path
d="M 66 202 C 120 202 150 205 188 205"
fill="none"
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
markerEnd="url(#arrow)"
{...draw(0.6)}
/>
@@ -766,9 +855,9 @@ const Tool: React.FC<{ x: number; y: number; label: string }> = ({
width={112}
height={44}
rx={10}
fill={PAPER}
stroke={INK}
strokeWidth={1.5}
fill={SOFT}
stroke={RIM}
strokeWidth={FINE}
/>
<text x={x} y={y + 4} textAnchor="middle" style={{ ...MONO, fill: INK }}>
{label}
@@ -788,7 +877,7 @@ const ByHand: React.FC = () => (
d={`M ${tool.x + (tool.x < 240 ? 56 : -56)} ${tool.y} L ${tool.x < 240 ? 214 : 266} ${140 + (tool.y < 140 ? -12 : 12)}`}
fill="none"
stroke="#f59e0b"
strokeWidth={1.5}
strokeWidth={FINE}
strokeDasharray="3 5"
{...draw(0.3 + i * 0.25)}
/>
@@ -800,13 +889,13 @@ const ByHand: React.FC = () => (
r={13}
fill={PAPER}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
<path
d="M 218 172 Q 218 146 240 146 Q 262 146 262 172"
fill={PAPER}
stroke={INK}
strokeWidth={1.5}
strokeWidth={FINE}
/>
</motion.g>
<motion.text
@@ -837,7 +926,7 @@ const Wiring: React.FC = () => (
d={d}
fill="none"
stroke={INK}
strokeWidth={2}
strokeWidth={1.5}
{...draw(0.3 + i * 0.4)}
/>
))}
@@ -860,7 +949,7 @@ const Wiring: React.FC = () => (
const Running: React.FC = () => (
<g>
{WIRES.map((d) => (
<path key={d} d={d} fill="none" stroke={INK} strokeWidth={2} />
<path key={d} d={d} fill="none" stroke={INK} strokeWidth={1.5} />
))}
{[
{ from: [146, 80], to: [334, 80], delay: 0 },
@@ -870,10 +959,11 @@ const Running: React.FC = () => (
].map((dot) => (
<motion.circle
key={dot.delay}
r={6}
r={3.5}
fill={MARK}
stroke={INK}
strokeWidth={1.5}
stroke={MARK}
strokeOpacity={0.28}
strokeWidth={6}
initial={{ cx: dot.from[0], cy: dot.from[1], opacity: 0 }}
animate={{
cx: [dot.from[0]!, dot.to[0]!],
@@ -932,8 +1022,8 @@ export const PathScene: React.FC<{
viewBox="0 0 10 10"
refX="8"
refY="5"
markerWidth="7"
markerHeight="7"
markerWidth="5"
markerHeight="5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill={INK} />