Skip to content

Commit 9658446

Browse files
SBrophy-devSBrophy-dev
authored andcommitted
Ship the demo as a static site deployed to GitHub Pages
TypeCraft has no real server dependency - projects live in localStorage and fonts compile client-side - so the whole app now exports statically: - Preset base designs are fetched directly from the Fontsource CDN on jsDelivr as static WOFF1 files (CORS-enabled), replacing the /api/font-proxy route, which is removed - /editor/[projectId] and /preview/[shareId] become /editor?project= and /preview?share= so no dynamic segments remain - next.config gains STATIC_EXPORT / NEXT_PUBLIC_BASE_PATH switches; the starter-font fetch and share links are basePath-aware - A GitHub Actions workflow builds the export and deploys it to GitHub Pages on every push to main (enables Pages on first run) - README gains the live demo link and updated architecture notes
1 parent a1b6e8d commit 9658446

14 files changed

Lines changed: 150 additions & 109 deletions

File tree

.github/workflows/deploy-pages.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Deploy demo to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 22
26+
cache: npm
27+
28+
- run: npm ci
29+
30+
- name: Configure GitHub Pages
31+
id: pages
32+
uses: actions/configure-pages@v5
33+
with:
34+
# Create the Pages site on first run so no manual setup is needed.
35+
enablement: true
36+
37+
- name: Build static export
38+
run: npm run build
39+
env:
40+
STATIC_EXPORT: "1"
41+
# e.g. "/TypeCraft" for a project site — provided by configure-pages.
42+
NEXT_PUBLIC_BASE_PATH: ${{ steps.pages.outputs.base_path }}
43+
44+
- uses: actions/upload-pages-artifact@v3
45+
with:
46+
path: out
47+
48+
deploy:
49+
needs: build
50+
runs-on: ubuntu-latest
51+
environment:
52+
name: github-pages
53+
url: ${{ steps.deployment.outputs.page_url }}
54+
steps:
55+
- id: deployment
56+
uses: actions/deploy-pages@v4

README.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ A real browser-based type foundry. TypeCraft loads any font as **editable Bézie
99

1010
Everything you see is the actual font: every preview is rendered with a real, freshly-compiled `FontFace`, not a CSS approximation.
1111

12+
**▶ Try it now — live demo: https://sbrophy-dev.github.io/TypeCraft/**
13+
14+
No account, no install. Projects live in your browser's localStorage; nothing leaves your machine.
15+
1216
## How it works
1317

14-
1. **Start from real outlines.** Remix one of ~19 curated open-licensed designs (Inter, Playfair, Space Mono, Bebas Neue, Caveat…), upload your own TTF/OTF/WOFF, or open a blank canvas. Imported fonts are normalized to a 1000-UPM, Y-up outline model with consistent winding.
18+
1. **Start from real outlines.** Remix one of ~19 curated open-licensed designs (Inter, Playfair, Space Mono, Bebas Neue, Caveat…) fetched as static WOFFs from the Fontsource CDN, upload your own TTF/OTF/WOFF, or open a blank canvas. Imported fonts are normalized to a 1000-UPM, Y-up outline model with consistent winding.
1519
2. **Style with geometry, not tricks.** Each control performs a genuine operation on the vectors:
1620
- **Weight** offsets the outline along its normals (miter-limited, with self-intersection cleanup so heavy weights stay clean).
1721
- **Contrast** thins horizontal strokes relative to vertical stems.
1822
- **Width / x-Height / Slant** rescale, remap and shear the outlines.
1923
- **Rounding** replaces sharp corners with Bézier fillets; **Roughen** adds deterministic hand-drawn jitter; **Hollow** turns letters into outlined forms.
20-
3. **Redraw any glyph.** A full vector editor: drag anchors and handles, toggle smooth/corner points, insert and delete points, draw new contours with the pen tool, marquee-select, nudge with the keyboard, and drag the sidebearings — all over live metric guides with a ghost of the original and the styled result.
24+
3. **Redraw any glyph.** A full vector workstation: pen, rectangle and ellipse tools; smart snapping to points, metric guides and a unit grid; per-glyph undo/redo; flip / rotate / align operations; a contour clipboard that works across glyphs (turn a `d` into a `b`); numeric coordinates; and a trace-reference underlay for drawing over any other glyph or the bundled template — all over live metric guides with a ghost of the original and the styled result.
2125
4. **Export a real font.** Compile to OpenType in-browser and download **OTF + WOFF2 + a CSS `@font-face` kit + sample HTML**, zipped.
2226

2327
## Tech stack
@@ -47,17 +51,26 @@ npm run dev # http://localhost:3000
4751

4852
> **Tip:** don't run `npm run build` while `npm run dev` is live — they share the `.next` directory.
4953
54+
## Demo deployment
55+
56+
The app has no server dependencies (projects live in localStorage, fonts compile client-side, presets come from a CORS-enabled CDN), so it exports as a fully static site:
57+
58+
```bash
59+
STATIC_EXPORT=1 npm run build # writes ./out
60+
```
61+
62+
Every push to `main` builds and deploys the demo to **GitHub Pages** via [.github/workflows/deploy-pages.yml](.github/workflows/deploy-pages.yml). `NEXT_PUBLIC_BASE_PATH` handles the `/TypeCraft` subdirectory automatically.
63+
5064
## Project structure
5165

5266
```
5367
src/
5468
├── app/
5569
│ ├── page.tsx # Marketing home + live hero demo
5670
│ ├── dashboard/ # Project library + new-project flow
57-
│ ├── editor/[projectId]/ # The editor
58-
│ ├── preview/[shareId]/ # Read-only share view
59-
│ ├── billing/ # Pricing page
60-
│ └── api/font-proxy/ # Server proxy that fetches preset fonts as WOFF
71+
│ ├── editor/ # The editor (?project=<id>)
72+
│ ├── preview/ # Read-only share view (?share=<id>)
73+
│ └── billing/ # Pricing page
6174
├── components/
6275
│ ├── editor/
6376
│ │ ├── TypeCraftEditor.tsx # Editor shell: state, history, compile, views
@@ -74,6 +87,7 @@ src/
7487
├── transforms.ts # The StyleParams pipeline (applyStyle)
7588
├── font-model.ts # Parse a font binary → editable outlines
7689
├── compile.ts # Project → opentype.js Font + live FontFace
90+
├── accents.ts # Auto-composed accented Latin (é ñ ü ç …)
7791
├── use-compiled-font.ts # React hook: debounced compile → CSS family
7892
├── exporter.ts # OTF / WOFF2 / CSS kit / sample HTML → ZIP
7993
├── charset.ts # Character coverage + glyph names

next.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
import type { NextConfig } from "next";
22

3+
/**
4+
* TypeCraft runs entirely in the browser (projects live in localStorage and
5+
* fonts compile client-side), so the whole app can ship as a static export.
6+
*
7+
* - STATIC_EXPORT=1 build a fully static site into ./out (used by the
8+
* GitHub Pages deploy workflow; `next dev` unaffected).
9+
* - NEXT_PUBLIC_BASE_PATH subdirectory the site is served from, e.g.
10+
* "/TypeCraft" on GitHub Pages project sites. Also
11+
* read by client code that fetches public assets.
12+
*/
13+
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
14+
315
const nextConfig: NextConfig = {
416
reactStrictMode: true,
17+
...(process.env.STATIC_EXPORT ? { output: "export" as const, trailingSlash: true } : {}),
18+
...(basePath ? { basePath } : {}),
519
experimental: {
620
optimizePackageImports: ["lucide-react"]
721
},

src/app/api/font-proxy/route.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/app/dashboard/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default function DashboardPage() {
8888
</p>
8989
</div>
9090
<div className="project-actions">
91-
<Link className="button button-dark" href={`/editor/${project.id}`}>
91+
<Link className="button button-dark" href={`/editor?project=${project.id}`}>
9292
Open
9393
</Link>
9494
<button className="icon-button" type="button" aria-label="Duplicate" onClick={() => onDuplicate(project.id)}>

src/app/editor/[projectId]/page.tsx

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/app/editor/page.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
3+
import { Suspense } from "react";
4+
import { useSearchParams } from "next/navigation";
5+
import TypeCraftEditor from "@/components/editor/TypeCraftEditor";
6+
7+
/**
8+
* The editor addresses projects via ?project=<id> (instead of a dynamic
9+
* segment) so the whole app can be exported as a static site.
10+
*/
11+
function EditorRoute() {
12+
const params = useSearchParams();
13+
return <TypeCraftEditor projectId={params.get("project") ?? ""} />;
14+
}
15+
16+
export default function EditorPage() {
17+
return (
18+
<Suspense fallback={null}>
19+
<EditorRoute />
20+
</Suspense>
21+
);
22+
}

src/app/preview/[shareId]/page.tsx

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/app/preview/page.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
3+
import { Suspense } from "react";
4+
import { useSearchParams } from "next/navigation";
5+
import SharedPreview from "@/components/editor/SharedPreview";
6+
7+
/**
8+
* Shared previews address shares via ?share=<id> (instead of a dynamic
9+
* segment) so the whole app can be exported as a static site.
10+
*/
11+
function PreviewRoute() {
12+
const params = useSearchParams();
13+
return <SharedPreview shareId={params.get("share") ?? ""} />;
14+
}
15+
16+
export default function PreviewPage() {
17+
return (
18+
<Suspense fallback={null}>
19+
<PreviewRoute />
20+
</Suspense>
21+
);
22+
}

src/components/dashboard/NewProjectDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function NewProjectDialog({ onClose }: { onClose: () => void }) {
1717
const [blankName, setBlankName] = useState("My Typeface");
1818
const fileRef = useRef<HTMLInputElement>(null);
1919

20-
const open = (id: string) => router.push(`/editor/${id}`);
20+
const open = (id: string) => router.push(`/editor?project=${id}`);
2121

2222
async function pickPreset(preset: FontPreset) {
2323
setError(null);

0 commit comments

Comments
 (0)