From ff9242014e746fb3c200ccfaae5b2ad6df650e07 Mon Sep 17 00:00:00 2001 From: keirsalterego Date: Sat, 23 May 2026 23:55:30 +0530 Subject: [PATCH 1/2] feat: mdBook documentation site with Vyrox brand theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the hand-rolled static index.html with a proper mdBook site built from the existing top-level Markdown chapters. The top-level files stay canonical; scripts/build.sh syncs them into src/ before mdbook compiles the site. Brand styling per product.md: - Surfaces: light = bone palette (#F2EAD8 on #0E0A05), dark = void palette (#050505 surface, #ECE3CC text), navy preset selected as the dark theme so mdBook picks it automatically. - Accent: ember (#E8462E in light, #FF6A3D in dark) on links, active sidebar items, selection background, and the in-page focus rule. - Typography: Fraunces for display headings (variable opsz/SOFT/WONK axes), Geist for body text and tables, JetBrains Mono for code and SUMMARY part titles. All three load from Google Fonts via theme/head.hbs with preconnect + display=swap. - Readable scale: 16.5px body / 1.65 line-height / 72ch max line length on paragraphs. Headings tighten the line-height and use a subtle border-bottom on h2 so they read as section dividers without shouting. Files: - book.toml mdBook config, additional CSS, edit URL pattern - src/SUMMARY.md four-part chapter order (Getting started, Architecture, Reference, Project) - theme/head.hbs Google Fonts preconnect + link, meta theme-color - theme/css/variables.css palette overrides for .light and .navy - theme/css/typography.css font families, scale, blockquote, tables - scripts/build.sh syncs top-level โ†’ src/, fetches mdbook if missing, builds, copies _headers/_redirects into book/ - _headers CSP with Google Fonts allow, HSTS, frame-deny, cache rules - _redirects friendly aliases (/architecture, /api, ...) + legacy SETUP_GUIDE redirect to QUICKSTART - DEPLOY_CLOUDFLARE.md deployment reference: settings, troubleshooting Removed: - index.html (the legacy single-page site). The mdBook output replaces it. Confirmed locally: - Cold build from clean checkout: 11 chapters synced, mdbook compiles, _headers and _redirects copied into book/. - book/index.html carries the Google Fonts link and references both fingerprinted theme CSS files. --- .gitignore | 13 + DEPLOY_CLOUDFLARE.md | 112 +++++ _headers | 33 ++ _redirects | 29 ++ book.toml | 63 +++ index.html | 1005 -------------------------------------- justfile | 191 +++----- package.json | 15 +- scripts/build.sh | 134 +++++ src/SUMMARY.md | 25 + theme/css/typography.css | 202 ++++++++ theme/css/variables.css | 166 +++++++ theme/head.hbs | 25 + 13 files changed, 869 insertions(+), 1144 deletions(-) create mode 100644 DEPLOY_CLOUDFLARE.md create mode 100644 _headers create mode 100644 _redirects create mode 100644 book.toml delete mode 100644 index.html create mode 100755 scripts/build.sh create mode 100644 src/SUMMARY.md create mode 100644 theme/css/typography.css create mode 100644 theme/css/variables.css create mode 100644 theme/head.hbs diff --git a/.gitignore b/.gitignore index 453533d..2ae698b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,16 @@ .vscode/ .DS_Store Thumbs.db + +# mdBook output. Cloudflare Pages builds it on demand from src/. +book/ + +# Local mdBook binary cache if a contributor installs into the repo. +.mdbook-bin/ + +# Chapter copies in src/ are generated by scripts/build.sh from the +# canonical top-level Markdown files. They exist locally after the +# first build, but they are not source-of-truth and should not be +# committed. The only file in src/ that is tracked is SUMMARY.md. +src/*.md +!src/SUMMARY.md diff --git a/DEPLOY_CLOUDFLARE.md b/DEPLOY_CLOUDFLARE.md new file mode 100644 index 0000000..16df5ac --- /dev/null +++ b/DEPLOY_CLOUDFLARE.md @@ -0,0 +1,112 @@ +# Deploying to Cloudflare Pages + +This is the exact configuration the documentation site uses on +Cloudflare Pages. It is reproduced here so a future deployer can +rebuild the project from scratch without guessing at settings. + +## Project settings + +In the Cloudflare dashboard under Pages > Create a project > Connect +to Git, point at the `vyrox-security/vyrox-docs` repository and set +the following build configuration. + +| Field | Value | +|---|---| +| Production branch | `main` | +| Build command | `bash scripts/build.sh` | +| Build output directory | `book` | +| Root directory | `/` | +| Environment variable | `MDBOOK_VERSION=v0.4.40` | + +The build command downloads a pinned mdBook release into +`.mdbook-bin/` if the binary is not already on PATH, syncs the +top-level Markdown into `src/`, and runs `mdbook build`. Output goes +to `book/`, which is what Cloudflare Pages publishes. + +You can change the pinned mdBook version by updating +`MDBOOK_VERSION` either in the Cloudflare environment variables or +inline in `scripts/build.sh`. We keep one source of truth so a +release bump is one edit. + +## Custom domain + +Cloudflare Pages serves the project at `.pages.dev` by +default. The production deployment maps to `docs.vyrox.dev` via a +CNAME under the `vyrox.dev` zone. Configure that under Pages > the +project > Custom domains. + +## Headers and redirects + +Both files live at the repo root and are copied into `book/` by the +build script: + +- `_headers` carries the security headers (CSP, HSTS, frame-ancestors, + referrer policy, permissions policy) plus aggressive caching for + hashed assets and short revalidation for HTML. +- `_redirects` maps the friendly path aliases (`/architecture`, + `/api`, etc) to the mdBook-generated chapter URLs. + +The header CSP allows Google Fonts because the site loads Fraunces, +Geist, and JetBrains Mono from `fonts.googleapis.com` and +`fonts.gstatic.com`. Removing those allowances breaks typography. + +## Preview deployments + +Cloudflare Pages builds every branch automatically. A pull request +gets a preview URL of the form +`..pages.dev`. The default behaviour is fine +for our use case; we do not gate previews behind authentication +because the docs are public anyway. + +If a contributor adds private documentation in the future, gate the +preview environments via Cloudflare Access. The setting lives under +Pages > the project > Settings > Access policy. + +## Local reproduction + +Run the exact build that Cloudflare Pages runs: + +```bash +bash scripts/build.sh +``` + +That fetches mdBook into `.mdbook-bin/` (so your global install is +not touched), syncs the top-level Markdown into `src/`, and writes +the site into `book/`. Serve it with anything that serves static +files: + +```bash +python -m http.server -d book 8080 +``` + +Or use `mdbook serve` for the watch loop while editing. + +## Troubleshooting + +**Build fails with "mdbook not found".** The script tries to fetch a +binary from GitHub Releases. Cloudflare Pages build hosts have +outbound HTTPS so this works by default. If you see a download +failure, check the GitHub Releases page for the pinned tag and +verify the URL pattern. Newer mdBook releases sometimes change the +archive naming. + +**Build succeeds but the deploy 404s.** The output directory is +`book`, not `dist` or `public`. Double-check the Cloudflare project +settings. + +**Headers are not applied.** Cloudflare Pages reads `_headers` from +the root of the published directory. If you do not see your CSP on +production, confirm that `_headers` ended up inside `book/` after the +build. The build script copies it; if you ran `mdbook build` +directly without the script, the copy will not have happened. + +**Fonts render as the system default.** Either the CSP is blocking +the Google Fonts CDN, or the build is missing the Google Fonts link +tag. The link tag is injected by `theme/head.hbs`. Confirm with +`grep fonts.googleapis.com book/index.html` that the link is present. + +## Cross-references + +- [`README.md`](README.md) for the project overview. +- [`CONTRIBUTING.md`](CONTRIBUTING.md) for the docs contribution + workflow. diff --git a/_headers b/_headers new file mode 100644 index 0000000..06ea672 --- /dev/null +++ b/_headers @@ -0,0 +1,33 @@ +# Cloudflare Pages headers. +# +# This file is copied into the build output at deploy time. The hosting +# layer applies these headers to every matched request. Reference: +# https://developers.cloudflare.com/pages/configuration/headers/ +# +# We keep the rule set tight. Defaults are deny-by-default for the +# things that should never be there; explicit allows where the site +# legitimately reaches off-host (Google Fonts only). + +/* + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=() + Strict-Transport-Security: max-age=31536000; includeSubDomains; preload + Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' + +# Aggressive caching on hashed asset files. mdBook fingerprints every +# CSS, JS, and font filename so this is safe for them. +/css/* + Cache-Control: public, max-age=31536000, immutable + +/fonts/* + Cache-Control: public, max-age=31536000, immutable + +/FontAwesome/* + Cache-Control: public, max-age=31536000, immutable + +# HTML revalidates fast so a docs update lands within minutes of a +# Cloudflare Pages deploy without users seeing a stale page. +/*.html + Cache-Control: public, max-age=0, must-revalidate diff --git a/_redirects b/_redirects new file mode 100644 index 0000000..374e8ac --- /dev/null +++ b/_redirects @@ -0,0 +1,29 @@ +# Cloudflare Pages redirects. +# +# Lines are ` `. mdBook publishes every +# chapter at /.html where NAME is the source filename without +# the extension. We redirect the friendlier / paths (and the +# .md form a developer might type) to the canonical .html URL. +# +# Reference: https://developers.cloudflare.com/pages/configuration/redirects/ + +# Root convenience aliases. +/architecture /ARCHITECTURE.html 301 +/threat-model /THREAT_MODEL.html 301 +/audit-chain /AUDIT_CHAIN.html 301 +/api /API_REFERENCE.html 301 +/api-reference /API_REFERENCE.html 301 +/adapters /ADAPTERS.html 301 +/quickstart /QUICKSTART.html 301 +/contributing /CONTRIBUTING.html 301 +/security /SECURITY.html 301 +/roadmap /ROADMAP.html 301 +/code-of-conduct /CODE_OF_CONDUCT.html 301 + +# Anyone typing the canonical filename with .md gets the rendered page. +/*.md /:splat.html 301 + +# Legacy SETUP_GUIDE.md is moved to vyrox-design-partners. Redirect the +# old public URL to a friendly explainer page. +/SETUP_GUIDE.html /QUICKSTART.html 308 +/setup /QUICKSTART.html 308 diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..de87da4 --- /dev/null +++ b/book.toml @@ -0,0 +1,63 @@ +# mdBook configuration for the Vyrox public documentation site. +# +# The canonical source for every chapter is a top-level Markdown file +# in the repo root (README.md, ARCHITECTURE.md, etc). `scripts/build.sh` +# copies them into src/ before invoking `mdbook build`, so editing the +# top-level file is the only place a contributor needs to touch. +# +# Output goes to `book/`. Cloudflare Pages serves the contents of +# `book/` as the static site. + +[book] +title = "Vyrox Security" +description = "Engineering documentation for the Vyrox security platform: architecture, API contracts, threat model, audit-log spec, EDR adapter guide." +authors = ["Vyrox Security"] +language = "en" +src = "src" + +[build] +build-dir = "book" +create-missing = false + +[output.html] +# Use the brand colour for the in-page accent and the website meta tags. +# Light theme is the daylight reading mode (bone palette). Dark theme +# is the void palette. Both are styled by theme/css/variables.css. +default-theme = "light" +preferred-dark-theme = "navy" + +# Smarter punctuation handling. mdBook would otherwise turn the "--" +# sequences that appear in CLI examples into en-dashes, which breaks +# copy-paste. Leaving curly-quotes on for prose readability. +smart-punctuation = false + +additional-css = ["theme/css/variables.css", "theme/css/typography.css"] +additional-js = [] + +# Edit-this-page link on every chapter. Points at the top-level file +# the chapter mirrors, so an external contributor who clicks "edit" +# lands on the canonical source. +git-repository-url = "https://github.com/vyrox-security/vyrox-docs" +git-repository-icon = "fab-github" +edit-url-template = "https://github.com/vyrox-security/vyrox-docs/edit/main/{path}" + +# Site-wide footer text. Single line because mdBook expects it. +[output.html.print] +enable = true + +[output.html.search] +enable = true +limit-results = 30 +teaser-word-count = 30 +use-boolean-and = true +boost-title = 2 +boost-hierarchy = 1 +boost-paragraph = 1 +expand = true +heading-split-level = 3 +copy-js = true + +# Custom hook into the document head: pulls in the three Google Fonts +# (Fraunces, Geist, JetBrains Mono) and adds the favicon. +[preprocessor.links] +[preprocessor.index] diff --git a/index.html b/index.html deleted file mode 100644 index 633e0c3..0000000 --- a/index.html +++ /dev/null @@ -1,1005 +0,0 @@ - - - - - - Vyrox Documentation | AI Security Operations Platform - - - - - - - -
- - -
-
- -
-
v1.0.0 Now Available
-

AI Security Operations,
On-Demand

-

Vyrox is an AI-native Managed Detection and Response (MDR) service that delivers enterprise-grade security at a fraction of traditional MSSP costs.

- -
- - -
-
-
๐ŸŽฏ
-

The Problem

-
-

Every company with CrowdStrike or SentinelOne faces the same gap: their EDR detects everything, but no one triages it at night.

-
-
- โš  -
-

500+ alerts per week

-

Hit a 200-person company's EDR

-
-
-
- ๐Ÿ‘ค -
-

IT Manager is the only analyst

-

Nights, weekends, holidays

-
-
-
- ๐Ÿ”ด -
-

Real attacks happen at 2am

-

When no one's watching

-
-
-
- ๐Ÿ”ฅ -
-

False positives burn trust

-

After 50 "scheduled task" alerts

-
-
-
-
- - -
-
-
๐Ÿš€
-

Quickstart

-
-

Get Vyrox running in ~15 minutes. No log management, no query language, no data lake required.

- -

Step 1: Get Your API Keys

-
    -
  1. Log into your Vyrox dashboard at app.vyrox.dev
  2. -
  3. Navigate to Settings โ†’ API Keys
  4. -
  5. Create a new key pair: Ingestion Key and HMAC Secret
  6. -
- -

Step 2: Configure EDR Webhook

-
-
-
๐Ÿ›ก๏ธ
-

CrowdStrike Falcon

-

Add webhook to Falcon console with HMAC-SHA256 signature

-
-
-
๐ŸŽฏ
-

SentinelOne

-

Configure Bearer token authentication

-
-
-
๐Ÿ”ท
-

Microsoft Defender

-

Connect via Azure Logic Apps

-
-
- -

Step 3: Install Discord Bot

-
    -
  1. Open Vyrox dashboard โ†’ Settings โ†’ Discord
  2. -
  3. Click Add to Server
  4. -
  5. Run /vyrox register in your server
  6. -
- -
-
- โœ“ Integration Complete -
-

Within 30 seconds, your first alert will appear in Discord with triage verdict and recommended action.

-
-
- - -
-
-
๐Ÿ—๏ธ
-

Architecture

-
-

Vyrox uses a two-stage triage pipeline: deterministic heuristics first, then LLM for ambiguous cases.

- -
-
-
EDR Webhooks
- โ†’ -
Ingestion
- โ†’ -
Redis Queue
- โ†’ -
AI Worker
- โ†’ -
Heuristics
- โ†’ -
LLM (ambiguous)
- โ†’ -
Discord
- โ†’ -
Human Approve
- โ†’ -
vyrox-proxy
-
-
- -

Stage 1: Heuristics Engine

-

Deterministic pattern-based triage in under 5ms. Covers 80%+ of alert volume with zero cost. Every verdict is explainable.

-
    -
  • Pattern matching against MITRE ATT&CK TTPs
  • -
  • No LLM dependency
  • -
  • Full audit trail
  • -
- -

Stage 2: LLM Triage

-

Ambiguous alerts escalate to a small language model with structured prompts. Returns verdict, confidence, and reasoning.

-
    -
  • Only ~20% of alerts reach LLM
  • -
  • Human-readable reasoning
  • -
  • Human approval required
  • -
-
- - -
-
-
๐Ÿ“ก
-

API Reference

-
- -

POST /webhook/crowdstrike

-

Receives detection events from CrowdStrike Falcon. Requires HMAC-SHA256 signature.

-
curl -X POST https://api.vyrox.dev/webhook/crowdstrike \
-  -H "Content-Type: application/json" \
-  -H "X-Vyrox-Signature: sha256=<signature>" \
-  -d '{"detect_id":"evt:123","timestamp":1704067200,"severity":"high"}'
- -

POST /webhook/sentinelone

-

Receives threat events from SentinelOne. Requires Bearer token.

-
curl -X POST https://api.vyrox.dev/webhook/sentinelone \
-  -H "Content-Type: application/json" \
-  -H "Authorization: Bearer <ingestion_key>" \
-  -d '{"id":"thrt_123","createdAt":1704067200,"severity":"high"}'
- -

Response Format

-
{
-  "status": "queued",
-  "alert_id": "550e8400-e29b-41d4-a716-446655440000"
-}
-
- - -
-
-
๐Ÿ”’
-

Security Model

-
- -

HMAC-SHA256 Request Signing

-

Every request between Vyrox services is signed with HMAC-SHA256. Unsigned requests are rejected at the boundary.

- -

Replay Protection

-

The proxy rejects requests with approval timestamps older than 30 seconds. Captured requests cannot be replayed.

- -

Human-in-the-Loop

-

Every CRITICAL and HIGH verdict requires human approval. No autonomous containment.

- -

Severity Workflow

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SeverityDiscord EmbedAction
CRITICALYesRequire approval
HIGHYesRequire approval
MEDIUMYesNotify only
LOWNo (configurable)Auto-dismiss
-
- - -
-
-
๐Ÿ“‹
-

Audit Logging

-
-

Every action is written to an append-only JSONL audit log with SHA-256 chaining.

- -

Log Entry Fields

-
    -
  • timestamp - Unix timestamp
  • -
  • tenant_id - Tenant isolation
  • -
  • action_type - HOST_ISOLATION, KILL_PROCESS, etc.
  • -
  • host - Target endpoint
  • -
  • approved_by - Discord user
  • -
  • dry_run - Whether action was simulated
  • -
- -

Export API

-
GET /audit/export?tenant_id=<tenant_id>
-
- - -
-
-
๐Ÿข
-

Tenant Isolation

-
-
-
- โš  Critical Security Requirement -
-

Every database query, Redis key, and audit entry includes tenant_id. A misconfiguration in one tenant cannot affect another.

-
- -

Isolation Rules

-
    -
  • Database: All queries include WHERE tenant_id = ?
  • -
  • Redis: Keys are namespaced vyrox:alerts:{tenant_id}
  • -
  • API Keys: Per-tenant, non-transferable
  • -
  • Discord Channels: Per-tenant, private
  • -
-
- - -
-
-
๐Ÿ”“
-

Open-Core Model

-
-

Vyrox follows an intentional open-core model. The containment proxy is MIT licensed and publicly available.

- -
-
-
๐Ÿ“ฆ
-

Open Source (MIT)

-

vyrox-proxy - the execution layer that touches your endpoints

-
-
-
๐Ÿ”’
-

Proprietary

-

Heuristics patterns, triage logic, detection intelligence

-
-
-
โœ…
-

Auditable

-

Read the code that executes containment on your infrastructure

-
-
-
๐ŸŽฏ
-

Defensible

-

Detection moat that enables small team to operate at MSSP scale

-
-
-
- - -
-
-
๐Ÿ’ฐ
-

Pricing

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
TierPriceWhat's Included
Sentinel$499/moUp to 50 endpoints, auto-triage, Discord approval, monthly digest
SOC Team$2K/mo + $3/endpointUnlimited endpoints, priority Discord, weekly briefing
EnterpriseCustomSSO, custom SLAs, dedicated support
- -
-
- โ„น Why endpoint-based pricing? -
-

Per-alert pricing creates an adversarial relationship. Endpoint-based pricing aligns incentives: we earn more as you grow, and we're incentivized to resolve alerts efficiently.

-
-
- - -
-

Vyrox Security © 2026. Built for IT managers who own security but aren't CISOs.

-

- GitHub ยท - Contact ยท - Website -

-
-
-
-
- - - - \ No newline at end of file diff --git a/justfile b/justfile index b146639..1322737 100644 --- a/justfile +++ b/justfile @@ -1,145 +1,72 @@ -# Vyrox Documentation Justfile -# ===================================================================== -# Production-grade task runner for documentation. -# Public docs: ARCHITECTURE.md, API_REFERENCE.md, QUICKSTART.md +# Vyrox Documentation task runner. # -# The docs site can be served as static HTML (index.html) or as -# markdown files. Both are maintained in sync. +# The docs site is an mdBook compiled from the top-level Markdown +# files. `book.toml` is the configuration, `src/SUMMARY.md` is the +# chapter order, and `theme/` holds the brand CSS and the Google +# Fonts loader. The actual `mdbook build` invocation lives in +# `scripts/build.sh` so Cloudflare Pages can call it directly. # # Usage: -# just # Show all commands -# just # Run specific command -# ===================================================================== +# just list every recipe +# just build produce ./book/ +# just serve local preview on http://localhost:3000 +# just lint line-length, trailing whitespace, broken links +# just clean remove ./book/ and the local mdbook cache -set shell := ["zsh", "-cu"] - -# ===================================================================== -# DEFAULT -# ===================================================================== +set shell := ["bash", "-cu"] default: @just --list -# ===================================================================== -# DOCUMENTATION -# ===================================================================== - -# View ARCHITECTURE.md -view-architecture: - @cat ARCHITECTURE.md - -# View API reference -view-api: - @cat API_REFERENCE.md - -# View quickstart -view-quickstart: - @cat QUICKSTART.md - -# View all docs -view-all: - @echo "=== ARCHITECTURE.md ===" - @cat ARCHITECTURE.md - @echo "" - @echo "=== API_REFERENCE.md ===" - @cat API_REFERENCE.md - @echo "" - @echo "=== QUICKSTART.md ===" - @cat QUICKSTART.md - -# ===================================================================== -# LINK CHECKING -# ===================================================================== - -# Check for broken links (requires markdown-link-check) -check-links: - @echo "Checking markdown links..." - @grep -rh "\[.*\](.*\.md)" . --include="*.md" | grep -v "^\s*#" | while read line; do target=$(echo "$line" | sed 's/.*](\([^)]*\)).*/\1/'); if [ -n "$target" ] && [ ! -f "$target" ]; then echo "Broken link: $target"; fi; done || true - -# ===================================================================== -# LINTING -# ===================================================================== - -# Lint markdown files -lint: - @echo "Linting markdown files..." - @for f in *.md; do if [ -f "$f" ]; then echo "Checking $f..."; grep -n "[[:space:]]$" "$f" || true; grep -En "\]\([a-zA-Z_]*\.md\)" "$f" | while read line; do file=$(echo "$line" | sed 's/:.*//'); link=$(echo "$line" | sed 's/.*](\([^)]*\)).*/\1/'); if [ ! -f "$link" ]; then echo "$file: broken link [$link]"; fi; done; fi; done - -# Check for TODO markers -check-todos: - @grep -rn "TODO\|FIXME\|XXX\|HACK" . --include="*.md" || echo "No TODOs found" - -# ===================================================================== -# FORMATTING -# ===================================================================== - -# Format markdown (basic cleanup) -format: - @echo "Formatting markdown files..." - @for f in *.md; do if [ -f "$f" ]; then sed -i 's/[[:space:]]*$//' "$f"; perl -i -pe 's/\n*\z/\n/' "$f"; fi; done - -# ===================================================================== -# WORD COUNT -# ===================================================================== - -# Count words in documentation -words: - @echo "Word count by file:"; @for f in *.md; do if [ -f "$f" ]; then echo " $f: $(wc -w < "$f") words"; fi; done; echo ""; echo "Total: $(cat *.md | wc -w) words" - -# Line count -lines: - @echo "Line count by file:"; @for f in *.md; do if [ -f "$f" ]; then echo " $f: $(wc -l < "$f") lines"; fi; done - -# ===================================================================== -# WEBSITE -# ===================================================================== - -# Serve the documentation website locally -serve: - @echo "Starting local docs server..." - @npx serve . -l 3000 - -# Serve with live reload -dev: - @echo "Starting development server with live reload..." - @npx serve . -l 3000 -c -1 - -# Build the static site (for deployment) +# Build the static site into ./book/. Idempotent. build: - @echo "Building static site..." - @if [ -f index.html ]; then \ - echo "index.html exists - static site ready"; \ - else \ - echo "No index.html found - run 'just website' first"; \ - fi + bash scripts/build.sh -# Validate website HTML -validate: - @echo "Validating HTML..." - @grep -c "" index.html || echo "WARNING: index.html not found or missing DOCTYPE" - @grep -c "/dev/null; then + echo "[lint] trailing whitespace in $f:" && grep -nE "[[:space:]]+$" "$f" + fail=1 + fi + if grep -nE "[โ€”โ€“]" "$f" >/dev/null; then + echo "[lint] em or en dash in $f (house style is plain ASCII):" + grep -nE "[โ€”โ€“]" "$f" + fail=1 + fi + done + exit $fail + +# Run a strict broken-link check across the public Markdown files. +links: + #!/usr/bin/env bash + set -euo pipefail + for f in *.md; do + grep -oE "\]\([A-Z_]+\.md\)" "$f" 2>/dev/null \ + | sed -E "s/\]\(([A-Z_]+\.md)\)/\1/" | sort -u \ + | while read target; do + if [[ ! -f "$target" ]]; then + echo "[links] $f references missing chapter: $target" + fi + done + done + +# Word and line counts per chapter. Useful for tracking doc growth. +stats: + @for f in *.md; do printf "%-22s %5d lines %6d words\n" "$f" "$(wc -l < $f)" "$(wc -w < $f)"; done + +# Remove the build output and the local mdbook binary cache. clean: - rm -f *.tmp - rm -f *~ - -# ===================================================================== -# CI/CD -# ===================================================================== - -# Check docs for CI -ci: - just lint - just check-todos - -# ===================================================================== -# HELP -# ===================================================================== + rm -rf book/ .mdbook-bin/ -help: - @just --list --unsorted \ No newline at end of file +# Run lint + links. Mirrored by CI. +ci: lint links diff --git a/package.json b/package.json index 3f1788f..319fd0d 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "name": "vyrox-docs", "version": "1.0.0", - "description": "Vyrox Documentation Website", + "description": "Vyrox Security engineering documentation site, built with mdBook", "private": true, "scripts": { - "dev": "npx serve .", - "build": "echo 'Static site - no build required'", - "preview": "npx serve . -l 3000" + "build": "bash scripts/build.sh", + "dev": "bash scripts/build.sh && npx serve book -l 3000", + "serve": "npx mdbook serve --open" }, "keywords": [ "documentation", "security", - "mdr", - "edr" + "edr", + "soc", + "mdbook" ], "author": "Vyrox Security", "license": "MIT" -} \ No newline at end of file +} diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..61fd10b --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# build.sh โ€” produce the static documentation site. +# +# Used by: +# - Cloudflare Pages (build command: `bash scripts/build.sh`) +# - Local contributors via `just build` or `npm run build` +# - CI in `.github/workflows/build-docs.yml` +# +# What it does: +# 1. Make sure the chapter files in src/ are in sync with the canonical +# top-level Markdown files. Top-level wins. We copy on every build +# so a contributor who only edits the top-level file does not need +# to remember src/. +# 2. Install mdbook if the host does not already have it. On Cloudflare +# Pages the workspace is ephemeral so we always end up here. +# 3. Run `mdbook build`. Output lands in `book/`. +# +# Exit codes: +# 0 build succeeded +# 1 sync or build failed +# 2 environment is missing a required tool + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +# --------------------------------------------------------------------- +# 1. Sync canonical top-level Markdown into src/ +# --------------------------------------------------------------------- +# These are the files we publish. SUMMARY.md and the theme/ directory +# are mdBook-only and live in src/ + theme/ already. +CHAPTERS=( + "README.md" + "ARCHITECTURE.md" + "THREAT_MODEL.md" + "AUDIT_CHAIN.md" + "API_REFERENCE.md" + "ADAPTERS.md" + "QUICKSTART.md" + "CONTRIBUTING.md" + "CODE_OF_CONDUCT.md" + "SECURITY.md" + "ROADMAP.md" +) + +echo "[build] syncing $((${#CHAPTERS[@]})) chapters into src/" +for f in "${CHAPTERS[@]}"; do + if [[ ! -f "$f" ]]; then + echo "[build] missing top-level chapter: $f" >&2 + exit 1 + fi + cp "$f" "src/$f" +done + +# --------------------------------------------------------------------- +# 2. Make sure mdbook is available +# --------------------------------------------------------------------- +# Cloudflare Pages does not ship mdbook. We fetch the pinned release +# tarball if the binary is not on PATH. Pinning the version means a +# release-channel change on the upstream cannot break this build. +MDBOOK_VERSION="${MDBOOK_VERSION:-v0.4.40}" +LOCAL_MDBOOK_BIN="$REPO_ROOT/.mdbook-bin/mdbook" + +mdbook_cmd() { + if command -v mdbook >/dev/null 2>&1; then + echo "mdbook" + return + fi + if [[ -x "$LOCAL_MDBOOK_BIN" ]]; then + echo "$LOCAL_MDBOOK_BIN" + return + fi + echo "" +} + +if [[ -z "$(mdbook_cmd)" ]]; then + echo "[build] mdbook not found, installing ${MDBOOK_VERSION} locally" + mkdir -p "$REPO_ROOT/.mdbook-bin" + + # Pick the right tarball for the current platform. Cloudflare Pages + # runs Ubuntu x86_64, but a local contributor might run macOS. We + # cover both and fail loudly on anything else. + OS="$(uname -s)" + ARCH="$(uname -m)" + case "$OS-$ARCH" in + Linux-x86_64) + URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz" + ;; + Darwin-arm64) + URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-aarch64-apple-darwin.tar.gz" + ;; + Darwin-x86_64) + URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-apple-darwin.tar.gz" + ;; + *) + echo "[build] unsupported platform: $OS-$ARCH" >&2 + echo "[build] install mdbook manually and put it on PATH" >&2 + exit 2 + ;; + esac + + echo "[build] fetching $URL" + curl -sSL "$URL" | tar -xz -C "$REPO_ROOT/.mdbook-bin/" + chmod +x "$REPO_ROOT/.mdbook-bin/mdbook" +fi + +MDBOOK="$(mdbook_cmd)" +if [[ -z "$MDBOOK" ]]; then + echo "[build] mdbook still not available after install attempt" >&2 + exit 2 +fi + +# --------------------------------------------------------------------- +# 3. Build +# --------------------------------------------------------------------- +echo "[build] running $MDBOOK build" +"$MDBOOK" build + +# --------------------------------------------------------------------- +# 4. Copy Cloudflare Pages metadata into the build output +# --------------------------------------------------------------------- +# `_headers` and `_redirects` belong at the root of the deployed +# directory. They live at the repo root so a contributor reading the +# repo can see them, and we copy them in at the end of the build so +# Cloudflare Pages sees them in `book/`. +for f in _headers _redirects; do + if [[ -f "$f" ]]; then + cp "$f" "book/$f" + echo "[build] copied $f into book/" + fi +done + +echo "[build] done; output in book/" diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 0000000..6e457cd --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,25 @@ +# Summary + +[Vyrox Security](./README.md) + +# Getting started + +- [Quickstart](./QUICKSTART.md) +- [Contributing](./CONTRIBUTING.md) +- [Code of Conduct](./CODE_OF_CONDUCT.md) + +# Architecture + +- [System architecture](./ARCHITECTURE.md) +- [Threat model](./THREAT_MODEL.md) +- [Audit chain specification](./AUDIT_CHAIN.md) + +# Reference + +- [API reference](./API_REFERENCE.md) +- [EDR adapter guide](./ADAPTERS.md) + +# Project + +- [Roadmap](./ROADMAP.md) +- [Security policy](./SECURITY.md) diff --git a/theme/css/typography.css b/theme/css/typography.css new file mode 100644 index 0000000..2cf4ac5 --- /dev/null +++ b/theme/css/typography.css @@ -0,0 +1,202 @@ +/* + * Vyrox typography for the documentation site. + * + * Three families per the brand spec in product.md: + * + * - Fraunces (display / headings) Variable serif, optical sizing + * - Geist (body) Highly readable sans-serif + * - JetBrains Mono (code, labels) Industry-standard programming font + * + * All three are loaded via Google Fonts in the document head. The + * fallback chain prefers system serifs / sans for the unlikely case + * the Google Fonts CDN is blocked, and we deliberately avoid + * `system-ui` and `-apple-system` as the primary face because the + * brand spec rules them out and they produce inconsistent rendering + * across platforms. + * + * Type scale targets readable line lengths and a generous line-height + * so the dense technical prose stays approachable. + */ + +/* Body */ +html, +body, +.content { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-size: 16.5px; + line-height: 1.65; + font-weight: 400; + letter-spacing: 0.001em; + /* `optical-sizing: auto` makes variable fonts render slightly + crisper at body sizes. Free perf win where supported. */ + font-optical-sizing: auto; +} + +/* Display headings */ +.content h1, +.content h2, +.content h3, +.content h4, +.content h5, +.content h6, +.menu-title { + font-family: "Fraunces", Georgia, "Times New Roman", serif; + font-weight: 600; + letter-spacing: -0.005em; + line-height: 1.18; + font-optical-sizing: auto; + /* Variable-axis tweaks. `opsz 28` keeps the body-level headings + legible; the hero (h1) bumps to a larger optical size below. */ + font-variation-settings: "opsz" 28, "SOFT" 0, "WONK" 0; +} + +.content h1 { + font-size: 2.4rem; + font-weight: 700; + margin-top: 1.2rem; + margin-bottom: 1.6rem; + font-variation-settings: "opsz" 144, "SOFT" 0, "WONK" 0; +} +.content h2 { + font-size: 1.7rem; + margin-top: 2.4rem; + margin-bottom: 1rem; + padding-bottom: 0.25rem; + border-bottom: 1px solid var(--quote-border); +} +.content h3 { + font-size: 1.3rem; + margin-top: 1.8rem; + margin-bottom: 0.6rem; +} +.content h4 { + font-size: 1.1rem; + margin-top: 1.4rem; + margin-bottom: 0.4rem; + font-weight: 600; +} + +/* Paragraphs and lists */ +.content p, +.content li { + font-size: 1rem; + max-width: 72ch; /* readable line length */ +} +.content p { + margin-top: 0.6rem; + margin-bottom: 1rem; +} +.content li { + margin-bottom: 0.35rem; +} +.content ul, +.content ol { + margin-top: 0.6rem; + margin-bottom: 1rem; +} + +/* Blockquotes โ€” used for the project tagline and inline call-outs */ +.content blockquote { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-style: normal; + font-weight: 400; + border-left: 3px solid var(--quote-border); + background: var(--quote-bg); + padding: 0.75rem 1.1rem; + margin: 1.2rem 0; + color: var(--fg); + opacity: 0.92; +} +.content blockquote p { + margin: 0.2rem 0; +} + +/* Code */ +code, +pre, +pre code, +.hljs { + font-family: "JetBrains Mono", "SF Mono", "Cascadia Code", + "Roboto Mono", Consolas, "Liberation Mono", monospace; + font-size: 0.92rem; + font-feature-settings: "liga" 0, "calt" 0; /* disable ligatures so == reads as = = */ + font-variant-ligatures: none; +} +pre { + padding: 0.9rem 1rem; + border-radius: 2px; + overflow-x: auto; + line-height: 1.55; +} + +/* Tables โ€” readable on dense API reference rows */ +.content table { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-size: 0.95rem; + border-collapse: collapse; + margin: 1.2rem 0; + width: 100%; +} +.content table th { + font-weight: 600; + text-align: left; + background: var(--table-header-bg); + padding: 0.55rem 0.7rem; + border-bottom: 1px solid var(--table-border-color); +} +.content table td { + padding: 0.5rem 0.7rem; + border-bottom: 1px solid var(--table-border-color); + vertical-align: top; +} +.content table tr:nth-child(2n) td { + background: var(--table-alternate-bg); +} + +/* Sidebar nav and menu titles */ +.sidebar, +.sidebar .chapter, +.menu-title { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; +} +.sidebar .chapter { + font-size: 0.96rem; + line-height: 1.4; +} +.menu-title { + font-weight: 600; + letter-spacing: -0.005em; +} + +/* Top nav (part titles defined by `# Part title` in SUMMARY.md) */ +.chapter > .part-title { + font-family: "JetBrains Mono", "SF Mono", Consolas, monospace; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--searchresults-header-fg); + margin-top: 1.25rem; + margin-bottom: 0.4rem; +} + +/* Search results */ +.searchbar-outer input, +#searchbar { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-size: 0.95rem; +} + +/* Footer/print niceties */ +@media print { + html, body { background: white; color: black; } + pre, code { font-size: 9pt; } +} + +/* Smaller screens. Keep readable line length but tighten the scale. */ +@media (max-width: 640px) { + html, body, .content { font-size: 15.5px; line-height: 1.6; } + .content h1 { font-size: 1.95rem; } + .content h2 { font-size: 1.45rem; } + .content h3 { font-size: 1.18rem; } + .content p, .content li { max-width: none; } +} diff --git a/theme/css/variables.css b/theme/css/variables.css new file mode 100644 index 0000000..bfac7ab --- /dev/null +++ b/theme/css/variables.css @@ -0,0 +1,166 @@ +/* + * Vyrox brand palette overrides for mdBook. + * + * The brand defines two surfaces. Light mode uses the "bone" palette + * (warm cream paper, near-black ink) for daylight reading. Dark mode + * uses the "void" palette (near-black with warm ash borders) for + * low-light. Ember is the accent on both. Every value here traces back + * to the canonical palette in `product.md`. + * + * mdBook ships several built-in themes. We override only the variables + * we need; everything else falls through to the upstream defaults. + * + * Last verified against mdbook 0.5.x default CSS structure. + */ + +/* ------------------------------------------------------------------- + * Light mode (the "bone" surface). Default reading experience. + * ------------------------------------------------------------------- */ +.light { + /* Surfaces */ + --bg: #f2ead8; /* bone */ + --fg: #0e0a05; /* ink */ + --sidebar-bg: #ece3cc; /* linen, sunken */ + --sidebar-fg: #2a2118; /* graphite-dark */ + --sidebar-non-existant: #b8a98a; /* muted clay */ + --sidebar-active: #e8462e; /* ember */ + --sidebar-spacer: #d6ccb0; + --scrollbar: #b8a98a; + --icons: #2a2118; + --icons-hover: #e8462e; + --links: #c93a25; /* darker ember for AA contrast on bone */ + --inline-code-color: #0e0a05; + + /* Theme color picker chips */ + --theme-popup-bg: #f6f0e1; + --theme-popup-border: #c9bfa8; + --theme-hover: #ece3cc; + + /* Tables */ + --table-border-color: #c9bfa8; + --table-header-bg: #ece3cc; + --table-alternate-bg: #efe6d1; + + /* Code blocks */ + --quote-bg: #efe6d1; + --quote-border: #c9bfa8; + + /* Search */ + --searchbar-border-color: #c9bfa8; + --searchbar-bg: #f6f0e1; + --searchbar-fg: #0e0a05; + --searchbar-shadow-color: rgba(14, 10, 5, 0.12); + --searchresults-header-fg: #6b5e48; + --searchresults-border-color: #d6ccb0; + --searchresults-li-bg: #f6f0e1; + --search-mark-bg: #ffe6b0; +} + +/* ------------------------------------------------------------------- + * Dark mode (the "void" surface). Selected via "navy" preferred dark. + * ------------------------------------------------------------------- */ +.navy { + --bg: #050505; /* void */ + --fg: #ece3cc; /* light bone on void */ + --sidebar-bg: #0a0a0b; /* pitch */ + --sidebar-fg: #ece3cc; + --sidebar-non-existant: #6b5e48; + --sidebar-active: #ff6a3d; /* ember-2 for brighter contrast on void */ + --sidebar-spacer: #1f1f26; + --scrollbar: #1f1f26; + --icons: #c9bfa8; + --icons-hover: #ff6a3d; + --links: #ff9156; /* ember-3 for AA contrast on void */ + --inline-code-color: #ffe6b0; /* marrow */ + + --theme-popup-bg: #0a0a0b; + --theme-popup-border: #1f1f26; + --theme-hover: #14141a; + + --table-border-color: #1f1f26; + --table-header-bg: #14141a; + --table-alternate-bg: #0e0e12; + + --quote-bg: #0e0e12; + --quote-border: #1f1f26; + + --searchbar-border-color: #1f1f26; + --searchbar-bg: #0a0a0b; + --searchbar-fg: #ece3cc; + --searchbar-shadow-color: rgba(232, 70, 46, 0.18); + --searchresults-header-fg: #c9bfa8; + --searchresults-border-color: #1f1f26; + --searchresults-li-bg: #0a0a0b; + --search-mark-bg: #8b2a12; /* rust */ +} + +/* ------------------------------------------------------------------- + * Code block colours. mdBook uses highlight.js styles; we override the + * background and a few token classes so syntax does not fight the + * surrounding text. Foreground tokens fall through to the default + * theme's palette where possible. + * ------------------------------------------------------------------- */ +.light pre, +.light pre code, +.light code { + background-color: #f6f0e1; + color: #0e0a05; +} + +.navy pre, +.navy pre code, +.navy code { + background-color: #0a0a0b; + color: #ece3cc; +} + +/* Inline code keeps a subtle border so it reads as a chip, not a tag. */ +.light :not(pre) > code { + border: 1px solid #d6ccb0; + border-radius: 2px; + padding: 0 4px; +} +.navy :not(pre) > code { + border: 1px solid #1f1f26; + border-radius: 2px; + padding: 0 4px; +} + +/* ------------------------------------------------------------------- + * Primary action accents. Buttons and the "edit this page" link pick + * up the ember accent so they read as the brand without overwhelming + * the reading surface. + * ------------------------------------------------------------------- */ +.light .nav-chapters, +.light .nav-chapters:hover { + color: #c93a25; +} +.navy .nav-chapters, +.navy .nav-chapters:hover { + color: #ff6a3d; +} + +/* Active sidebar item gets a thin ember rule on the left so it reads + like a tactical status indicator rather than a coloured background. */ +.light .chapter li.chapter-item.expanded > a.active, +.light .chapter li.chapter-item > a.active { + color: #c93a25; + border-left: 2px solid #e8462e; + padding-left: 8px; +} +.navy .chapter li.chapter-item.expanded > a.active, +.navy .chapter li.chapter-item > a.active { + color: #ff6a3d; + border-left: 2px solid #ff6a3d; + padding-left: 8px; +} + +/* Selection colour. Subtle ember tint, not bright. */ +.light ::selection { + background: rgba(232, 70, 46, 0.18); + color: #0e0a05; +} +.navy ::selection { + background: rgba(255, 106, 61, 0.32); + color: #ece3cc; +} diff --git a/theme/head.hbs b/theme/head.hbs new file mode 100644 index 0000000..ec6f513 --- /dev/null +++ b/theme/head.hbs @@ -0,0 +1,25 @@ + + + + + + + + + + From 5bc8452ff930d302311f0da32f217a9c6ad74adb Mon Sep 17 00:00:00 2001 From: keirsalterego Date: Sun, 24 May 2026 00:00:18 +0530 Subject: [PATCH 2/2] docs(theme): retune to Rust-book reference-doc feel Earlier theme leaned editorial-brutalism per product.md: warm bone cream surfaces, Fraunces for every heading, h2 with a border-bottom. That overpowered the prose in a reference-doc context. This pass takes the visual cues from doc.rust-lang.org/stable/book/ while keeping the brand fonts. Light theme is now near-white (hsl(0,0%,100%) bg, hsl(0,0%,12%) fg) with a calm gray sidebar (#fafafa) and a darker ember accent (#c93a25) that holds AA contrast on white. Dark theme stays on the void palette with the brighter #ff6a3d ember for AA on near-black. Typography drops Fraunces from every heading and keeps it only for H1 and the menu title. H2 through H6 use Geist. The h2 border-bottom is gone. Sizes tighten: H1 2.8rem, H2 2.0rem, H3 1.7rem. Body stays on Geist at 1.6rem / 1.6 line-height to match Rust-book density. Content max-width bumped to 820px (Rust book uses 750px; we go wider because the API reference tables are denser). Sidebar width 290px. Tables, code, blockquote, hr, and inline-code chips all get the calmer treatment: subtle borders, neutral backgrounds, no decorative strokes. Active sidebar item is a thin ember rule on the left rather than a coloured background. Verified: cold build is clean, both CSS files emit with a new fingerprint, fonts.googleapis.com link still present in head. --- theme/css/typography.css | 265 ++++++++++++++++++++++++--------------- theme/css/variables.css | 209 +++++++++++++----------------- 2 files changed, 250 insertions(+), 224 deletions(-) diff --git a/theme/css/typography.css b/theme/css/typography.css index 2cf4ac5..5000084 100644 --- a/theme/css/typography.css +++ b/theme/css/typography.css @@ -1,21 +1,13 @@ /* - * Vyrox typography for the documentation site. + * Vyrox docs typography. * - * Three families per the brand spec in product.md: + * Visual target is the Rust book: clean reference-doc feel, sans-serif + * body and headings, generous line-height, content centered in a + * readable column. We use Geist for body, Fraunces only for the page + * H1 as a tasteful brand accent, and JetBrains Mono for all code. * - * - Fraunces (display / headings) Variable serif, optical sizing - * - Geist (body) Highly readable sans-serif - * - JetBrains Mono (code, labels) Industry-standard programming font - * - * All three are loaded via Google Fonts in the document head. The - * fallback chain prefers system serifs / sans for the unlikely case - * the Google Fonts CDN is blocked, and we deliberately avoid - * `system-ui` and `-apple-system` as the primary face because the - * brand spec rules them out and they produce inconsistent rendering - * across platforms. - * - * Type scale targets readable line lengths and a generous line-height - * so the dense technical prose stays approachable. + * Font scale is rem-based on top of mdBook's 62.5% root, so 1.6rem + * resolves to ~16px on default browser settings. */ /* Body */ @@ -23,180 +15,249 @@ html, body, .content { font-family: "Geist", "Helvetica Neue", Arial, sans-serif; - font-size: 16.5px; - line-height: 1.65; - font-weight: 400; - letter-spacing: 0.001em; - /* `optical-sizing: auto` makes variable fonts render slightly - crisper at body sizes. Free perf win where supported. */ + font-feature-settings: "kern", "liga", "calt"; font-optical-sizing: auto; } -/* Display headings */ -.content h1, +body { + font-size: 1.6rem; + line-height: 1.6; + font-weight: 400; +} + +.content { + line-height: 1.6; +} + +/* Headings. + * + * Geist for h2 and below so the page reads as a reference doc, not a + * magazine. H1 keeps Fraunces because the brand spec calls for the + * variable serif and the H1 is the one place a touch of identity does + * not fight the prose. */ +.content h1 { + font-family: "Fraunces", Georgia, "Times New Roman", serif; + font-weight: 600; + font-size: 2.8rem; + line-height: 1.2; + letter-spacing: -0.01em; + margin-block-start: 0.4em; + margin-block-end: 0.6em; + font-variation-settings: "opsz" 96, "SOFT" 0, "WONK" 0; +} + .content h2, .content h3, .content h4, .content h5, -.content h6, -.menu-title { - font-family: "Fraunces", Georgia, "Times New Roman", serif; +.content h6 { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; font-weight: 600; letter-spacing: -0.005em; - line-height: 1.18; - font-optical-sizing: auto; - /* Variable-axis tweaks. `opsz 28` keeps the body-level headings - legible; the hero (h1) bumps to a larger optical size below. */ - font-variation-settings: "opsz" 28, "SOFT" 0, "WONK" 0; + line-height: 1.3; } -.content h1 { - font-size: 2.4rem; - font-weight: 700; - margin-top: 1.2rem; - margin-bottom: 1.6rem; - font-variation-settings: "opsz" 144, "SOFT" 0, "WONK" 0; -} .content h2 { - font-size: 1.7rem; - margin-top: 2.4rem; - margin-bottom: 1rem; - padding-bottom: 0.25rem; - border-bottom: 1px solid var(--quote-border); + font-size: 2rem; + margin-block-start: 2.2em; + margin-block-end: 0.6em; } .content h3 { - font-size: 1.3rem; - margin-top: 1.8rem; - margin-bottom: 0.6rem; + font-size: 1.7rem; + margin-block-start: 1.8em; + margin-block-end: 0.5em; } .content h4 { + font-size: 1.4rem; + margin-block-start: 1.6em; + margin-block-end: 0.4em; +} +.content h5 { + font-size: 1.2rem; + margin-block-start: 1.4em; +} +.content h6 { font-size: 1.1rem; - margin-top: 1.4rem; - margin-bottom: 0.4rem; - font-weight: 600; + margin-block-start: 1.2em; + color: var(--searchresults-header-fg); } /* Paragraphs and lists */ .content p, .content li { - font-size: 1rem; - max-width: 72ch; /* readable line length */ + font-size: 1.6rem; } .content p { - margin-top: 0.6rem; - margin-bottom: 1rem; -} -.content li { - margin-bottom: 0.35rem; + margin-block-start: 0; + margin-block-end: 1em; } .content ul, .content ol { - margin-top: 0.6rem; - margin-bottom: 1rem; + padding-inline-start: 1.8em; + margin-block-end: 1em; +} +.content li { + margin-block-end: 0.35em; +} +.content li > p { + margin-block-end: 0.5em; +} + +/* Links. Underline on hover keeps the page calm while still feeling + responsive. */ +.content a:link, +.content a:visited { + text-decoration: none; +} +.content a:hover { + text-decoration: underline; + text-underline-offset: 2px; } -/* Blockquotes โ€” used for the project tagline and inline call-outs */ +/* Blockquotes. Plain background tint, thin border, no italics. */ .content blockquote { - font-family: "Geist", "Helvetica Neue", Arial, sans-serif; - font-style: normal; - font-weight: 400; - border-left: 3px solid var(--quote-border); background: var(--quote-bg); - padding: 0.75rem 1.1rem; - margin: 1.2rem 0; + border-inline-start: 3px solid var(--quote-border); + padding: 0.6em 1em; + margin: 1.2em 0; color: var(--fg); - opacity: 0.92; } .content blockquote p { - margin: 0.2rem 0; + margin: 0.2em 0; } -/* Code */ +/* Code. JetBrains Mono everywhere, ligatures off so == reads as = =. */ code, pre, pre code, -.hljs { +.hljs, +.menu-title code { font-family: "JetBrains Mono", "SF Mono", "Cascadia Code", - "Roboto Mono", Consolas, "Liberation Mono", monospace; - font-size: 0.92rem; - font-feature-settings: "liga" 0, "calt" 0; /* disable ligatures so == reads as = = */ + "Source Code Pro", Consolas, "Liberation Mono", monospace; + font-feature-settings: "liga" 0, "calt" 0; font-variant-ligatures: none; } + pre { - padding: 0.9rem 1rem; - border-radius: 2px; + padding: 0.85em 1em; + border-radius: 4px; overflow-x: auto; line-height: 1.55; + font-size: 1.4rem; +} + +/* Inline code chip. Subtle border so it reads as an identifier + without becoming a coloured tag. */ +.content :not(pre) > code { + color: var(--inline-code-color); + background: var(--quote-bg); + border: 1px solid var(--quote-border); + border-radius: 3px; + padding: 0 0.3em; + font-size: 0.88em; } -/* Tables โ€” readable on dense API reference rows */ +/* Tables. Used heavily in the API reference. */ .content table { - font-family: "Geist", "Helvetica Neue", Arial, sans-serif; - font-size: 0.95rem; + font-size: 1.5rem; border-collapse: collapse; - margin: 1.2rem 0; + margin: 1.4em 0; width: 100%; } .content table th { font-weight: 600; text-align: left; background: var(--table-header-bg); - padding: 0.55rem 0.7rem; - border-bottom: 1px solid var(--table-border-color); + padding: 0.5em 0.7em; + border: 1px solid var(--table-border-color); } .content table td { - padding: 0.5rem 0.7rem; - border-bottom: 1px solid var(--table-border-color); + padding: 0.5em 0.7em; + border: 1px solid var(--table-border-color); vertical-align: top; } .content table tr:nth-child(2n) td { background: var(--table-alternate-bg); } -/* Sidebar nav and menu titles */ +/* Horizontal rule. Lighter than mdBook's default so it does not slice + the page. */ +.content hr { + border: 0; + border-top: 1px solid var(--quote-border); + margin: 2.2em 0; +} + +/* Sidebar. Geist with a calm 1.4 line-height. */ .sidebar, .sidebar .chapter, .menu-title { font-family: "Geist", "Helvetica Neue", Arial, sans-serif; } .sidebar .chapter { - font-size: 0.96rem; - line-height: 1.4; + font-size: 1.4rem; + line-height: 1.45; +} +.sidebar .chapter li.chapter-item { + padding: 0.18em 0; } .menu-title { + font-family: "Fraunces", Georgia, "Times New Roman", serif; font-weight: 600; - letter-spacing: -0.005em; + font-size: 1.9rem; + letter-spacing: -0.01em; + font-variation-settings: "opsz" 96; } -/* Top nav (part titles defined by `# Part title` in SUMMARY.md) */ +/* SUMMARY part titles (`# Heading` lines in SUMMARY.md) read as + small uppercase labels above each section so the nav stays + scannable. */ .chapter > .part-title { font-family: "JetBrains Mono", "SF Mono", Consolas, monospace; - font-size: 0.75rem; + font-size: 1.05rem; text-transform: uppercase; - letter-spacing: 0.12em; + letter-spacing: 0.14em; color: var(--searchresults-header-fg); - margin-top: 1.25rem; - margin-bottom: 0.4rem; + margin-block-start: 1.4em; + margin-block-end: 0.4em; + padding-inline-start: 0.6em; } -/* Search results */ -.searchbar-outer input, -#searchbar { +/* Search */ +#searchbar, +.searchbar-outer input { font-family: "Geist", "Helvetica Neue", Arial, sans-serif; - font-size: 0.95rem; + font-size: 1.45rem; +} + +/* Active sidebar item gets a thin ember rule and the accent colour + without changing background. Reads as a calm pointer instead of a + highlight bar. */ +.chapter li.chapter-item.expanded > a.active, +.chapter li.chapter-item > a.active { + color: var(--sidebar-active); + font-weight: 500; + border-inline-start: 2px solid var(--sidebar-active); + margin-inline-start: -2px; + padding-inline-start: 8px; } -/* Footer/print niceties */ +/* Print niceties */ @media print { html, body { background: white; color: black; } + .content { max-width: 100%; } pre, code { font-size: 9pt; } + .content a:link, + .content a:visited { color: black; } } -/* Smaller screens. Keep readable line length but tighten the scale. */ +/* Small screens. Tighten the scale, drop max-width so the column + fills the viewport. */ @media (max-width: 640px) { - html, body, .content { font-size: 15.5px; line-height: 1.6; } - .content h1 { font-size: 1.95rem; } - .content h2 { font-size: 1.45rem; } - .content h3 { font-size: 1.18rem; } - .content p, .content li { max-width: none; } + body { font-size: 1.55rem; line-height: 1.55; } + .content h1 { font-size: 2.2rem; } + .content h2 { font-size: 1.7rem; } + .content h3 { font-size: 1.45rem; } + .content h4 { font-size: 1.25rem; } + .content table { font-size: 1.35rem; } } diff --git a/theme/css/variables.css b/theme/css/variables.css index bfac7ab..753e2f0 100644 --- a/theme/css/variables.css +++ b/theme/css/variables.css @@ -1,163 +1,128 @@ /* - * Vyrox brand palette overrides for mdBook. + * Vyrox docs theme variables. * - * The brand defines two surfaces. Light mode uses the "bone" palette - * (warm cream paper, near-black ink) for daylight reading. Dark mode - * uses the "void" palette (near-black with warm ash borders) for - * low-light. Ember is the accent on both. Every value here traces back - * to the canonical palette in `product.md`. + * The target visual is a clean reference doc in the style of the Rust + * book (https://doc.rust-lang.org/stable/book/). Plain near-white + * background, high-contrast text, a single accent colour for links + * and active sidebar items, no decorative borders. * - * mdBook ships several built-in themes. We override only the variables - * we need; everything else falls through to the upstream defaults. + * The brand still shows up through the ember accent and the heading + * face, but it does not fight the prose. * - * Last verified against mdbook 0.5.x default CSS structure. + * mdBook ships several built-in themes. We override only the + * variables we need; everything else falls through to the upstream + * defaults. */ +:root { + /* Slightly wider than mdBook's default to fit the longer tables + in the API reference without horizontal scroll. The Rust book + uses 750px; we go to 820px because our API tables are wider. */ + --content-max-width: 820px; + --sidebar-target-width: 290px; +} + /* ------------------------------------------------------------------- - * Light mode (the "bone" surface). Default reading experience. + * Light theme. The default and the recommended reading experience. + * Near-white background, near-black text, ember accent. * ------------------------------------------------------------------- */ -.light { - /* Surfaces */ - --bg: #f2ead8; /* bone */ - --fg: #0e0a05; /* ink */ - --sidebar-bg: #ece3cc; /* linen, sunken */ - --sidebar-fg: #2a2118; /* graphite-dark */ - --sidebar-non-existant: #b8a98a; /* muted clay */ - --sidebar-active: #e8462e; /* ember */ - --sidebar-spacer: #d6ccb0; - --scrollbar: #b8a98a; - --icons: #2a2118; - --icons-hover: #e8462e; - --links: #c93a25; /* darker ember for AA contrast on bone */ - --inline-code-color: #0e0a05; - - /* Theme color picker chips */ - --theme-popup-bg: #f6f0e1; - --theme-popup-border: #c9bfa8; - --theme-hover: #ece3cc; - - /* Tables */ - --table-border-color: #c9bfa8; - --table-header-bg: #ece3cc; - --table-alternate-bg: #efe6d1; - - /* Code blocks */ - --quote-bg: #efe6d1; - --quote-border: #c9bfa8; - - /* Search */ - --searchbar-border-color: #c9bfa8; - --searchbar-bg: #f6f0e1; +.light, html:not(.js) { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 12%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 12%); + --sidebar-non-existant: #aaa; + --sidebar-active: #c93a25; + --sidebar-spacer: #f0f0f0; + + --scrollbar: #8f8f8f; + + --icons: #747474; + --icons-hover: #c93a25; + + --links: #c93a25; + --inline-code-color: #6b1a08; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #d8d8d8; + --theme-hover: #ececec; + + --quote-bg: #f6f6f6; + --quote-border: #e5e5e5; + + --warning-border: #c9892f; + + --table-border-color: hsl(0, 0%, 92%); + --table-header-bg: hsl(0, 0%, 96%); + --table-alternate-bg: hsl(0, 0%, 98%); + + --searchbar-border-color: #cccccc; + --searchbar-bg: #ffffff; --searchbar-fg: #0e0a05; - --searchbar-shadow-color: rgba(14, 10, 5, 0.12); - --searchresults-header-fg: #6b5e48; - --searchresults-border-color: #d6ccb0; - --searchresults-li-bg: #f6f0e1; + --searchbar-shadow-color: rgba(0, 0, 0, 0.08); + --searchresults-header-fg: #777; + --searchresults-border-color: #e5e5e5; + --searchresults-li-bg: #fafafa; --search-mark-bg: #ffe6b0; + + --color-scheme: light; } /* ------------------------------------------------------------------- - * Dark mode (the "void" surface). Selected via "navy" preferred dark. + * Dark theme (navy). Selected automatically for users whose OS + * prefers dark. Pitch black background, warm-cream text, brighter + * ember accent for AA contrast. * ------------------------------------------------------------------- */ .navy { - --bg: #050505; /* void */ - --fg: #ece3cc; /* light bone on void */ - --sidebar-bg: #0a0a0b; /* pitch */ + --bg: #0a0a0b; + --fg: #ece3cc; + + --sidebar-bg: #050505; --sidebar-fg: #ece3cc; --sidebar-non-existant: #6b5e48; - --sidebar-active: #ff6a3d; /* ember-2 for brighter contrast on void */ - --sidebar-spacer: #1f1f26; - --scrollbar: #1f1f26; - --icons: #c9bfa8; + --sidebar-active: #ff6a3d; + --sidebar-spacer: #14141a; + + --scrollbar: #2a2a30; + + --icons: #8a8a90; --icons-hover: #ff6a3d; - --links: #ff9156; /* ember-3 for AA contrast on void */ - --inline-code-color: #ffe6b0; /* marrow */ + + --links: #ff9156; + --inline-code-color: #ffe6b0; --theme-popup-bg: #0a0a0b; --theme-popup-border: #1f1f26; --theme-hover: #14141a; + --quote-bg: #14141a; + --quote-border: #1f1f26; + + --warning-border: #c9892f; + --table-border-color: #1f1f26; --table-header-bg: #14141a; --table-alternate-bg: #0e0e12; - --quote-bg: #0e0e12; - --quote-border: #1f1f26; - --searchbar-border-color: #1f1f26; - --searchbar-bg: #0a0a0b; + --searchbar-bg: #14141a; --searchbar-fg: #ece3cc; - --searchbar-shadow-color: rgba(232, 70, 46, 0.18); + --searchbar-shadow-color: rgba(255, 106, 61, 0.18); --searchresults-header-fg: #c9bfa8; --searchresults-border-color: #1f1f26; --searchresults-li-bg: #0a0a0b; - --search-mark-bg: #8b2a12; /* rust */ -} - -/* ------------------------------------------------------------------- - * Code block colours. mdBook uses highlight.js styles; we override the - * background and a few token classes so syntax does not fight the - * surrounding text. Foreground tokens fall through to the default - * theme's palette where possible. - * ------------------------------------------------------------------- */ -.light pre, -.light pre code, -.light code { - background-color: #f6f0e1; - color: #0e0a05; -} - -.navy pre, -.navy pre code, -.navy code { - background-color: #0a0a0b; - color: #ece3cc; -} + --search-mark-bg: #8b2a12; -/* Inline code keeps a subtle border so it reads as a chip, not a tag. */ -.light :not(pre) > code { - border: 1px solid #d6ccb0; - border-radius: 2px; - padding: 0 4px; -} -.navy :not(pre) > code { - border: 1px solid #1f1f26; - border-radius: 2px; - padding: 0 4px; + --color-scheme: dark; } /* ------------------------------------------------------------------- - * Primary action accents. Buttons and the "edit this page" link pick - * up the ember accent so they read as the brand without overwhelming - * the reading surface. + * Selection colour. Subtle ember tint on light, slightly stronger on + * dark so the highlight reads through the lower-contrast surface. * ------------------------------------------------------------------- */ -.light .nav-chapters, -.light .nav-chapters:hover { - color: #c93a25; -} -.navy .nav-chapters, -.navy .nav-chapters:hover { - color: #ff6a3d; -} - -/* Active sidebar item gets a thin ember rule on the left so it reads - like a tactical status indicator rather than a coloured background. */ -.light .chapter li.chapter-item.expanded > a.active, -.light .chapter li.chapter-item > a.active { - color: #c93a25; - border-left: 2px solid #e8462e; - padding-left: 8px; -} -.navy .chapter li.chapter-item.expanded > a.active, -.navy .chapter li.chapter-item > a.active { - color: #ff6a3d; - border-left: 2px solid #ff6a3d; - padding-left: 8px; -} - -/* Selection colour. Subtle ember tint, not bright. */ .light ::selection { - background: rgba(232, 70, 46, 0.18); + background: rgba(232, 70, 46, 0.16); color: #0e0a05; } .navy ::selection {