diff --git a/README.md b/README.md index a8752fb..75fe84a 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/public/template-previews/pare.jpg b/public/template-previews/pare.jpg deleted file mode 100644 index 9339052..0000000 Binary files a/public/template-previews/pare.jpg and /dev/null differ diff --git a/src/__tests__/exportPages.test.ts b/src/__tests__/exportPages.test.ts index 0147199..a81371f 100644 --- a/src/__tests__/exportPages.test.ts +++ b/src/__tests__/exportPages.test.ts @@ -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"); }); }); diff --git a/src/__tests__/lightPalette.test.ts b/src/__tests__/lightPalette.test.ts index 205e5e5..439bb24 100644 --- a/src/__tests__/lightPalette.test.ts +++ b/src/__tests__/lightPalette.test.ts @@ -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([]); }); }); diff --git a/src/lib/templates.ts b/src/lib/templates.ts index d266b0f..0f8e56d 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -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, - }, - ], - }, ];