# MoCipher — Site specification

This document describes **mocipher.com** (Mohammed Rahhal’s personal site): design tokens, layout, animations, pages, APIs, data flows, privacy posture, and security controls. It is the single reference for contributors and for aligning deployments with intent.

---

## 1. Principles

| Principle | Implementation |
|-----------|----------------|
| **No third-party front-end delivery** | Public HTML loads scripts, styles, and fonts only from this origin (`/scripts/`, `/styles/`, `/assets/`). Libraries are **vendored** under `/assets/vendor/` (marked, DOMPurify, Prism, Quill for admin). No Google Fonts, jsDelivr, or analytics scripts on public pages. |
| **Privacy-respecting** | No ad trackers; contact data stored first-party (KV); optional Resend only when server secret is set; concise notice in `privacy.html` (CH/EU framing). **Credentials:** Public certification **titles** come from the **KV timeline list** (admin **History** → type **Certification**): homepage grid, **Now** chips, and **dynamic** `hasCredential` JSON-LD (`#person-credentials-ld`) are generated in `main.js` from `/api/timelines`. Keep entries aligned with the CV; optional per-item **`verifyUrl`** (https) opens issuer sites in a new tab (`rel="noopener"`). **`llms.txt`** is static — refresh prose if you still list creds there, or leave it pointing at the live site. |
| **Secure defaults** | Strict CSP and related headers in `_headers`; admin routes `noindex` + `no-store`; session cookie httpOnly (see privacy notice). |

**Optional server-side third parties:** Cloudflare (hosting, KV, Workers), and optionally **Resend** for email relay — these are not loaded in the visitor’s browser as scripts.

---

## 2. Design system

### 2.1 Typography

- **Primary UI font:** Inter (variable), self-hosted: `/assets/fonts/InterVariable.woff2`, declared in `/styles/fonts.css`.
- **Accent / fallback:** Quicksand variable TTF in `/assets/fonts/`.
- **Stack (body):** `'Inter', 'Quicksand', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif` (`styles/main.css`).

Preload Inter on pages that use it (see `index.html`, `blog.html`, `privacy.html`, etc.).

### 2.2 Color tokens (`:root` in `styles/main.css`)

| Token | Hex | Usage |
|-------|-----|--------|
| `--primary` | `#6c63ff` | Brand, links, accents, focus ring |
| `--primary-dark` | `#5a52e0` | Hover / pressed states |
| `--secondary` | `#ff6b6b` | Warm accent (badges, highlights) |
| `--accent` | `#4ecdc4` | Cool accent |
| `--dark` | `#181c2f` | Primary text, headings |
| `--dark-light` | `#2a2f4a` | Secondary text |
| `--light` | `#f5f7fa` | Page background |
| `--white` | `#ffffff` | Cards, nav surface |
| `--gray` | `#666` | Muted text |
| `--gray-light` | `#e0e0e0` | Borders, dividers |
| `--gradient-1` | `135deg, #667eea → #764ba2` | Hero, CTAs, availability strip |
| `--gradient-2` | pink → red | Decorative sections |
| `--gradient-3` | blue → cyan | Decorative / cards |
| `--shadow-sm` / `--shadow-md` / `--shadow-lg` | rgba blacks | Elevation |
| `--shadow-glow` | purple glow | Hero / emphasis |

**Theme color (PWA):** `#6c63ff` (light), `#0f1117` (dark) — see `<meta name="theme-color">` in HTML heads.

### 2.3 Layout

- **Max content width:** `.container` — `max-width: 1200px`, horizontal padding `2rem` (tighter on small screens).
- **Static legal / uses-style pages:** `.static-document` — `max-width: 720px` for readable line length.
- **Nav:** Fixed top, frosted glass (`backdrop-filter: blur(20px)`), centered menu; mobile hamburger + full-screen backdrop.

### 2.4 Motion and interaction

- **Hero legibility:** Subtle `text-shadow` on subtitle, location, and description so copy stays readable over gradient orbs.
- **Global:** `scroll-behavior: smooth` on `html`.
- **Nav links:** Underline animates via `::after` width transition (`0.3s ease`).
- **Cards (projects, posts, testimonials):** `transform` + `box-shadow` on hover (~`0.25s ease`), slight `translateY(-4px)` pattern.
- **Buttons:** Color/background transitions; primary buttons often use `--gradient-1`.
- **Page transition overlay:** `#pageTransition` (used on multi-page nav — see `main.js`).
- **Blog:** Reading progress bar, Prism line-highlight feel via `prism-tomorrow.min.css` (vendor).
- **PWA install banner:** Slide/fade behavior in `main.css` (`.pwa-install-banner`).

Prefer `prefers-reduced-motion` awareness for new components where motion is non-essential (existing site may add checks incrementally).

### 2.5 Components (high level)

- **Hero:** Gradient background, headline, CTA, social/skills hooks; optional **From the blog** card when `pinnedHomePostId` is set in Settings.
- **Hero / footer outbound links:** Strip tries each link origin’s **`/favicon.ico`**, then **`/favicon-32x32.png`**, **`/favicon-16x16.png`**, **`/apple-touch-icon.png`** (this site for `mailto:`); on repeated load errors, the first letter of the label is shown. `mailto:` opens in the same tab (no `target="_blank"`).
- **Availability band:** `.availability-section` / `.availability-card` — eyebrow, headline, subtext; overridable via Settings (`availabilityHeadline`, `availabilitySubtext`).
- **History:** `#history` — **Highlights** row (items with `highlight`, else three most recent non-certification entries) and a **Full history** `<details>` with all journey rows; certifications are excluded from this block and remain under **Certifications**.
- **Impact & perspectives:** Testimonial cards (`.testimonial-card`); homepage shows HTML placeholders until `/api/testimonials` returns data, then `loadTestimonials` cross-fades the grid (`testimonials-grid--placeholders`, `aria-busy`, `aria-live="polite"` on `#testimonialsGrid`); `prefers-reduced-motion: reduce` skips the opacity transition.
- **Contact:** Recruiter-focused copy, `.recruiter-note`, logistics; link to Privacy.
- **Newsletter blocks:** `.home-newsletter-cta` on homepage and strip on blog — hidden until `newsletterUrl` (and related labels) exist in Settings.
- **Footer:** Three columns — brand, links (Home, About, Blog, Uses, Now, Projects, Privacy, Contact, optional RSS), **social link row** (same favicon treatment as hero); secondary line with **content revised** (`time.footer-revised`, text from `SITE_CONTENT_REVISED_ISO` in `main.js`), **Colophon**, **llms.txt**.
- **RSS discovery:** `<link rel="alternate" type="application/rss+xml" href="/api/rss">` on main public pages (first-party feed only).

---

## 3. Pages

| File | Role |
|------|------|
| `index.html` | Landing: hero (optional **pinned post** strip from Settings), availability, about, **History** (`#history`: highlights strip + collapsible full journey; non-certification items), **Certifications** (API), impact/testimonials, projects (+ `projects.html` link), latest posts, contact, newsletter. **`#field-note` URLs** redirect to **`#impact`**; legacy **`#timeline`** to **`#history`**. Person JSON-LD includes `@id`; **`hasCredential`** injected from API into `#person-credentials-ld`. |
| `blog.html` | Listing, search, tags, post view, related posts, newsletter strip, client-side meta updates for SPA-style open/close. |
| `uses.html` | Stack / tools. |
| `now.html` | Long-form “Now” snapshot (TOC with **Credentials** jump, stats, homelab, lessons, building); **certification chips** from `/api/timelines` (`#nowCredentialsGrid`), aligned with home **Certifications**. |
| `projects.html` | `/api/projects` with stable **`#project-{id}`** anchors for sharing. |
| `colophon.html` | Stack, fonts, **no third-party analytics**, links to spec + `llms.txt`. |
| `privacy.html` | CH/EU-oriented privacy notice. |
| `llms.txt` | Plain-text context for assistants (first-party). |
| `admin.html` / `admin-login.html` | CMS UI (Quill, local vendor). **History** tab edits `/api/timelines` (jobs, education, certifications). **Credentials** follow the pipeline under **Privacy-respecting** (type **Certification**, JSON-LD, `now.html`, `llms.txt`). Optional **`highlight`** flags experience/education for the homepage strip. |
| `404.html`, `offline.html` | Error / offline. |

**Navigation:** Main nav includes **Now**, **Privacy**, and **Projects** where applicable; footers align with the link set above.

**Open Graph / Twitter:** `og:image` + dimensions and `twitter:card` = `summary_large_image` on `uses.html`, `now.html`, `privacy.html`, `projects.html`, `colophon.html` (large preview when sharing).

---

## 4. Front-end scripts

| Script | Responsibility |
|--------|----------------|
| `scripts/main.js` | Nav (mobile **Escape** closes menu; `aria-controls` on toggle), **site revision** footers (`SITE_CONTENT_REVISED_ISO`), homepage API hydration, **History + certifications** from `/api/timelines` (including **Now** page + **JSON-LD** `hasCredential`), **social links** (chained favicon paths per link origin in hero + footer, letter fallback if all fail), **projects-only** page (`projects.html`), contact form POST, PWA registration, back-to-top. |
| `scripts/blog.js` | Fetch posts, filter/search (including stripped body text), tag group **`role="group"`** + **`aria-label`**, open post, load Prism languages sequentially from `/assets/vendor/`, related posts (shared tags then recency), OG/Twitter/canonical DOM updates, newsletter strip visibility; back control has **`aria-label`**. |
| `scripts/admin.js` | Auth, CRUD for posts (tags, `ogImage`), settings merge, testimonials tab, **History** entries (`verifyUrl`, **`highlight`**), **Social links** list (chained favicon preview + letter fallback, same as public site), etc. |

**Cache busting:** Query params like `?v=34` on `main.js` / `?v=20` on `admin.js` when releasing — bump when logic changes materially.

---

## 5. Back-end (Cloudflare Pages Functions)

- **KV binding:** `MY_KVSITE` (see Cloudflare project settings).
- **Key patterns:** Posts, site settings (merged on save; **GET `/api/settings`** strips deprecated keys such as legacy Field-note fields and rewrites KV once), `testimonials` list, contact submissions, **`timeline`** KV list served as **`/api/timelines`** (optional **`verifyUrl`**; optional **`highlight`** for homepage History strip; certification verify links), etc.
- **Notable routes:** `/api/posts`, `/api/settings`, `/api/testimonials`, `/api/testimonials/:id`, `/api/contact`, `/api/rss`, `/api/sitemap`, `/api/timelines`, auth routes.
- **`functions/blog/[slug].js`:** Serves `blog.html` with server-injected meta for the post: **og:image** priority `ogImage` → `image` → profile default.

**Settings fields (site):** Include `availabilityHeadline`, `availabilitySubtext`, `recruiterContactNote`, `newsletterUrl`, `newsletterLabel`, `newsletterDescription`, plus existing hero/CV/social keys — **merge** with previous KV document on PUT so partial updates preserve fields.

---

## 6. Security

### 6.1 HTTP headers (`_headers`)

- `Strict-Transport-Security` (preload-ready)
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `Referrer-Policy: strict-origin-when-cross-origin`
- Restrictive `Permissions-Policy`
- **CSP (summary):** `default-src 'self'`; `script-src 'self'`; `style-src 'self'`; `font-src 'self'`; `img-src 'self' data: https: blob:` (https for absolute OG images if needed); `connect-src 'self'`; `frame-ancestors 'none'`; `object-src 'none'`; `base-uri 'self'`; `upgrade-insecure-requests`
- `Cross-Origin-Opener-Policy: same-origin`, `Cross-Origin-Resource-Policy: same-origin`
- `/api/*`: `X-Robots-Tag: noindex, nofollow`
- Admin HTML: `noindex`, `no-store` where configured

### 6.2 Service worker (`sw.js`)

- **Precache** critical static assets including **all Prism language chunks** used by `blog.js`, plus `projects.html`, `colophon.html`, `llms.txt`, `MoCipher-SPEC.md`, `styles/now.css`, `scripts/now.js`; bump `CACHE_NAME` when the precache list changes.
- **Strategy:** Network-first for `/api/*`; cache-first for static (with `ignoreSearch: true` for `?v=` busting).

### 6.3 Content safety

- Blog HTML from markdown: **marked** + **DOMPurify** before injection.
- Admin: Quill output should continue to be sanitized on render if stored as HTML (follow existing post pipeline).

---

## 7. Privacy (summary)

Full copy lives in **`privacy.html`**. In short:

- Contact form: name, email, message; honeypot for bots.
- KV storage for site content and bounded contact history.
- Optional Resend when `RESEND_API_KEY` is present (server only).
- Cookies: httpOnly session for admin.
- SW cache for offline; user can clear site data.
- Rights: access, rectification, erasure, restriction, objection, portability, complaint (EDÖB / EU DPA) as applicable.

---

## 8. Assets and vendor directory

- **Images:** `/assets/images/` — profile, OG defaults, PWA icons.
- **Fonts:** `/assets/fonts/` — Inter WOFF2, Quicksand TTF.
- **Vendor:** `/assets/vendor/` — `marked.min.js`, `purify.min.js`, `prism*.js`, `prism-tomorrow.min.css`, `quill.min.js`, `quill.snow.css`.

Upstream minified files may contain license comments referencing original CDNs; **runtime loading** remains first-party.

---

## 9. Deployment checklist

1. Confirm `_headers` deployed with Pages.
2. KV binding `MY_KVSITE` attached (see `wrangler.toml` comments for CLI hints).
3. Secrets: session/auth secrets; optional `RESEND_API_KEY`.
4. After changing `sw.js` precache: bump `CACHE_NAME` and deploy.
5. Update **Last updated** in `privacy.html` when processing changes.
6. On substantive static copy changes: set **`SITE_CONTENT_REVISED_ISO`** in `scripts/main.js` and matching `datetime` on all `time.footer-revised` elements in HTML (or rely on JS overwrite of text while keeping `datetime` in sync).

## 9b. Analytics

- **None** on public pages: no third-party analytics scripts, beacons, or engagement trackers. Operational hosting logs may exist per provider policy — see `privacy.html` and `colophon.html`.

---

## 10. Related docs (repository)

- `README.md` — overview and setup.
- `wrangler.toml` — Pages project name + `compatibility_date`; KV binding documented in dashboard.
- `QUICKSTART.md`, `DEPLOYMENT.md`, `API-CONFIGURATION.md`, `FEATURES.md`, `PAGES-SETUP.md`, etc. — operational detail.

## 11. Accessibility (baseline)

- Skip link to `#main-content`; meaningful **`aria-label`** on icon-only controls; **`aria-expanded` / `aria-controls`** on mobile nav toggle; **Escape** closes the mobile menu and returns focus to the toggle.
- Blog tag strip: **`aria-pressed`** on tag buttons; container **`role="group"`** + **`aria-label`**; live region for result counts where implemented.
- Prefer visible focus (`:focus-visible` in `main.css`); respect **`prefers-reduced-motion`** in page-specific CSS where added.

---

*Document version: May 2026 — includes projects index, colophon, llms.txt, RSS discovery, footer revision line, expanded SW precache, and no analytics policy.*
