Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A scroll-scrubbed site plays through a sequence of frames as the reader scrolls,
background moves with them instead of sitting still behind the text. ScrollCraft builds
those sites without WebGL, an animation library, or a render farm.

1. **Start from a template.** 24 finished sites, each with its own palette,
1. **Start from a template.** 23 finished sites, each with its own palette,
typography, pacing and copy. Or upload a video and use your own footage.
2. **Get frames.** The engine draws the frame sequence on a canvas in your browser and
maps it to scroll position.
Expand All @@ -37,7 +37,7 @@ one exception is the copy assistant below, which is off unless you configure a k
palette. Frames are drawn on canvas locally, with no API key and nothing to install.
- **Scroll engine.** Smooth canvas scrubbing with separate desktop and portrait frame sets,
so a phone gets a background shaped for it rather than a letterboxed one.
- **Template library** — 24 finished scroll sites across 17 categories, each with its own
- **Template library** — 23 finished scroll sites across 17 categories, each with its own
Google Fonts pairing and palette.
- **Plain HTML export.** No dependencies and no lock-in. The exported page scores 100 on
Lighthouse for performance, accessibility, best practices and SEO.
Expand Down
Binary file removed public/template-previews/pare.jpg
Binary file not shown.
10 changes: 8 additions & 2 deletions src/__tests__/exportPages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,14 @@ describe("the exported page is legible on a light palette too", () => {
expect(footer).not.toMatch(/opacity:\s*0\.\d+/);
});

it("exports the light template without falling back to a dark page", async () => {
const { body } = await exportSite({ template: "pare" });
it("paints a light palette's own ground, whenever one ships", async () => {
// No light template is in the catalogue today, so this exercises the path directly.
const { body } = await exportSite({
sections: [{ heading: "On a pale page", scrollHeight: 1000 }],
siteName: "Pale",
styleJson: JSON.stringify({ style: "geometric", colors: ["#1b3a8f", "#8a5a2b", "#eff2f6"] }),
themeJson: JSON.stringify({ ink: "#111823", ground: "#eff2f6" }),
});
expect(body.html).toContain("#eff2f6");
});
});
13 changes: 7 additions & 6 deletions src/__tests__/lightPalette.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@ describe("a light palette gets a light page", () => {
expect(baseFill(optsFor(["#28407e", "#6b2f2a", "#0a0d18"]), "#030308")).toBe("#030308");
});

it("changes nothing for any template that already existed", () => {
// The whole catalogue, minus the one light template this was built for: every one of
// them must still start from the exact fill it started from before.
it("changes nothing for any template in the catalogue", () => {
// Every template ships dark today, so none of them may move.
for (const template of TEMPLATES) {
if (template.slug === "pare") continue;
for (const dark of DARK_BASES) {
expect(baseFill(optsFor(template.colors), dark), `${template.slug} moved`).toBe(dark);
}
}
});

it("the catalogue has a light template at all, which it did not before", () => {
it("leaves the catalogue dark until a light template is designed to look it", () => {
// Pare was reverted: a light palette with this geometry paints a page whose mean
// brightness is 237 and whose background barely moves, which reads as an unstyled
// page rather than a quiet one. The engine can do light; the design has to earn it.
const light = TEMPLATES.filter((t) => baseFill(optsFor(t.colors), "#030308") !== "#030308");
expect(light.map((t) => t.slug)).toContain("pare");
expect(light.map((t) => t.slug)).toEqual([]);
});
});
58 changes: 0 additions & 58 deletions src/lib/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1566,62 +1566,4 @@ export const TEMPLATES: Template[] = [
},
],
},
{
slug: "pare",
name: "Pare",
tagline: "An engineer who makes slow databases fast, on a page that is not dark",
category: "Portfolio",
tags: ["Light", "Technical", "Consulting"],
style: "geometric",
colors: ["#1b3a8f", "#8a5a2b", "#eff2f6"],
gradient: "from-slate-300 via-stone-300 to-slate-100",
theme: { fontDisplay: "JetBrains Mono", fontBody: "Public Sans", scale: "compact", displayWeight: 700, displayTracking: -0.03, ink: "#111823", muted: "rgba(17,24,35,0.72)", accent: "#1d4ed8", accentText: "#17347f", ground: "#eff2f6", radius: 4 },
sections: [
{
kind: "statement", layout: "center", reveal: "mask",
eyebrow: "Independent, booked by the fortnight",
heading: "Your database is not slow. One query is.",
ctaLabel: "See what that looks like", ctaHref: "#section-2",
scrollHeight: 1300,
},
{
layout: "left", reveal: "stagger",
eyebrow: "The usual shape of it",
heading: "A report that took forty seconds now takes three hundred milliseconds",
body: "Nine times out of ten it is one query, one missing index and one ORM call in a loop. I read the explain plans, fix the three that matter, and leave the rest alone.",
scrollHeight: 1400,
},
{
layout: "right", reveal: "fade",
eyebrow: "How I work",
heading: "Two weeks, on your branch, with your team watching",
body: "No access to production without someone from your side in the room. Every change arrives as a pull request with the before and after timings in the description, so your engineers can argue with it.",
scrollHeight: 1400,
},
{ kind: "spacer", scrollHeight: 600 },
{
layout: "left", reveal: "stagger",
eyebrow: "What you keep",
heading: "The explain plans, not just the fix",
body: "You get a short document per query: what it was doing, why it was slow, what changed, and how to spot the same pattern again. The point is that you do not need me the second time.",
scrollHeight: 1400,
},
{
layout: "right", reveal: "fade",
eyebrow: "What I turn down",
heading: "Greenfield, migrations and rewrites",
body: "Starting from nothing is somebody else's work, and a rewrite is rarely the cheapest way out of a slow query. If that is what you need I will say so in the first call rather than the fourth week.",
scrollHeight: 1300,
},
{ kind: "spacer", scrollHeight: 500 },
{
layout: "center", reveal: "fade",
eyebrow: "Next opening",
heading: "Send me the slow query.",
body: "Paste the statement and its explain plan. I will tell you whether it is worth two weeks of my time or twenty minutes of yours, for free, within a day.",
ctaLabel: "Send a query", ctaHref: "#contact",
scrollHeight: 1200,
},
],
},
];
Loading