Skip to content
Open
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
5 changes: 4 additions & 1 deletion app/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link, useLocation } from "@remix-run/react";
import { SITE } from "~/utils/site-config";
import { ThemePicker } from "./ThemePicker";
import { SparkleText } from "./SparkleText";

export function TopNav({ current }: { current?: string }) {
const links = [
Expand Down Expand Up @@ -44,7 +45,9 @@ export function SiteHeader() {
<Link to="/">{SITE.name}</Link>
</p>
)}
<p className="site-tagline">{SITE.bio}</p>
<p className="site-tagline">
<SparkleText text={SITE.bio} />
</p>
<TopNav current={pathname} />
</header>
);
Expand Down
25 changes: 25 additions & 0 deletions app/components/SparkleText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Fragment } from "react";

// Any of these characters, typed inline in a body string, will render
// wrapped in a .sparkle span (theme-accent color). Surrounding text is
// left as-is.
const SPARKLES = ["✦", "✧", "✻", "✽", "✶", "✴", "✳"] as const;
const SPARKLE_RE = new RegExp(`([${SPARKLES.join("")}])`, "g");

export function SparkleText({ text }: { text: string }) {
if (!SPARKLES.some((s) => text.includes(s))) return <>{text}</>;
const parts = text.split(SPARKLE_RE);
return (
Comment on lines +9 to +12
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s no automated coverage validating that gets wrapped/styled (e.g., asserting a .sparkle span appears when content contains the glyph). Since this is a new rendering behavior used across multiple pages, it would be good to add a small Playwright assertion (or component-level test if you have one) to prevent regressions.

Copilot uses AI. Check for mistakes.
<>
{parts.map((part, i) =>
SPARKLES.includes(part as (typeof SPARKLES)[number]) ? (
<span key={i} className="sparkle" aria-hidden="true">
{part}
</span>
) : (
<Fragment key={i}>{part}</Fragment>
),
)}
</>
);
}
7 changes: 6 additions & 1 deletion app/routes/books.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MetaFunction } from "@vercel/remix";
import { BOOKS, SITE, SITE_URL } from "~/utils/site-config";
import { SparkleText } from "~/components/SparkleText";

export const meta: MetaFunction = () => [
{ title: `Books · ${SITE.name}` },
Expand Down Expand Up @@ -39,7 +40,11 @@ export default function Books() {
{", "}
<span className="book-author">{b.author}</span>
{b.rating && <Stars n={b.rating} />}
{b.note && <span className="book-note">{b.note}</span>}
{b.note && (
<span className="book-note">
<SparkleText text={b.note} />
</span>
)}
</div>
))}
</section>
Expand Down
3 changes: 2 additions & 1 deletion app/routes/interests.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MetaFunction } from "@vercel/remix";
import { INTERESTS, SITE, SITE_URL } from "~/utils/site-config";
import { SparkleText } from "~/components/SparkleText";

export const meta: MetaFunction = () => [
{ title: `Interests · ${SITE.name}` },
Expand All @@ -21,7 +22,7 @@ export default function Interests() {
{INTERESTS.map((it) => (
<section key={it.title}>
<h2>{it.title}</h2>
<p>{it.body}</p>
<p><SparkleText text={it.body} /></p>
</section>
))}
</main>
Expand Down
3 changes: 2 additions & 1 deletion app/routes/now.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { MetaFunction } from "@vercel/remix";
import { Link } from "@remix-run/react";
import { NOW, SITE, SITE_URL } from "~/utils/site-config";
import { SparkleText } from "~/components/SparkleText";

export const meta: MetaFunction = () => [
{ title: `Now · ${SITE.name}` },
Expand Down Expand Up @@ -29,7 +30,7 @@ export default function Now() {
<section key={s.heading}>
<h2>{s.heading}</h2>
<p>
{s.body}
<SparkleText text={s.body} />
{s.link && (
<>
{" "}
Expand Down
3 changes: 2 additions & 1 deletion app/routes/then.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { MetaFunction } from "@vercel/remix";
import { Link } from "@remix-run/react";
import { THEN, SITE, SITE_URL } from "~/utils/site-config";
import { SparkleText } from "~/components/SparkleText";

export const meta: MetaFunction = () => [
{ title: `Then · ${SITE.name}` },
Expand Down Expand Up @@ -41,7 +42,7 @@ export default function Then() {
<div key={s.heading} className="stack">
<h3>{s.heading}</h3>
<p>
{s.body}
<SparkleText text={s.body} />
{s.link && (
<>
{" "}
Expand Down
17 changes: 17 additions & 0 deletions app/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
--rule: #1c3a1c;
--link: #b0ff7d;
--link-visited: #70c040;
--sparkle: #d8ff55;
--grain: none;
}

Expand All @@ -29,6 +30,7 @@
--rule: #d8cfbc;
--link: #0b4aa2;
--link-visited: #6c3e9e;
--sparkle: #c9751c;
/* Chunkier, more visible grain: larger tile (400), lower baseFrequency
(bigger noise blobs), 3 octaves, higher alpha. Reads as real paper. */
--grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.35' numOctaves='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.32 0 0 0 0 0.22 0 0 0 0 0.14 0 0 0 0.28 0'/></filter><rect width='400' height='400' filter='url(%23n)'/></svg>");
Expand All @@ -42,6 +44,7 @@
--rule: #2a2520;
--link: #8cc0ff;
--link-visited: #c8a4d4;
--sparkle: #e8c67b;
--grain: none;
}

Expand All @@ -53,6 +56,7 @@
--rule: #3a2a18;
--link: #ffc870;
--link-visited: #c88030;
--sparkle: #ffe6a0;
--grain: none;
}

Expand All @@ -64,6 +68,7 @@
--rule: #334862;
--link: #9ed4ff;
--link-visited: #d4a8f0;
--sparkle: #ffd48a;
--grain: none;
}

Expand All @@ -75,6 +80,7 @@
--rule: #3a2a45;
--link: #e8b4f0;
--link-visited: #b890c2;
--sparkle: #ffd48a;
--grain: none;
}

Expand Down Expand Up @@ -315,6 +321,17 @@ footer {
padding: 0 1.25rem;
}

/* Sparkle accent: a theme-colored glyph for emphasis.
Write ✦ in any content string wrapped by <SparkleText> and it
gets this color automatically. */
.sparkle {
color: var(--sparkle);
font-size: 0.9em;
display: inline-block;
transform: translateY(-0.05em);
padding: 0 0.1em;
}

/* Theme picker (row of text buttons in the footer) */
.theme-picker {
margin: 0.5rem 0 0;
Expand Down