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
6 changes: 3 additions & 3 deletions .claude/skills/frontend-quality/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ The product is **reading long-form fiction and essays on a phone, often at night

## 2. Performance (mobile-first static)

- Astro ships **zero client JS** by default -- keep it that way. Any new `<script>` or client island must justify itself; this is a reading site, not an app.
- Astro ships **no framework runtime** -- client JS is limited to the small inline reader scripts (progress, preferences, nav, TTS). Any NEW `<script>` must justify itself against the reading experience; this is a reading site, not an app.
- **Fonts** are the main weight: self-hosted Eczar + Literata via Fontsource (`@fontsource-variable/*/index.css`). Only import weights actually used; never add a Google Fonts CDN link.
- **LCP** is the hero heading (text) on the homepage and the first paragraph on a chapter -- text LCP is fast; don't introduce a large hero image without `loading="eager"` + width/height.
- **CLS**: reserve space. Skeleton rows have fixed heights; any future image needs explicit `width`/`height` or an aspect-ratio box.
- **Motion**: the `rise` and `shimmer` animations use transform/opacity/background-position only (compositor-friendly). Do not animate layout props (width/height/top). Motion is intentional here -- do NOT add `prefers-reduced-motion` gating (house rule).
- **Motion**: the `rise` and `shimmer` animations use transform/opacity/background-position only (compositor-friendly). Do not animate layout props (width/height/top). Every animation MUST have a `prefers-reduced-motion` path -- global.css freezes entrances, shimmer, and the ambient glow when the OS asks (WCAG 2.3.3).
- Tools: `pnpm build` prints per-route output; watch for unexpected JS. Verify a real mobile viewport via `preview_*`.

## 3. SEO
Expand All @@ -56,6 +56,6 @@ The product is **reading long-form fiction and essays on a phone, often at night

- Mobile-first; most readers are on phones.
- No em/en dash characters anywhere (chat, code, copy, metadata) -- `--` or `-`.
- Motion stays visible; never add `prefers-reduced-motion` or tone down animation.
- Motion is visible by default AND fully disabled under `prefers-reduced-motion` -- both paths ship together, always.
- Internal links go through `withBase()`; the site lives at a subpath (`/sagas`).
- Chapter prose is Sagar's voice -- never "fix" it here; this gate is about the shell around the words, not the words.
2 changes: 1 addition & 1 deletion .claude/skills/outline-book/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Turn a book idea into a structural outline with promises, arcs, and

# Outline a book

Produce a working outline for a book in `src/content/books/<slug>/`, stored in the book folder as `outline.md` (not a content collection file, so prefix with `_`: `_outline.md` is ignored by the glob patterns).
Produce a working outline for a book in `src/content/books/<slug>/`, stored in the book folder as `_outline.md` (the `_` prefix keeps it out of the content-collection globs).

## Preparation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/revise-chapter/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Read `references/revision.md` and `references/prose-style.md` from the `writing-
## Status transitions

- After a full ladder run: suggest `draft -> revising` or `revising -> final`.
- `final` + ready to announce = set `publishedOn` (this is what puts it in RSS); confirm the date with Sagar before setting.
- Publishing (`publishedOn`) is the `publish-chapter` skill's job, never this one's -- its gates (continuity, fanfic checks, mechanical sweep) must not be bypassed by an edit pass.

## Rules

Expand Down
19 changes: 19 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Enforce LF on all text files so Prettier (endOfLine: "lf") agrees with the
# working tree on every platform. Without this, Windows checkouts get CRLF
# via core.autocrlf and the format check fails on every file.

* text=auto eol=lf

# Keep binary assets binary -- no normalization, no diffs.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.avif binary
*.ico binary
*.pdf binary
*.woff binary
*.woff2 binary
*.mp3 binary
*.m4a binary
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

# Least-privilege default: CI only reads the repo to check and build.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
quality:
name: format, verify, check, build and smoke test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # full history so per-chapter writing dates build

- uses: pnpm/action-setup@v4
# version comes from packageManager in package.json

- uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Format check
run: pnpm format:check

- name: Content invariants
run: pnpm verify:content

- name: Astro check
run: pnpm check

- name: Build
run: pnpm build

- name: Install Chromium
run: pnpm exec playwright install --with-deps chromium

- name: Browser smoke tests
run: pnpm test:e2e
12 changes: 9 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ on:
branches: [main]
workflow_dispatch:

# Default to read-only; the deploy job elevates only what Pages needs.
permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
Expand All @@ -17,17 +16,24 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # full history so per-chapter writing dates build
- uses: withastro/action@v6
with:
package-manager: pnpm@latest
package-manager: pnpm@11.10.0

deploy:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ pnpm-debug.log*

# graphify knowledge graph artifacts
graphify-out/

# playwright artifacts
test-results/
playwright-report/
.playwright-mcp/
19 changes: 11 additions & 8 deletions .impeccable.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@

## Users

Readers of serialized fiction and essays, mostly on phones, often at night. The job: check what's new on the shelf, then read a chapter comfortably. Sagar himself is the second user: the site must make unfinished work feel presentable, not embarrassing.
Readers of serialized fiction and essays, mostly on phones, often at night. The job: check what's new on the shelf, then read a chapter comfortably. Sagar himself is the second user: the site must make unfinished work feel presentable, not embarrassing -- including while the shelves are still empty.

## Brand Personality

Candlelit, patient, bookish. A private library that happens to have its door open. Not a startup, not a blog, not an ebook store.

## Aesthetic Direction

- Dark-first (reading at night), light theme honored via prefers-color-scheme with light-dark().
- Warm amber-on-ink OKLCH palette. Neutrals tinted toward candle gold. No cyan, no purple gradients, no glow.
- Type: Eczar (display, Rosetta/Vaibhav Singh) + Literata (body, built for long-form e-reading). Both variable, self-hosted via Fontsource.
- Dark-first (reading at night), light theme honored via prefers-color-scheme with light-dark(); a forced sepia theme for long reading.
- Warm amber-on-ink OKLCH palette. Neutrals tinted toward candle gold. No cyan, no purple gradients.
- Depth flows through the warm-ink elevation ladder (--shadow-hairline, --shadow-e1..e4 in global.css), never raw black shadows.
- Atmosphere: a fixed ambient candle-glow backdrop (dot lattice + drifting radial glow + vignette) and an SVG paper-grain layer. Both decorative, both pointer-events: none.
- Glass surfaces are used deliberately on chrome and cards -- translucent, candle-tinted, subtle. Restraint over slop.
- Type: Eczar (display) + Literata (body, built for long-form e-reading). Both variable, self-hosted via Fontsource.
- Reading measure ~65ch, generous line-height, drop cap on chapter openers.
- Motion visible: staggered rise on page load, smooth deceleration easing. Never add prefers-reduced-motion.
- Motion is visible but always honors prefers-reduced-motion: entrances become instant, ambient drift and shimmer freeze. Never ship motion without the reduced path.

## Design Principles

1. The chapter is the product. Everything else is a hallway to it.
2. Mobile-first always; most readers are on phones.
3. Honest status over polish theater: every book shows drafting/ongoing/hiatus plainly.
2. Mobile-first always; most readers are on phones. No horizontal overflow at 320px, ever.
3. Honest status over polish theater: every book shows drafting/ongoing/hiatus plainly, and empty shelves say plainly that the first book is coming.
4. One accent, used rarely. Quiet surfaces, loud words.
5. No AI-tell patterns: no side-stripe borders, no gradient text, no glassmorphism, no identical card grids.
5. Accessibility is part of the aesthetic: skip link, focus rings, accessible names, reduced motion. A beautiful page nobody can operate is a failed page.
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"plugins": ["prettier-plugin-astro"],
"singleQuote": true,
"endOfLine": "lf",
"overrides": [
{
"files": "*.astro",
Expand Down
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Changelog

All notable changes to sagas.

## [0.2.0] - 2026-07-15

Zero-book launch: the placeholder seed books are gone, the shelves open
honestly empty, and every finding from the full repo audit is fixed.

### Added

- Ambient candlelight backdrop (dot lattice, drifting radial glow, edge
vignette) and an SVG paper-grain layer, both decorative and fully inert
(patterns adapted from the portfolio-react and kalchar design systems).
- Tiered design tokens with a warm-ink elevation ladder
(`--shadow-hairline`, `--shadow-e1..e4`) replacing ad-hoc shadows.
- Tilted-plate hero and crafted per-shelf empty states, so a zero-book
library reads as intent instead of absence.
- "How this library works" colophon on the homepage.
- Skip-to-content link, sticky-header scroll padding, and accessible names
for the text-size buttons and the read-aloud toggle.
- `Book` and `Chapter` JSON-LD, `article:published_time`, and excerpt-based
chapter meta descriptions; RSS items now carry descriptions.
- Content schema hardening: positive-integer `order`, https/relative-only
media URLs, `universe` required on the fanfic shelf.
- `pnpm verify:content`: cross-file invariants (orphan chapters, duplicate
orders, filename-prefix mismatches, future/missing `publishedOn`).
- Playwright smoke suite (desktop, mobile, 320px) covering the fixed
regressions; reader tests self-activate once the first chapter lands.
- CI quality gate on every PR: frozen install, format check, content
verification, `astro check`, build, browser tests.
- `.gitattributes` LF policy and a pinned `packageManager`.
- Authoring contract documented in `src/content/books/README.md`.

### Changed

- Reading progress persists only after real engagement and is debounced;
the saved position is read before anything writes, so the resume offer
survives reloads (previously the load path zeroed it out).
- Chapter shortcuts (arrows / h,l) no longer fire from focused links,
buttons, or form controls, and respect all modifier keys.
- All motion honors `prefers-reduced-motion`; the anti-reduced-motion house
rule in `.impeccable.md` and the frontend-quality skill is reversed.
- `[hidden]` now always computes to `display: none`, fixing the always-open
reader/TTS panels, the premature stop button, and the empty
continue-reading card.
- Grid tracks use `minmax(min(100%, NNrem), 1fr)`, removing horizontal
overflow at 320px viewports.
- Git writing history batches into one `git log --name-only` pass per build
instead of one process per chapter (rename tracking via `--follow` is
dropped; renames restart a chapter's visible history).
- Markdown pipeline moved to `satteri({ features: { smartPunctuation:
false } })`, clearing the deprecated `markdown.smartypants` flag; schema
imports moved to `astro/zod`, clearing all 22 deprecation hints.
- Deploy workflow hardened: job-scoped least-privilege permissions and a
pinned pnpm version instead of `pnpm@latest`.
- Inline JSON payloads (`readingMap`, JSON-LD) escape `<` to prevent
script-block breakout from content-controlled strings.
- Favicon replaced with the flame brand mark used in the masthead.
- `revise-chapter` can no longer set `publishedOn` (publishing stays behind
the `publish-chapter` gates); `outline-book` names `_outline.md`
consistently.
- README, CLAUDE.md, and `.impeccable.md` rewritten to describe the actual
product (reader platform, tests, glass-and-grain aesthetic, zero-book
launch).

### Removed

- The three placeholder seed books and the sample chapter. The library now
launches genuinely empty; templates and pipelines stay ready for the
first real book.

## [0.1.0] - 2026-07-12

Everything up to the reader-platform release, previously unversioned:

- Astro 7 static site: three shelves as content collections, book pages,
chapter reader, RSS feed, GitHub Pages deploy (#1, #2).
- Shared visual system: status pills, skeletons, glass chrome, candlelit
OKLCH palette (#3).
- SEO axis: sitemap, robots.txt, canonical/OG/Twitter tags, WebSite
JSON-LD; frontend-quality gate (#4).
- Formats capability: optional per-book/per-chapter PDF and audio, inline
players, format badges; reading-time estimates; glass theme (#5).
- Two-tier responsive width system for desktop (#6).
- Read-aloud TTS, styled RSS feed, elevated masthead (#7).
- Reader platform: preferences, progress, keyboard/swipe navigation,
richer TTS with media-session keys, git writing history (#8).
- Writing-craft skill library: 18 fact-checked reference modules and the
authoring workflow skills.
25 changes: 15 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@

## Project

sagas -- a living library of unfinished books. Original fantasy, fan fiction, and perspective essays by Sagar, written in the open one chapter at a time. Frontend-only static site; downloads and audiobooks may come later.
sagas -- a living library of unfinished books. Original fantasy, fan fiction, and perspective essays by Sagar, written in the open one chapter at a time. Static site with a small client-side reader platform (preferences, progress, TTS); no backend, everything in localStorage. Launches with zero books -- the shelves show crafted empty states until real writing lands.

Deploys to GitHub Pages at https://sagargupta.online/sagas (project-site subpath, matching every other project repo in the workspace).

## Stack

- **Language**: TypeScript (strict)
- **Framework**: Astro 7, content collections, zero client JS
- **Framework**: Astro 7, content collections; inline client scripts power the chapter reader (progress, preferences, keyboard/swipe nav, speech synthesis)
- **Database**: none -- Markdown files in `src/content/` are the data
- **Package manager**: pnpm
- **Deploy target**: GitHub Pages via `.github/workflows/deploy.yml`
- **Package manager**: pnpm (pinned via `packageManager`)
- **Deploy target**: GitHub Pages via `.github/workflows/deploy.yml`; PR quality gate in `.github/workflows/ci.yml`

## Run

Expand All @@ -34,11 +34,16 @@ pnpm build
## Test

```
pnpm check
pnpm check # types + schema
pnpm verify:content # cross-file content invariants
pnpm format:check # prettier (LF everywhere; .gitattributes enforces it)
pnpm build
pnpm test:e2e # Playwright smoke tests (desktop / mobile / 320px)
```

No test framework yet; `astro check` + a clean build is the bar.
CI runs all of these on every PR. The reader regression tests in
`tests/e2e/reader.spec.ts` skip themselves while the library has zero
chapters and activate automatically when the first chapter lands.

## Entry points

Expand All @@ -61,9 +66,9 @@ No test framework yet; `astro check` + a clean build is the bar.
- Three shelves: `fantasy`, `fanfic`, `essays`. Essay collections are books; each essay is a chapter.
- Book status: `ideation | drafting | ongoing | hiatus | complete`. Chapter status: `draft | revising | final`.
- Chapters need `order` (number) and get into RSS only once `publishedOn` is set.
- Fan fiction is free and non-commercial, always. `universe` frontmatter names the source world.
- The three seed books tagged `placeholder` exist to prove routes; replace them as real writing lands.
- Per-book working files use a `_` prefix (`_outline.md`, `_bible.md`) so the collection globs ignore them.
- Fan fiction is free and non-commercial, always. `universe` frontmatter is required on the fanfic shelf (schema-enforced).
- The library launches with zero books; shelves render crafted empty states. `src/content/books/README.md` documents the authoring contract.
- Per-book working files use a `_` prefix (`_outline.md`, `_bible.md`) so the collection globs ignore them. They are visible in the public repo -- keep spoilers out or the repo private-drafted.

## Skills (project-level)

Expand All @@ -89,7 +94,7 @@ Invoke via the Skill tool when the trigger matches. The craft knowledge base is

## Gotchas

- `smartypants: false` in `astro.config.mjs` -- Markdown `--` must NOT become en-dashes (dash rule applies to prose too).
- `markdown.processor: satteri({ features: { smartPunctuation: false } })` in `astro.config.mjs` -- Markdown `--` must NOT become en-dashes (dash rule applies to prose too). The old `smartypants` flag is deprecated in Astro 7.
- pnpm 11 needs `allowBuilds` in `pnpm-workspace.yaml` for esbuild/sharp; without it installs fail.
- Fontsource imports must point at `index.css` explicitly or `astro check` fails (no types on the bare entry).
- Fonts are self-hosted via Fontsource (Eczar display, Literata body). No Google Fonts CDN.
Expand Down
Loading
Loading