A catalog system for underground electronic music.
SIGIL.ZERO operates as a record label and release platform for dark, underground, and experimental electronic dance music. The label organizes its catalog through a series structure: modular collections that group releases by concept, aesthetic, or sonic territory.
This is not a streaming service. It is an index. A map of recorded transmissions.
The source code for the SIGIL.ZERO website. A static site generator built to serialize markdown content into structured release catalogs, artist profiles, and mixtape archives.
Content is stored as markdown files with YAML frontmatter. The system validates schemas, filters by metadata, and renders catalog pages with search and filter capabilities.
All content lives in /content. All logic lives in /src. All output is static HTML.
- Next.js 16 with static export and Turbopack
- React 19 for UI components
- TypeScript for type safety across content schemas and application logic
- Zod for runtime schema validation
- Tailwind CSS for styling
- Vitest for unit and integration testing
- Playwright for end-to-end testing
- GitHub Actions for continuous integration
Content processing uses gray-matter for frontmatter parsing, remark and rehype for markdown transformation.
SIGIL.ZERO uses a browser-side tracker at /public/assets/js/tracking.js with a GTM-first pipeline.
tracking.jsis vendor-agnostic and only pushes normalized objects intowindow.dataLayer.- Google Tag Manager (GTM) is installed once in the root app shell.
- GA4 and Meta are configured in GTM, not in application code.
- No direct
gtag()orfbq()calls exist in the app code.
This keeps analytics implementation centralized and safer to evolve:
- Application code emits one stable event contract.
- Tag vendors are configured in GTM without redeploying app code.
- Event governance, filtering, and mapping live in one place.
- Privacy controls remain inside a single normalization layer.
- GTM Container ID:
GTM-TLP35C5T - GA4 Measurement ID (configured in GTM):
G-K5W8ENE0DH
All custom events must push with:
event: "sigilzero_event"
Core contract:
entityactiontarget
Events missing any of those three required fields are blocked and not pushed.
tracking.js emits a flat payload (no arrays/objects) and allows these keys:
evententityactiontargetplatformpage_typepage_pathpage_titlerelease_titlerelease_catalog_idartist_nametrack_titlecta_typesectioncomponentlink_urllink_textdestination_domainis_externaldebug_source
Enable debug mode either way:
- Set local storage:
localStorage.setItem("sigilzero_debug_tracking", "true") - Or use URL param:
?debug_tracking=true
When enabled, events include:
debug_source: "tracking.js"
And helper methods are available in the console:
window.SigilZeroTracking.debugStatus()window.SigilZeroTracking.testEvent()window.SigilZeroTracking.getConsoleTestExamples()
- Open GTM container
GTM-TLP35C5T. - Create a Custom Event trigger where
Event name = sigilzero_event. - Create Data Layer Variables for fields you need (for example
entity,action,target,platform,page_type,link_url). - Attach GA4/Meta tags to the custom event trigger.
- Use GTM Preview to validate payload values before publishing.
- In GTM, create a GA4 Configuration tag using measurement ID
G-K5W8ENE0DH. - Create a GA4 Event tag triggered by
sigilzero_event. - Map Data Layer Variables to GA4 event parameters.
- Validate in GTM Preview, then GA4 DebugView.
- Add a Meta Pixel tag in GTM (no direct pixel script in app code).
- Trigger from
sigilzero_event(or filtered subsets). - Map only approved, non-PII fields.
- Validate in Meta Test Events.
Embed interactions (Spotify/SoundCloud) push:
entity: "embed"action: "play_intent"target: "spotify_embed"ortarget: "soundcloud_embed"
Embed events are deduped with a longer window (EMBED_DEDUPE_WINDOW_MS = 2000) to prevent duplicate pushes from iframe focus/blur behavior.
tracking.js applies defensive sanitization:
- Blocks email-like and phone-like free-text values.
- Redacts
mailto:andtel:links (link_url: "redacted",target: "contact_link"). - Blocks nested objects/arrays from payload output.
- Drops undefined/null values.
- Only
http/httpsURLs are allowed for normal tracked links. - Querystrings are stripped from
link_url. - Hash fragments are stripped from
link_url. - External domain extraction is preserved for external links only.
- Valid commerce URLs with numeric path IDs are preserved.
Notes:
- Query parameter stripping is intentional to avoid analytics noise and accidental parameter leakage.
mailto:andtel:are never sent as raw URLs.
- Run
npm run dev. - Open the site and DevTools console.
- Confirm
window.dataLayerexists and receivessigilzero_eventpayloads. - Exercise page views, release/artist/social/stream/store links, and embed interactions.
- Confirm payloads stay flat and contain no undefined values.
- Start GTM Preview for
GTM-TLP35C5T. - Navigate core routes (
/,/releases, release detail, artist detail). - Trigger representative interactions (stream/store/social/embed).
- Confirm each event appears as
sigilzero_event. - Verify mapped variables and publish after validation.
- Keep GTM Preview active.
- Open GA4 DebugView.
- Trigger key interactions and confirm receipt.
- Validate expected parameters (
entity,action,target, page context, link metadata). - Confirm no unexpected PII-like parameter values appear.
Install dependencies:
npm installRun the development server:
npm run devThe site will be available at http://localhost:3000.
Add new content to these directories:
/content/releases— Official releases/content/mixtapes— DJ sets and mixes/content/artists— Artist profiles/content/series— Series definitions
Global site data lives in /data:
/data/label.yml— Label metadata/data/links.yml— External link definitions/data/series.yml— Series registry
Each markdown file requires specific frontmatter fields. Run validation:
npm run check-frontmatterAdditional validators:
# Fast local validation (frontmatter + mixtapes)
npm run validate:quick
# Full validation (frontmatter + all content types)
npm run validate:contentRun unit and integration tests:
npm run test # Watch mode
npm run test:run # Single run
npm run test:ui # Browser-based UI
npm run test:coverage # With coverage reportRun end-to-end tests:
npm run e2e # Headless
npm run e2e:ui # Interactive mode
npm run e2e:headed # With browser visible
npm run e2e:debug # Debug mode
npm run e2e:report # Show last Playwright reportThis repository uses Husky to enforce validation and tests during development:
- Pre-commit: runs frontmatter + mixtape validation.
- Pre-push: runs full content validation and unit tests.
- Pre-push E2E: optional, enabled with
RUN_E2E_ON_PUSH=1.
Default pre-push checks match:
npm run prepush:localRun all checks (including E2E) manually before larger merges/releases:
npm run prepush:full
# or
RUN_E2E_ON_PUSH=1 git pushBypass hooks only if necessary:
git commit --no-verify -m "message"The site deploys as static HTML to GitHub Pages.
Build the production site:
npm run buildOutput is written to /docs. The build process:
- Validates all markdown frontmatter
- Generates static pages for all routes
- Copies output to
/docsfor GitHub Pages - Preserves CNAME for custom domain
Push to the main branch to trigger automatic deployment via GitHub Actions.
CI pipeline runs on push/PR as three parallel jobs:
- Unit & Integration Tests — runs
check-frontmatter,test:run, andtest:coverage - E2E Tests (Playwright) — runs the full Playwright suite against a built server
- Build Verification — runs
npm run buildand confirmsdocs/index.htmlwas produced
All three jobs must pass for the CI status check to succeed.
GitHub Actions policy in this repository:
- Workflows use
actions/checkout@v5,actions/setup-node@v5, andactions/upload-artifact@v7. - Build/test runtime is pinned via
setup-nodeto Node22for Next.js stability. - Workflows set
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=trueto proactively validate JavaScript action runtime compatibility.
Version 1.0 publicly released.
The catalog system is operational. Testing infrastructure is complete. Content added.
This system prioritizes content over presentation. Releases are data. The interface is an access layer.
The architecture enforces constraints:
- Static output only. No server, no database.
- Markdown as the source of truth. No CMS.
- Type-safe schemas. Invalid content fails the build.
- Comprehensive test coverage. Behavior is verified.
Speed and simplicity over feature accumulation.
- This repository’s source code is released under the MIT License.
- Brand identity, artwork, and site content are excluded and remain proprietary.