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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run format:check
- run: npm run lint
- run: npm run typecheck
- run: npm run build
6 changes: 2 additions & 4 deletions .opencode/opencode.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
".opencode/plugins/graphify.js"
]
}
"plugin": [".opencode/plugins/graphify.js"]
}
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
graphify-out
package-lock.json
3 changes: 3 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"printWidth": 90
}
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st
When the user types `/graphify`, invoke the `skill` tool with `skill: "graphify"` before doing anything else.

Rules:

- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
- Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it.
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
683 changes: 21 additions & 662 deletions LICENSE

Large diffs are not rendered by default.

116 changes: 94 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,114 @@
# Flick
# Flick — Website

Marketing site for [Flick](https://github.com/imsosleepy/flick), a high-performance audiophile music player for Android.
Marketing site for [Flick](https://github.com/moss-apps/Flick), a bit-perfect
audiophile music player for Android.

## Stack
Vanilla TypeScript SPA with pushState routing, built with Vite, styled with
Tailwind CSS v4, animated with Motion, and prerendered to static HTML at build
time.

Vanilla TypeScript SPA — no framework. Built with Vite, styled with Tailwind CSS v4, animated with Motion.
## Pages

All routes are defined once in `src/routes.ts` — it drives navigation,
prerendering, SEO metadata and the sitemap.

| Route | Content |
| ----------------- | ----------------------------------------- |
| `/` | Hero, features, specs, contributors |
| `/downloads` | Release downloads, QR code, stats |
| `/release-notes` | Full release history from GitHub |
| `/supported-dacs` | USB DAC and DAP compatibility list |
| `/faq` | Frequently asked questions |
| `/community` | Contributing guide and project principles |
| `/about` | Project background and architecture docs |
| `/privacy` | Privacy policy |
| `/terms` | Terms of use |
| `/licenses` | Open source license inventory |

Unknown paths serve a prerendered, `noindex` 404 page. Platform errors fall
back to `public/500.html`.

## Getting started

```bash
npm install
npm run dev # dev server on :3000
npm run build # tsc + vite build + prerender
npm run preview # serve production build locally
npm run dev # http://localhost:3000
```

Copy `.env.example` to `.env` and set `GITHUB_TOKEN` to raise GitHub API rate limits (optional).
Optionally copy `.env.example` to `.env` and set `GITHUB_TOKEN` to raise GitHub
API rate limits during builds. The token is only used server-side (proxy and
build-time feed generation) and is never shipped to the client.

## How it works
## Scripts

Client-side routing via `pushState`. Three pages: Home, Downloads, Release Notes. All routes are prerendered at build time to static HTML for SEO. GitHub data (releases, contributors, stats) is fetched through a serverless proxy at `api/github-proxy.ts` with rate limiting and bot blocking.
| Script | What it does |
| ------------------------ | ------------------------------------------------------- |
| `npm run dev` | Vite dev server |
| `npm run build` | `tsc` then `vite build` (includes prerender plugin) |
| `npm run preview` | Serve the production build locally |
| `npm run typecheck` | `tsc --noEmit` |
| `npm run lint` | ESLint |
| `npm run format` | Prettier write |
| `npm run format:check` | Prettier check (used in CI) |
| `npm run licenses:sync` | Regenerate `src/generated/licenses.json` from lockfiles |
| `npm run licenses:check` | Verify the license inventory is in sync |

## Project structure
## How it works

```
src/
main.ts # routing, boot, meta
render.ts # route → HTML mapping
animations.ts # scroll-triggered Motion animations
style.css # Tailwind + Google Fonts
api/github.ts # GitHub API client, charts, markdown, tracking
components/ # navbar, hero, features, specs, downloads-page, release-notes-page, footer
main.ts Routing, boot, page-level init
render.ts Route -> page markup
routes.ts Route manifest (paths, SEO meta, sitemap)
site.ts Site-wide constants (origin, repo, contact)
structured-data.ts Per-page JSON-LD graph builder
animations.ts Motion in-view animations
style.css Tailwind v4 + self-hosted variable fonts
components/ Navbar, hero, features, specs, contributors,
downloads, release notes, legal/content pages, footer
api/github.ts GitHub API client (charts, markdown, releases)
generated/ licenses.json (generated, do not edit by hand)
api/
github-proxy.ts # Vercel serverless proxy for GitHub API
prerender.ts # Vite plugin for build-time SSR
vercel.json # headers, caching, SPA rewrites
github-proxy.ts Serverless proxy for the GitHub API
prerender.ts Vite plugin: static HTML for every route, sitemap,
Atom feed, per-page JSON-LD, 404 page
scripts/
sync-licenses.mjs License inventory generator
public/ Static assets, 500 page, robots.txt, security.txt
```

## Deploy
### Data and proxy

The site reads releases, contributors and commit data from GitHub through
`api/github-proxy.ts`. The proxy is GET-only, validates paths and query
parameters against allowlists, blocks known scrapers, applies a per-IP rate
limit and locks CORS to the production origin. Responses are cached in
`localStorage` for 10 minutes to keep API traffic low.

### Build-time generation

`prerender.ts` renders all routes to static HTML, injects per-page metadata and
JSON-LD, writes `dist/sitemap.xml`, generates an Atom feed at `/feed.xml` from
GitHub releases (falling back to a valid empty feed if the API is
unreachable), and emits `dist/404.html`.

## Deployment

Deployed on Vercel from `main`. `vercel.json` defines long-lived caching for
hashed assets and security headers (HSTS, CSP, COOP/CORP, Referrer-Policy,
Permissions-Policy) on every route. The GitHub proxy runs as a serverless
function. There is no SPA catch-all rewrite: every valid path is prerendered,
and anything else gets a real 404.

CI (`.github/workflows/ci.yml`) runs `format:check`, `lint`, `typecheck` and
`build` on every push and pull request.

## License

MIT — see [LICENSE](LICENSE) and the [/licenses](https://flick-player.site/licenses)
page for third-party notices.

## Security

Deployed on Vercel. Static assets cached for 1 year. The API proxy runs as a serverless function.
See [SECURITY.md](SECURITY.md) for the security policy and implemented
controls.
53 changes: 53 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Security Policy

Last updated: September 25, 2026

## Reporting a vulnerability

Please report suspected vulnerabilities privately:

- Email: [moss_apps@proton.me](mailto:moss_apps@proton.me)
- GitHub private advisory: https://github.com/moss-apps/Flick/security/advisories/new

Please do not open a public issue for security problems. You can expect an
acknowledgement within a few days. This policy also applies to the Flick
Android app; machine-readable contact details are published at
`/.well-known/security.txt`.

## Scope

- This website (`moss-apps/Flick-web`): static pages, prerender pipeline,
serverless GitHub proxy.
- The Flick Android app (`moss-apps/Flick`).

## Implemented controls

- **Security headers** on every response: `X-Content-Type-Options: nosniff`,
`X-Frame-Options: DENY`, `Referrer-Policy: strict-origin-when-cross-origin`,
`Permissions-Policy` (geolocation, microphone and camera disabled), HSTS
(2 years, `includeSubDomains`, `preload`), COOP/CORP `same-origin`.
- **Content Security Policy**: `default-src 'self'` with scripts, styles and
fonts restricted to the origin, `object-src 'none'`, `frame-ancestors
'none'`, `base-uri 'self'`. Markup contains no inline event handlers.
- **GitHub proxy**: GET only; method, path and query allowlists; known scraper
and bot user agents blocked; per-IP rate limit (60 requests/minute, 429 with
`Retry-After`); 8-second upstream timeout; CORS locked to
`https://flick-player.site`; optional `GITHUB_TOKEN` used server-side only.
- **Output handling**: all GitHub-sourced strings are HTML-escaped and URLs
are validated before rendering (contributor avatars, release assets, release
notes).
- **Privacy**: no analytics, no telemetry, no cookies, no third-party fonts.
GitHub API responses are cached in `localStorage` for 10 minutes.
- **Supply chain**: dependencies are locked by `package-lock.json`, installed
with `npm ci` in CI, and inventoried on the `/licenses` page.

## Known limitations

- The proxy rate limiter is in-memory and per serverless instance, so it is
best-effort and resets on cold starts. It is a courtesy layer, not an
edge-level DDoS defence (Vercel provides platform-level protection).
- There is no third-party error monitoring by design. Platform-level errors
are handled by Vercel's static `500.html` fallback.
- The Atom feed is generated at build time from the GitHub releases API; if
the API is unreachable during a build, the feed falls back to a valid,
empty feed.
143 changes: 0 additions & 143 deletions SECURITY_AUDIT.md

This file was deleted.

Loading
Loading