docs: establish approved Phase 1 design + Phase 2 spec as project foundation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-03 21:07:25 +02:00
commit 1f695d3932
39 changed files with 2686 additions and 0 deletions
@@ -0,0 +1,91 @@
# DESIGN_RESEARCH.md
Portfolio Redesign — Phase 1 Research
Connor Babbington · cesnimda.co.uk · July 2026
---
## 1. Audit of the existing site
**Current state:** WordPress + Avada (Fusion Builder) single-page site. Sections: Hero → About → Proficiencies → Experience → Projects → CV downloads → Contact form.
### What works
- The content skeleton is correct: summary, skills, experience, projects, CV, contact — everything a recruiter needs exists somewhere.
- Clear availability statement ("open to remote, hybrid, on-site") in the first viewport.
- Both CV languages are downloadable from the hero.
### What fails against the goal ("decide to interview me")
| Problem | Evidence | Impact |
|---|---|---|
| **Generic template look** | Avada theme, stock section headings ("About.", "Let's Talk.") | A hiring manager evaluating a *software engineer* sees a page-builder site — it signals "assembled", not "engineered". The single strongest negative signal on the current site. |
| **No proof, only claims** | Skills are bare word-lists; experience is one paragraph; projects have no screenshots, no links, no architecture, no outcomes | Recruiters can't verify; engineers can't assess depth. Nothing differentiates from 10,000 similar CVs. |
| **Projects undersold** | Job Tracker gets 2 sentences; InboxIntel absent entirely | These two projects are the *strongest* seniority evidence available (multi-service architecture, OAuth, AI integration, Docker, testing, security hardening) and they're invisible. |
| **English only** | `lang="en-GB"`, no Norwegian content | Candidate lives in Tønsberg targeting the Norwegian market; a Norwegian hiring manager gets zero accommodation. Bokmål content is also a trust and effort signal ("aktiv språkutvikling" made visible). |
| **Heavy, slow stack** | WP + Avada + W3TC minified bundles, lazyload shims | Poor Core Web Vitals undercut the "production-level thinking" message. The site itself should be a work sample. |
| **No dark mode, no motion design, no personality** | Static template sections | Feels dated next to any modern product site. |
| **Weak SEO/meta** | Empty `og:title`, description is a raw text dump of the hero | Sloppy details on the most detail-oriented artifact a developer publishes. |
**Conclusion:** the redesign is not a reskin — the site must *become the work sample*: fast, precise, bilingual, with projects presented as engineering case studies.
---
## 2. What makes engineer portfolios convincing (pattern research)
Synthesised from high-performing engineer portfolios (e.g. the genre exemplified by Brittany Chiang, Lee Robinson, Josh Comeau, Rauno Freiberg) and product marketing sites (Stripe, Linear, Vercel, Notion, Framer):
### Convincing signals
1. **Specificity beats adjectives.** "Reduced manual spreadsheet workflows for a county council" beats "passionate problem-solver". Numbers, stack names, and constraints read as experience.
2. **Case studies, not thumbnails.** Portfolios that convert treat projects like product launches: problem → approach → architecture → decisions/trade-offs → outcome → screenshots. Engineers hiring engineers read the *trade-offs* section first.
3. **The site itself is evidence.** Fast load, keyboard access, tasteful motion, working dark mode, correct typography — hiring managers notice these subconsciously; senior ones notice consciously.
4. **Restraint reads as seniority.** Junior portfolios over-animate. The Linear/Stripe lesson: one or two signature moves, executed perfectly, on a quiet base.
5. **Low-friction verification.** CV download, LinkedIn, e-mail, code links reachable within one interaction from anywhere on the page.
6. **A human face.** A real photo near the top measurably increases trust and recall; recruiters match the site to the LinkedIn profile.
### What makes visitors leave
- Walls of unscannable text (recruiters scan, engineers skim then deep-read).
- "Skills: HTML, CSS, JavaScript…" laundry lists with no context or proficiency framing.
- Broken/missing project links, placeholder content, lorem-tier copy.
- Slow first paint; janky scroll-hijacking; autoplaying anything.
- No way to answer "can this person do *my* job?" within ~30 seconds.
### SaaS landing-page techniques worth borrowing (not copying)
| Site | Technique to adapt |
|---|---|
| **Stripe** | Dense-but-calm information design; gradient used as a single identity moment, not everywhere; impeccable code-adjacent typography. |
| **Linear** | Dark-first palette; short declarative sentences; motion only on state change; keyboard-first affordances. |
| **Vercel** | Monochrome + one accent; triangle-precision spacing; "proof strip" (logos/metrics) directly under the hero. |
| **Notion** | Friendly humanity inside a rigid grid; illustration used sparingly to soften. |
| **Framer** | Scroll-linked reveals that never block reading; hero that demonstrates the product (here: the site demonstrates the engineer). |
---
## 3. Recruiter & hiring-manager evidence base
(Expanded in RECRUITER_BEHAVIOUR_ANALYSIS.md.)
- **Time budget:** eye-tracking studies of recruiters (Ladders, 2018) put initial CV scan at **~7.4 seconds**. A portfolio gets slightly more — roughly 2050 seconds before a stay/leave decision — but the first viewport carries the decision.
- **Scan pattern:** F-pattern / layered scanning (Nielsen Norman Group): name → title → location/availability → most recent role → skills. Layout must place these on the F.
- **What they screen for:** role fit keywords, seniority, location/work-permit, availability, red flags (gaps, vagueness). The hero must answer: *who, what level, what stack, where, available?*
- **Two-audience problem:** recruiters (non-technical, 30s, checklist-driven) and engineering managers/CTOs (technical, 310 min, depth-driven) visit the same URL. The IA must serve a **fast lane** (scannable hero + skills + CV button) and a **deep lane** (case studies, architecture, decisions) without either blocking the other.
- **Norway-specific:** Norwegian hiring culture values understatement, concrete competence claims (jf. *janteloven* — avoid grandiosity), and language effort. "Norsk A2/B1, i aktiv utvikling" stated plainly is a positive signal, not a weakness to hide. Work-permit clarity ("gyldig oppholdstillatelse") removes the #1 recruiter objection for foreign candidates.
---
## 4. Accessibility baseline (non-negotiable, also a seniority signal)
- WCAG 2.2 AA: contrast ≥ 4.5:1 body text (≥ 3:1 large text/UI), visible focus states, target size ≥ 24px.
- Full keyboard operability including the language switch and theme toggle.
- `prefers-reduced-motion` honoured by *every* animation (motion system must define reduced variants, not just "off").
- Semantic landmark structure; skip-link; alt text strategy for project screenshots.
- Language correctness: `lang` per locale, `hreflang` alternates — this doubles as SEO.
---
## 5. Strategic conclusions → design brief
1. **Reposition the site as a product.** Name the artefact: it is "Connor Babbington — systems developer" presented with the craft of a SaaS launch page.
2. **Projects become the centre of gravity.** Two full case studies (JobTrack, InboxIntel) + an infrastructure/homelab capability page. Each with architecture diagram, screenshots, decisions, and honest "what I'd improve".
3. **Two-lane IA.** 30-second lane: hero → proof strip → skills → CV. 10-minute lane: case studies → experience → about.
4. **Bilingual as a feature, not a toggle afterthought.** Norwegian content is written (not machine-mirrored), the switch is visible in the header, and URLs are language-scoped.
5. **Honest seniority framing.** The CV says "mid-level, 8 years". The site should not claim "senior" in a title; it should *demonstrate* senior judgement (trade-off writing, production thinking, security notes) and use the framing "systems developer, 8+ years". Evidence over adjectives — this is also the safest posture for the Norwegian market.
6. **Performance is part of the design.** The visual system is deliberately light: system-adjacent type stacks, no hero video, SVG-first graphics, motion via transform/opacity only.
7. **The design direction is defined in DESIGN_SYSTEM.md as "Kontrollrom / Quiet Console"** — a Nordic-restrained, dark-first engineering aesthetic with a single aurora-teal accent and monospace metadata as its identity move. Unique, ownable, and cheap to render.
@@ -0,0 +1,62 @@
# RECRUITER_BEHAVIOUR_ANALYSIS.md
How recruiters and hiring managers actually behave on candidate sites — and the design responses.
---
## 1. The decision funnel
```
Application / LinkedIn / referral
│ (click, if the link looks worth it)
First viewport ······· ~510 s → stay or bounce
Scan pass ············ ~2045 s → "worth shortlisting?"
Depth pass (technical) ~310 min → "worth interviewing?"
CV download / e-mail / forward to hiring manager
```
Each stage has different owners: the scan pass is usually a recruiter; the depth pass an engineering manager. **The same page must pass both.**
## 2. Evidence & observed patterns
1. **~7.4 seconds** median initial CV scan (Ladders eye-tracking, 2018). Web pages get marginally more, but the fixation order is stable: **name → title → location → recent employer → dates → skills**. Design response: those six items are literally the hero and proof strip, in that order.
2. **F-pattern scanning** (NN/g): two horizontal sweeps near the top, then a vertical skim down the left edge. Design response: left-align key nouns; front-load sentences; section labels in the left rail on desktop; never bury critical facts mid-paragraph or right-aligned.
3. **Layer-cake scanning:** on well-structured pages, scanners read *headings only*. Design response: headings must carry the argument alone. Test: read only the H2/H3s — do they say "experienced .NET/full-stack developer in Norway with production evidence, available now"? Ship only if yes.
4. **Checklist screening:** recruiters screen *out* before they screen in. Top objections for this candidate profile: (a) work permit / right to work in Norway, (b) language, (c) seniority mismatch, (d) unverifiable claims. Design response: kill all four above the fold — permit ✓, "English native / Norsk B1" ✓, "8+ yrs" ✓, links to real running systems ✓.
5. **Mobile share is significant** for first visits from LinkedIn (often 4060%). Design response: mobile hero carries identical decision-facts; CV download works on phone (and the CV itself is ATS-parseable — see CONTENT_STRATEGY §6).
6. **Trust markers ranked** (approximate weighting for a mid/senior dev profile):
- Real employer names + durations ●●●
- Screenshots of real, running software ●●●
- Face photo matching LinkedIn ●●
- Code/architecture detail ●● (hiring managers)
- Domain + professional e-mail ●
- Testimonials ○ (low value unless verifiable — omit rather than fake)
7. **Exit triggers:** template look, broken links, "under construction", walls of text, unlabeled skill bars ("JavaScript 80%" — meaningless, reads junior), autoplay, scroll hijack, pop-ups.
## 3. The two-lane rule (governing IA principle)
- **Fast lane (P1/P3):** hero → proof strip → skills matrix → CV/contact. Complete in ≤ 45 s without a single click. Every fact scannable, nothing interactive required.
- **Deep lane (P2/P4):** project case studies and experience detail, one click away, each readable in 35 minutes with skimmable structure (headings carry the argument; diagrams before prose).
- Lanes must not interleave: no case-study prose on the homepage beyond a 2-line teaser; no marketing fluff inside case studies.
## 4. Norway-market specifics
- **Understatement wins.** "Senior rockstar ninja" framing actively harms with Norwegian evaluators. Use concrete competence statements: "Åtte års erfaring med systemutvikling i britisk offentlig sektor."
- **Public sector experience is an asset** — Warwickshire County Council maps mentally to kommune/fylkeskommune IT. Frame it that way in Norwegian copy.
- **Language effort is a first-class signal.** A natively-written Bokmål site says more than "Norwegian: B1" ever can. Never machine-mirror; write shorter, plainer Norwegian rather than translated English sentence structures.
- **Practicalities beat charisma:** permit, location (Tønsberg — commutable to Oslo south corridor, Horten, Sandefjord tech), remote readiness, start availability.
## 5. Measurable success criteria for the design (Phase 1 acceptance)
| # | Criterion | Test |
|---|---|---|
| 1 | Identity, role, stack, location, permit, availability all visible in first desktop viewport (1440×900) and first two mobile screens | Mockup review |
| 2 | Headings alone tell the hire story | Read H-tags only |
| 3 | CV (correct language) reachable in ≤ 1 interaction from any scroll position | Mockup review |
| 4 | A technical reader can find an architecture diagram in ≤ 2 clicks | Journey walkthrough |
| 5 | Language switch discoverable in ≤ 5 s, state obvious | Mockup review |
| 6 | Nothing moves without user intent except one hero identity moment (≤ 800 ms, reduced-motion safe) | Motion spec |
| 7 | All text ≥ 4.5:1 contrast in both themes | Colour system audit |
+55
View File
@@ -0,0 +1,55 @@
# USER_PERSONAS.md
Four personas drive every layout and content decision. Each has a *time budget*, a *question they must answer*, and a *conversion action*.
---
## P1 — "Silje", Tech Recruiter (agency or in-house), Oslo/Vestfold
- **Context:** Screening 40+ profiles today. Found the site via LinkedIn or an application link. Likely on desktop, possibly mobile between meetings. Norwegian native; reads English fine but a Norwegian option signals local commitment.
- **Time budget:** 2045 seconds.
- **Technical depth:** Low. Matches keywords to a job spec.
- **Must answer:** Right title? Right stack keywords (C#, .NET, React, Docker)? Located where? Work permit? Available? Language level? CV downloadable?
- **Conversion action:** Download CV (correct language) or copy e-mail/LinkedIn into ATS.
- **Design implications:** Hero states role, stack, location, permit and availability *in text* (not images). CV buttons persistent (hero + sticky nav + footer). Norwegian toggle prominent. Phone-friendly one-thumb layout.
- **Failure mode:** Any ambiguity about location/permit → skip candidate.
## P2 — "Martin", Engineering Manager, product company (Oslo/remote-EU)
- **Context:** Recruiter shortlisted Connor; Martin has 5 minutes between meetings to decide interview/no-interview. Desktop, probably dark-mode IDE person.
- **Time budget:** 37 minutes.
- **Technical depth:** High. Reads architecture, judges trade-offs, opens dev tools out of habit.
- **Must answer:** Can he build and *operate* real systems? Does he test? Does he think about security and maintenance? Will he communicate well with stakeholders?
- **Conversion action:** Skim one case study end-to-end → recommend interview; possibly click through to code.
- **Design implications:** Case studies with architecture diagrams, decision/trade-off sections, testing & security notes, honest "known limitations". Site performance and a11y are silent evidence. Metadata in monospace, correct semantics — he *will* view source.
- **Failure mode:** Claims without proof; a slow template site ("if the portfolio is Avada, what will the codebase be?").
## P3 — "Anne", CTO / Head of IT at a Norwegian SME or kommune-adjacent supplier
- **Context:** Small team, hires rarely, values dependability over rockstar energy. Found via application. Reads Bokmål by preference.
- **Time budget:** 25 minutes, often on the Norwegian version.
- **Technical depth:** Medium-high, but evaluates *risk*: will this person maintain legacy systems, talk to users, stay?
- **Must answer:** Public-sector experience? Boring-technology competence (SQL, .NET, servers)? Stability and communication? Settled in Norway?
- **Conversion action:** Read About + Experience → forward to colleague → invite to interview.
- **Design implications:** Warwickshire County Council experience framed as *public-sector systems work* (highly relevant in NO). Understated tone (janteloven-compatible). Norwegian copy natively written, not translated-sounding. "Bosatt i Tønsberg, gyldig oppholdstillatelse" visible.
- **Failure mode:** Grandiose self-labels, hype language, English-only.
## P4 — "Priya", Senior Engineer doing interview-loop prep (secondary persona)
- **Context:** Interview is booked; she's preparing questions from his projects. Deep-reads one case study.
- **Time budget:** 10+ minutes.
- **Must answer:** What exactly did he build? Where are the interesting decisions to probe?
- **Conversion action:** Arrives at interview with informed, favourable questions.
- **Design implications:** Case studies must be accurate and honest — everything on the page is fair game in an interview. "What I'd do differently" sections turn probing questions into prepared wins.
---
## Persona → priority matrix
| Section | P1 Recruiter | P2 Eng Manager | P3 CTO (NO) | P4 Engineer |
|---|---|---|---|---|
| Hero (role/stack/location/permit) | ●●● | ●● | ●●● | ● |
| Skills matrix | ●●● | ●● | ●● | ● |
| CV download | ●●● | ● | ●● | ● |
| Case studies | ● | ●●● | ●● | ●●● |
| Experience timeline | ●● | ●● | ●●● | ● |
| About (human) | ● | ● | ●●● | ● |
| Contact | ●●● | ●● | ●●● | ● |
| Norwegian locale | ●● | ○ | ●●● | ○ |
**Reading:** The homepage top half serves P1/P3; the middle and project pages serve P2/P4. Nothing P2 needs may push P1's answers below the fold.
+57
View File
@@ -0,0 +1,57 @@
# CANONICAL_CONTENT.md
Master source of truth for all site + CV copy (English). Provided by Connor, July 2026. All Phase 2/3 content derives from this file; the Norwegian versions are written natively against these *facts*, not translated sentence-by-sentence.
---
## Identity & contact
- **Name:** Connor Babbington
- **E-mail:** connor.babbington@cesnimda.co.uk
- **Phone:** +47 41 33 44 70
- **Website:** cesnimda.co.uk
- **LinkedIn:** via redirect `cesnimda.co.uk/Linkedin`
- **Location:** Tønsberg, Norway — valid residence permit
- **Languages:** English (native) · Norwegian A2/B1, actively developing
- **Availability:** open to remote, hybrid, or on-site developer roles
## Profile (canonical facts behind the About section)
- Systems developer, eight years in UK local government (20152023).
- Full-stack: backend, frontend, and server administration.
- Analyses and plans user needs; programs and tests with modern languages/frameworks; designs robust, scalable systems.
- Collaborates with developers, designers, project managers, and customers.
- Strong experience: Azure DevOps (CI/CD), GitHub.
- Works well independently and in teams; structured and open-ended tasks.
> Site framing decision (approved direction): self-label is **"Systems Developer"** — the CV's "mid-level" qualifier is dropped everywhere (site *and* reworked CV); seniority is shown through evidence. "Eight years" stays because the apprenticeship was on-the-job development work.
## Experience
### Warwickshire County Council, UK — System Developer · 20152023
- **20152017 was an apprenticeship** → progressed to full developer role. Site treatment: show this *as progression* on the timeline (`20152017 apprentice → 20172023 developer`) — it's an asset (grew into the role, retained 8 years), not something to bury.
- Developed and maintained multiple full-stack applications: **C#, Python, Ruby on Rails, SQL, JavaScript**.
- Delivered reliable, well-tested software; resolved stability issues.
- Translated stakeholder requirements into solutions; adapted across languages/frameworks as projects changed.
- Responsibilities: backend + frontend design/build on several projects; bug fixing for stability and performance; clean maintainable code with testing and documentation; collaborative problem-solving; guided team members on code/process best practices.
- **Concrete anchor (confirmed):** worked on the highways/streetlight fault-reporting system used across the whole of Warwickshire (county-wide public-facing + internal use). Draft site bullet: *"Worked on the county-wide highways and streetlight fault-reporting system used across Warwickshire."* Exact user counts unknown — say "county-wide", never invent numbers.
### 2023 → now (confirmed)
- Self-directed development: JobTrack, InboxIntel, self-hosted infrastructure lab.
- **Norskkurs** — ongoing Norwegian language study. Gets a timeline node (`2023 · Norskkurs + full-stack product development · Tønsberg`); it substantiates the "aktiv språkutvikling" claim and fills the employment gap honestly.
### Side roles (confirmed: part-time alongside the council role — vape shop weekend days, bar evenings/weekend evenings)
- Royal Vapes, UK — Sales Representative · 20172021 (customer service, client issues, communications, payments)
- The Hodcarrier, UK — Bartender · 20162018 (operations, multitasking)
- Nuffield Health, UK — Receptionist · 20142015 (front of house, organisation)
Site treatment per CONTENT_STRATEGY: compact one-liners, de-emphasised, grouped under "Earlier & alongside: customer-facing roles that shaped how I communicate."
## Education
- Warwickshire College, UK · 20122015 — **Extended Diploma NVQ Level 3 in ICT** (programming, systems administration, IT support).
## Interests
PC and board games (strategic thinking, problem solving), cooking, learning new skills. Site treatment: one plain line in About; no icons, no over-explanation.
## Open items — all resolved July 2026
1. ~~Side roles part-time?~~ Confirmed: weekend days (shop) and evenings/weekend evenings (bar), alongside the council role.
2. ~~Concrete Warwickshire detail?~~ Confirmed: county-wide highways/streetlight fault-reporting system (see Experience above).
3. ~~2023→now?~~ Confirmed: self-development + norskkurs (see section above).
4. "Systems Developer, 8+ years" positioning: **approved by Connor.**
+82
View File
@@ -0,0 +1,82 @@
# CONTENT_STRATEGY.md
## 1. Voice & tone
| Attribute | Do | Don't |
|---|---|---|
| **Plain-spoken** | "I build and run web systems." | "Passionate technologist crafting digital experiences." |
| **Evidence-led** | "Replaced spreadsheet-driven reporting workflows for a UK county council." | "Extensive experience with stakeholders." |
| **Understated confidence** (Norwegian-market calibrated) | "Eight years building and maintaining production systems." | "Senior rockstar engineer." |
| **Honest** | "SQLite was the right call for a single-user app; I'd move to Postgres for multi-tenant." | Hiding limitations. |
**Seniority framing decision:** the site never self-labels "senior". Role line = **"Systems Developer"** (EN) / **"Systemutvikler"** (NO), qualified by "8+ years". Seniority is demonstrated through trade-off writing, security notes, and production thinking. This is honest against the CV ("mid-level, eight years"), safe in Norwegian culture, and *more* convincing to P2/P3 than a claimed title.
## 2. Hero copy (approved-draft level)
**EN**
> **Connor Babbington**
> Systems Developer — .NET, full-stack & infrastructure
> I design, build and run web systems: eight-plus years delivering internal software for UK local government, now building full-stack products in Norway.
> Chips: `Tønsberg, Norway` `Work permit ✓` `Open to remote / hybrid / on-site` `English native · Norsk B1`
> CTAs: **[View projects]** **[Download CV]**
**NO (written natively, not translated)**
> **Connor Babbington**
> Systemutvikler — .NET, fullstack og infrastruktur
> Jeg utvikler og drifter websystemer. Åtte års erfaring med fagsystemer i britisk offentlig sektor — nå bygger jeg fullstack-løsninger fra Tønsberg.
> Chips: `Bosatt i Tønsberg` `Gyldig oppholdstillatelse` `Åpen for remote / hybrid / oppmøte` `Engelsk morsmål · Norsk B1`
> CTAs: **[Se prosjekter]** **[Last ned CV]**
## 3. Section content rules
- **Skills:** grouped chips + one context line per group. Never percentage bars, never star ratings. Groups: *Development* (C#, .NET, Python, JavaScript/TypeScript, React, SQL) · *DevOps & Infrastructure* (Docker, Linux, CI/CD, Azure DevOps, nginx/reverse proxies, monitoring, self-hosting) · *Practices* (testing, security hardening, OAuth2 integrations, stakeholder communication, production support). Each chip must be defensible in an interview.
- **Experience:** the 20152023 Warwickshire role gets 34 highlight bullets (outcomes, not duties), with the 20152017 apprenticeship shown as *progression* on the timeline (apprentice → developer — a retention/growth signal, not a footnote). Non-dev roles (sales, bartender, receptionist — held alongside/before the council role) collapse into one line — "Earlier & alongside: customer-facing roles that shaped how I communicate" — present for timeline honesty, de-emphasised visually. NO version frames council work as "fagsystemer for lokalforvaltning". Canonical facts: see CANONICAL_CONTENT.md.
- **About:** 3 short paragraphs — (1) how I work, (2) the homelab/personal-projects habit as evidence of currency, (3) life in Norway + language journey + interests (games → strategic thinking framing kept light). Photo: the outdoor headshot (warm, approachable); the suit photo reserved for CV/LinkedIn.
## 4. Case-study content sources & angles
### JobTrack (`/projects/jobtrack`)
- **Source:** `D:\Job tracker\website_details.md` (already portfolio-shaped — reuse its headline, features, use cases) + README (stack: React, ASP.NET Core (.NET 9), SQLite, FastAPI summarizer, Ollama, Docker, PWA share-target).
- **Angle:** *product thinking + integration depth.* Story: "I had a real problem (job hunting), built a real tool, then hardened it like production software."
- **Decision candidates:** SQLite vs Postgres for single-user; local AI (Ollama) vs cloud API (privacy + cost); PWA share-target vs native app; no offline cache by design (deploy freshness) — this one is *gold*: a deliberate anti-feature with reasoning.
- **Security notes:** Google ID-token auth, ownership checks, secure file uploads.
### InboxIntel (`/projects/inboxintel`)
- **Source:** repo README + docs/ARCHITECTURE.md.
- **Angle:** *architecture discipline.* Story: "Clean Architecture in practice: four-layer .NET solution, background sync worker, safe-by-design destructive operations."
- **Decision candidates:** Clean Architecture layering & dependency rule; encrypted-at-rest OAuth refresh tokens (Data Protection API); preview-then-confirm for all destructive cleanup; advisory-only AI layer; Polly retry/backoff against Gmail API.
- **Honesty note:** README calls it a scaffold with marked extension points — the case study says "in active development" with a roadmap, and status chip `In development`. Never overclaim; P4 will probe.
### Homelab (`/projects/homelab`) — capability page, lighter template
- Ubuntu + Docker services, reverse proxy, auth, monitoring, self-hosted Gitea (git.cesnimda.uk — itself proof), backups. Angle: *operations competence* — "I don't just deploy; I keep things running." Include a small topology diagram.
### The site itself (footer meta-link)
- One short page/footnote: performance budget, accessibility choices, bilingual architecture. Written *after* implementation (Phase 3 content); designed now as a P2 hook.
## 5. Localisation strategy (content level)
1. **Write EN and NO as siblings, not source→target.** NO sentences should be shorter and plainer; Bokmål tolerates directness English pads out.
2. **Equivalence, not identity:** the NO About may spend more words on the Norway/language story; the EN version more on UK career detail. Facts identical; emphasis local.
3. **Glossary (fixed term pairs):** Systems Developer/Systemutvikler · case study/prosjektgjennomgang · public sector/offentlig sektor · work permit/oppholdstillatelse · experience/erfaring · skills/kompetanse · self-hosted/egendriftet. Keep product names (JobTrack, InboxIntel) and technology names untranslated.
4. **Quality gate:** a native/fluent Bokmål speaker reviews all NO copy before launch (flagged as an explicit pre-launch task — the current CV's Norwegian has minor tells, e.g. "Ytret fremragende kundeservice" should be "Ytet…"; the site must be cleaner than the CV).
5. **Tone parity:** understated in both; the NO version must never read as marketing-translated.
## 6. CV improvement recommendations (assets provided → must improve)
The current PDFs are two-column, heavily letter-spaced graphical CVs. Problems: (a) letter-spaced headings ("E X P E R I E N C E") and two-column order break ATS parsing badly — text extraction confirms scrambled reading order; (b) content undersells projects; (c) "mid-level" self-label undercuts an 8-year record.
**Recommendations (content design, no implementation):**
1. Produce a **single-column, ATS-safe layout**: standard headings (Experience, Education, Skills, Projects), no letter-spacing tricks, real text, consistent date formats.
2. Replace "Mid-level system developer" with **"Systems developer with eight years' experience"** in both languages.
3. Add a **Projects section** (JobTrack, InboxIntel, homelab — 2 lines each with stack) — currently the CV omits his strongest recent evidence entirely.
4. Split the 20152023 role into outcome bullets mirroring site copy (single source of truth with the site content model).
5. Fix Norwegian errors ("Ytret" → "Ytet", "holdt baren oppdatert på lager" → "holdt baren velfylt"); native review pass.
6. Add the website URL prominently; site and CV cross-promote.
7. Keep a designed "pretty" variant for humans if desired, but the *download default* is the ATS-safe version. Filenames: `Connor-Babbington-CV-EN.pdf` / `Connor-Babbington-CV-NO.pdf`.
## 7. SEO content (conceptual)
- Title patterns: `Connor Babbington — Systems Developer (.NET, React) · Tønsberg, Norway` / `Connor Babbington — Systemutvikler (.NET, React) · Tønsberg`. Case studies: `JobTrack — case study · Connor Babbington`.
- Meta descriptions handwritten per page per language (≤ 155 chars), answering "who/what/where".
- Structured data (conceptual): `Person` on home (name, jobTitle, address locality, sameAs → LinkedIn/Gitea), `SoftwareSourceCode`/`CreativeWork` per case study.
- OpenGraph: per-language OG images (name + role line + accent motif; generated per design system).
@@ -0,0 +1,91 @@
# INFORMATION_ARCHITECTURE.md
## 1. Site map
```
/ Home (EN default)
/no Hjem (Bokmål)
├── /projects Projects index (teaser cards) /no/prosjekter
│ ├── /projects/jobtrack Case study: JobTrack /no/prosjekter/jobtrack
│ ├── /projects/inboxintel Case study: InboxIntel /no/prosjekter/inboxintel
│ └── /projects/homelab Capability page: Self-hosted /no/prosjekter/hjemmelab
│ infrastructure lab
├── /experience Experience & timeline /no/erfaring
├── /about About + photo + languages + interests /no/om-meg
├── /contact Contact (form + direct channels) /no/kontakt
└── /cv CV hub (EN/NO download, web-readable /no/cv
version, ATS note)
404 Styled, bilingual, links home
```
**Depth rule:** nothing is more than 2 levels deep. Home teases everything; dedicated pages carry depth. This replaces the current one-pager because case studies need URLs of their own (shareable by recruiters into ATS/Slack, individually indexable per language).
**Home remains a narrative one-page scroll** (hero → proof → skills → featured projects → experience summary → about teaser → contact CTA) so the P1 fast lane never requires navigation. Deep pages serve P2/P4.
## 2. Navigation model
**Header (persistent, all pages):**
```
[CB monogram] Projects Experience About Contact | [EN/NO] [☾/☀] [Download CV ▾]
```
- Monogram → home. 4 nav items max — recruiters don't explore menus.
- `Download CV ▾` is a split-button: primary action downloads the CV matching current locale; the chevron reveals the other language. Persistent = criterion "CV in ≤1 interaction from any scroll position".
- Header is sticky, shrinks on scroll (see SCROLL_EXPERIENCE.md); backdrop blurs over content.
- Mobile: monogram + CV button + hamburger (full-screen overlay panel, large tap targets, language + theme controls inside the panel top).
**Footer (all pages):** e-mail, LinkedIn, git.cesnimda.uk, CV both languages, language switch repeat, "Built by me — [how this site works]" link (small meta-page or case-study footnote; a deliberate P2 hook).
**Local navigation inside case studies:** right-side "On this page" mini-TOC (desktop ≥1200px only): Problem · Architecture · Decisions · Security · Outcome · Next.
## 3. Homepage section order & rationale
| # | Section | Serves | First-fact placement |
|---|---|---|---|
| 1 | **Hero** — name, role line, 2-line summary, location/permit/availability chips, CTAs (View projects / Download CV), portrait | P1 P3 | Everything from RECRUITER §2.1 |
| 2 | **Proof strip** — 4 compact stat/fact tiles: `8+ yrs experience` · `UK public sector` · `.NET / React / Docker` · `Based in Tønsberg, NO` | P1 | Reinforces scan pass |
| 3 | **Skills matrix** — 3 columns (Development / DevOps & Infrastructure / Practices), grouped chips with context lines, no percentage bars | P1 P2 | Keyword match |
| 4 | **Featured projects** — 2 large case-study cards (JobTrack, InboxIntel) + 1 slim homelab card; each: screenshot, 1-line problem, stack chips, "Read case study →" | P2 P4 | Deep-lane entry |
| 5 | **Experience** — compact vertical timeline (System Developer 20152023 emphasised; earlier roles collapsed one-liners) | P1 P3 | Employment verification |
| 6 | **About teaser** — photo, 3 sentences, language levels, "More about me →" | P3 | Human trust |
| 7 | **Contact CTA band** — "Looking for a systems developer in Norway?" + e-mail button + form link | all | Conversion |
## 4. Case study page template (both projects share it)
```
1. Header: project mark + name, one-line value prop, status chip (Active / In development),
stack chips, links (live demo if applicable · repository)
2. Hero screenshot (framed browser mock, themed)
3. TL;DR box — 4 bullets: What / Why / Stack / My role ← the only part P1 reads
4. Problem & context
5. Architecture — diagram first, then short prose (frontend / API / data / workers / integrations)
6. Key decisions & trade-offs — 35 numbered decisions, each: choice, alternative, why
7. Security & production notes — auth, secrets, encryption-at-rest, backups, CI
8. Screenshots gallery — 35 annotated captures
9. What I'd improve next — honest, specific
10. Footer nav: ← other project | All projects
```
Content per project sourced from `website_details.md` (JobTrack) and InboxIntel README/docs — see CONTENT_STRATEGY.md §4.
## 5. Bilingual architecture (conceptual)
- **URL strategy:** path-prefix locales. English at root (`/projects/jobtrack`), Norwegian under `/no/` with **localised slugs** (`/no/prosjekter/jobtrack`). Rationale: root-EN keeps existing inbound links and international reach; `/no/` prefix is the conventional, SEO-clean pattern; localised slugs signal genuine Norwegian content.
- **Language switch behaviour:** switches to the *same page* in the other language (per-page mapping table), never to the other homepage. Preference remembered for return visits; first visit may show a one-time, dismissible "Denne siden finnes på norsk" hint if the browser prefers Norwegian — never an auto-redirect (recruiters share links across languages; links must stay stable).
- **Parity rule:** every page exists in both languages; content is *equivalent, not identical* (CONTENT_STRATEGY §5). If a page ever ships EN-first, the NO version shows a short native-Norwegian summary + "full versjon på engelsk" link — never machine output, never a broken switch.
- **SEO (conceptual):** `hreflang` pairs on every page + `x-default` → EN; per-language titles/descriptions/OpenGraph; per-language sitemap entries; canonical per locale (no cross-language canonicals — the versions are alternates, not duplicates).
## 6. Content model (design-level, informs Phase 2 data model)
```
Profile name, roleLine ×2 lang, summary ×2, chips (location, permit,
availability, languages), photo variants, links
SkillGroup title ×2, ordered skills[] {name, contextLine ×2 (optional)}
Project slug ×2, name, valueProp ×2, status, stack[], links[],
tldr ×2, sections[] ×2 (problem/architecture/decisions/
security/next), media[] {image, alt ×2, caption ×2}, diagram
ExperienceItem employer, role ×2, period, location, summary ×2,
highlights[] ×2, emphasis (featured | compact)
Page meta title ×2, description ×2, ogImage per lang
```
Every user-visible string exists per-language by construction — this is the "avoid duplication issues" answer: one structural source, two content channels, no forked page trees.
+52
View File
@@ -0,0 +1,52 @@
# USER_JOURNEYS.md
Five journeys validated against the IA. Format: step → what they see → design element that makes it succeed.
---
## J1 · Recruiter Silje — LinkedIn → CV in ATS (target: < 60 s)
1. Clicks site link on LinkedIn (mobile). → Hero: "Connor Babbington — Systems Developer · .NET / React / Docker · Tønsberg, Norway · Open to work." Portrait matches LinkedIn photo. *(Identity confirmed in ~3 s.)*
2. Thumb-scrolls once. → Proof strip chips: 8+ yrs · UK public sector · work permit ✓ · EN native/NO B1. *(All screening objections cleared without reading prose.)*
3. Scrolls skills matrix. → Keyword match against job spec: C#, .NET, SQL, React, Docker, CI/CD. *(Chips, not bars — parseable at a glance.)*
4. Taps persistent **Download CV** in header. → Gets `Connor-Babbington-CV-EN.pdf` (locale-matched). *(Split-button defaults correctly; no hunting.)*
5. Done — candidate shortlisted. **Exit quality: converted without ever leaving the fast lane.**
## J2 · Eng manager Martin — shortlist e-mail → interview recommendation (target: ≤ 7 min)
1. Opens link on desktop, dark IDE habits → site respects `prefers-color-scheme`, loads instantly. *(First silent evidence.)*
2. Skims hero, ignores chips, clicks **Projects** in nav. → Two case-study cards with real screenshots + stack chips. Picks InboxIntel (Gmail/OAuth catches his eye).
3. Case study: reads TL;DR box, jumps via mini-TOC to **Architecture**. → Clean-architecture diagram: React SPA → ASP.NET API → PostgreSQL, background sync worker, Gmail API, token encryption. *(Diagram before prose — his reading order.)*
4. Reads **Decisions & trade-offs** ("why a hosted worker instead of a queue", "why advisory-only AI"). → Judgement demonstrated, not claimed.
5. Reads **Security & production notes** (encrypted refresh tokens, confirmed-flag destructive ops, integration tests asserting authz). → *This* is the interview trigger for a production-minded manager.
6. Opens dev tools out of habit → semantic HTML, no framework soup, fast. Checks the footer "how this site works" link, smirks approvingly.
7. Replies to recruiter: "Yes, bring him in — ask about the sync worker." **Exit quality: P4 (interviewer prep) journey pre-seeded.**
## J3 · CTO Anne (Norwegian) — application → forwarded to colleague (target: ≤ 5 min)
1. Opens link from application e-mail. Browser prefers `nb-NO` → dismissible hint: "Denne siden finnes på norsk →". Clicks it. → `/no` with natively-written Bokmål. *(Effort signal received before any content.)*
2. Hero: "Systemutvikler · åtte års erfaring fra britisk offentlig sektor · bosatt i Tønsberg." *(Public-sector frame + local residence = risk questions answered.)*
3. Reads **Erfaring**: Warwickshire County Council role described in kommune-relatable terms (saksbehandlingssystemer, rapportering, drift).
4. Reads **Om meg**: photo, plain-spoken 3 paragraphs, "norsk B1, i aktiv utvikling", interests. *(Understated tone; janteloven-compatible.)*
5. Copies URL of `/no/prosjekter/jobtrack` into e-mail to her senior dev. *(Per-language, per-page URLs make forwarding work.)*
**Exit quality: converted in Norwegian end-to-end; never saw English.**
## J4 · Engineer Priya — interview prep deep-read (target: 10+ min, accuracy critical)
1. Arrives directly on `/projects/jobtrack` (link from Martin). Reads whole case study top-to-bottom.
2. Mini-TOC lets her jump back to Architecture while reading Decisions. Screenshots are annotated so she can reference specific UI ("the Gmail import view").
3. **What I'd improve next** gives her interview questions the candidate is *prepared for*.
**Design requirement surfaced: every claim in case studies must be true and demoable.**
## J5 · Return visitor / language edge cases
- **Language switch mid-page:** Anne's colleague (English-speaking) receives the `/no/...` link → header `EN` switch → lands on `/projects/jobtrack`, same scroll context (same page mapping, position preserved where feasible).
- **Theme:** system default on first visit; manual toggle persists; no flash of wrong theme on load (Phase 2 must solve this; Phase 1 flags it as a perceived-quality requirement).
- **404:** mistyped shared link → bilingual 404 with links to both homepages and projects index. Never a dead end for a recruiter.
- **Slow connection (mobile, train to Oslo):** text renders first; screenshots lazy-load below fold with fixed aspect-ratio placeholders (no layout shift — see MOTION_GUIDELINES loading states).
---
## Journey-derived requirements checklist
- [x] Locale-matched CV default with one-tap access anywhere (J1)
- [x] Architecture diagram ≤ 2 clicks from entry (J2)
- [x] Security/testing content in every case study (J2)
- [x] Native Bokmål, public-sector framing (J3)
- [x] Stable per-language deep links; switch maps page↔page (J3, J5)
- [x] Honest, demoable case-study claims (J4)
- [x] No auto-redirect on language; hint pattern only (J3, J5)
- [x] Zero layout shift on image load (J5)
+58
View File
@@ -0,0 +1,58 @@
# COLOUR_SYSTEM.md
## 1. Palette philosophy
Near-monochrome surfaces + **one** accent ("aurora" teal-green — a nod to Norway without cliché flags/fjords) + **one** functional secondary (amber, status-only). The accent behaves like a **status LED**: it marks liveness, focus, and action — it is never decoration or large fills.
## 2. Core tokens
### Dark theme (design lead)
| Token | Value | Contrast vs surface-0 | Use |
|---|---|---|---|
| `surface-0` | `#0B0E14` | — | Page bg (blue-black, not pure black) |
| `surface-1` | `#11151D` | — | Cards |
| `surface-2` | `#171C26` | — | Nested panels, TL;DR |
| `ink` | `#E8ECF2` | 15.4:1 | Headings, body |
| `ink-muted` | `#9AA4B2` | 7.1:1 | Secondary text, captions |
| `ink-faint` | `#5C6675` | 3.4:1 | Disabled, decorative only (never body) |
| `border` | `#E8ECF2` @ 8% | — | Hairlines |
| `accent` | `#3ECFAE` | 9.8:1 | Links, primary buttons (dark text on it), status dots, trace motif, focus rings |
| `accent-ink` | `#062A22` | 12:1 on accent | Text on accent fills |
| `amber` | `#E5A93D` | 8.3:1 | `IN DEVELOPMENT` chips, form warnings only |
| `danger` | `#E5606B` | 5.6:1 | Form errors only |
### Light theme
| Token | Value | Contrast vs surface-0 | Use |
|---|---|---|---|
| `surface-0` | `#FAFAF8` | — | Warm paper white |
| `surface-1` | `#FFFFFF` | — | Cards |
| `surface-2` | `#F1F2EF` | — | Nested panels |
| `ink` | `#171A20` | 15.9:1 | Text |
| `ink-muted` | `#555E6B` | 6.8:1 | Secondary |
| `border` | `#171A20` @ 10% | — | Hairlines |
| `accent` | `#0E8C72` | 4.9:1 | Links, buttons (white text: 5.1:1), focus |
| `amber` | `#8A6114` | 5.6:1 | Status text (paired w/ `#F5E5C2` chip bg) |
| `danger` | `#B3323E` | 6.2:1 | Errors |
**Note:** the accent is *perceptually the same identity* in both themes but shifted per theme to hold WCAG AA (dark uses the luminous `#3ECFAE`; light uses the deepened `#0E8C72`). This is deliberate — never reuse the dark accent on white (2.1:1, fails).
## 3. Usage rules
1. **Accent budget:** ≤ 3 accent moments per viewport (e.g. hero: trace line + primary CTA + one chip dot). If a screen feels flat, fix hierarchy with type/spacing — not more accent.
2. **Large fills:** accent may fill only buttons and chips; never section backgrounds or headings-as-blocks.
3. **Text on accent:** always `accent-ink`/white per table; never accent-on-accent.
4. **Status semantics (fixed):** accent dot = active/live · amber dot = in development · muted dot = archived. Consistent across chips, timeline, project cards.
5. **Charts/diagrams:** surfaces + border + ink-muted, accent for the *one* flow being explained, amber for external systems (e.g. Gmail API node). Max 4 hues in any diagram.
6. **Duotone photo treatment (dark):** shadows → `#0B0E14`, highlights → warm neutral; accent never tints skin.
7. **Never colour-only meaning:** every status pairs dot + text label; errors pair colour + icon + message.
## 4. Gradients
Exactly one permitted gradient: hero background halo — radial, accent @ 6% → transparent, anchored behind the trace motif. Everything else flat. (The Stripe lesson: one identity moment, not a gradient system.)
## 5. Accessibility audit summary
- All body/secondary text ≥ 4.5:1 in both themes (see tables).
- UI components/borders on interactive elements ≥ 3:1 against adjacent colours.
- Focus ring `accent` at 2px passes 3:1 against both surface-0 values.
- `ink-faint` is documented decorative-only. High-contrast/forced-colors mode: dot-grid and halo disabled, hairlines → CanvasText.
+80
View File
@@ -0,0 +1,80 @@
# DESIGN_SYSTEM.md
Design direction: **"Kontrollrom"** (control room) — a quiet, Nordic-restrained engineering aesthetic.
---
## 1. Concept
The site should feel like **well-run production infrastructure given a public face**: calm surfaces, precise alignment, information-dense but never busy, one living accent colour that behaves like a status light. Not a marketing site pretending to be technical; a technical artefact polished until it feels premium.
Three ideas fused, none copied:
1. **Nordic restraint** — generous whitespace, muted surfaces, understatement (matches the Norwegian market and the candidate's voice).
2. **Console/ops vernacular as *seasoning*** — monospace metadata labels, status chips, subtle grid/blueprint texture. Used at ~10% intensity: labels, captions, chips — never full terminal cosplay, no fake CLIs, no green-on-black.
3. **SaaS product craft** — case studies presented like product launch pages (framed screenshots, TL;DR boxes, proof strips).
**Litmus test for every design decision:** *"Would this look at home in the admin console of a product you trust?"* If it's decorative noise — cut it.
## 2. Identity elements
- **Monogram:** `CB` set in the mono face inside a rounded square, with a 2px accent tick in the corner — reads as both initials and a status LED. Used as favicon, header mark, OG-image anchor.
- **Signature motif — "the trace":** a thin (1.5px) accent line that draws horizontally then steps (like an oscilloscope/PCB trace). Appears once in the hero (animated draw on load), as section-divider punctuation, and in the 404. This is the site's single ownable visual move.
- **Status chips:** rounded-full, mono, 12px, used for facts (location, permit, availability) and project status (`ACTIVE`, `IN DEVELOPMENT`). The chip *is* the brand's information atom.
- **Photography:** the outdoor headshot, duotone-treated in dark mode (slight cool grade) and natural in light mode; always in a hairline-bordered rounded frame with a mono caption (`tønsberg, norway · 2025`).
## 3. Surfaces & elevation
Flat-first. Depth via **hairline borders (1px, low-alpha)** and background steps, not drop shadows.
| Token | Dark | Light | Use |
|---|---|---|---|
| `surface-0` | #0B0E14 | #FAFAF8 | Page background |
| `surface-1` | #11151D | #FFFFFF | Cards, panels |
| `surface-2` | #171C26 | #F1F2EF | Nested/hover panels, code-ish blocks |
| `border` | #FFFFFF @ 8% | #16181D @ 10% | All hairlines |
| `border-strong` | #FFFFFF @ 16% | #16181D @ 20% | Focused/hover cards |
Shadows: only two, both subtle, only in light mode + overlays (`shadow-card`, `shadow-overlay`). Dark mode uses border-strong + surface step instead.
Radius scale: `4px` (chips inner) · `8px` (buttons, inputs) · `12px` (cards) · `16px` (screenshot frames) · `full` (chips).
## 4. Grid & layout
- Max content width **1200px**; case-study prose measure capped at **72ch**.
- 12-column grid, 24px gutters desktop; 4-column, 16px gutters mobile.
- Left-rail section labels on desktop (mono, vertical rhythm anchor): `01 — ABOUT`, `02 — SKILLS`… echoing the current site's numbering, done properly.
- Breakpoints: 480 / 768 / 1024 / 1280 / 1536.
- Background texture: an extremely faint dot-grid (border-colour dots, 24px pitch) on `surface-0` hero + section headers only — the "blueprint" whisper. Disabled in forced-colors/high-contrast.
## 5. Core components (design-level inventory)
| Component | States to design | Notes |
|---|---|---|
| Header / nav | default, scrolled (compressed + blur), mobile overlay | see mockups M7 |
| Split CV button | default, hover, open (menu shows both languages + file size) | locale-aware default |
| Language switch | EN active, NO active, hover, focus, keyboard | segmented control, see M11 |
| Theme toggle | dark, light, hover (icon morph sun⇄moon) | respects system on first load |
| Status chip | fact, status-active (accent dot pulse ≤ 2 loops), status-dev (amber dot) | |
| Skill group card | default, hover (border-strong) | chips inside |
| Project card | default, hover (screenshot parallax-shift 4px + border glow), focus | whole card clickable, single tab stop |
| Timeline item | featured (expanded), compact (one-liner), hover | |
| TL;DR box | — | surface-2, accent left edge (3px) |
| Architecture diagram frame | static SVG, nodes highlight on hover (desktop only) | must read perfectly with zero interaction |
| Screenshot frame | loading (skeleton), loaded, lightbox open | browser-chrome mock, aspect-locked |
| Form field | default, focus, filled, error, success | error text + icon, never colour-only |
| Buttons | primary (accent), secondary (outline), ghost | see MICRO_INTERACTIONS |
| Footer | — | dense, mono-flavoured, sitemap + channels |
| 404 | — | trace motif breaks/frays; bilingual links home |
## 6. Iconography & illustration
- Icons: single stroke-icon set, 1.5px stroke, 20px grid (Lucide-class aesthetic — final set is a Phase 2 choice). Never filled+outlined mixed.
- No stock illustration, no 3D blobs, no particles. The only "illustrations" are: the trace motif, architecture diagrams (drawn in-system: surface-2 nodes, border hairlines, accent flow arrows, mono labels), and framed product screenshots.
## 7. Theming rules
- Dark is the **design-lead theme** (P2 audience default; screenshots of both apps present well on dark), light must be equally finished — P1/P3 recruiters often browse light.
- Every token has a pair; no theme-conditional layouts. Accent identical in both (passes contrast both ways — see COLOUR_SYSTEM).
- First paint must match user preference without flash (implementation constraint flagged for Phase 2).
## 8. What this system deliberately avoids
Percentage skill bars · testimonial carousels · particle heroes · scroll-hijacking · fake terminals · gradient-everything · cookie-consent theatre (no tracking → no banner; a privacy footnote instead — itself a trust signal).
@@ -0,0 +1,50 @@
# MOTION_GUIDELINES.md
## 1. Motion philosophy
**"Motion confirms; it never performs."** Every animation must answer a user question — *did my click register? what changed? where did this come from?* — or it doesn't ship. One exception: the hero trace draw, the site's single identity moment.
This is the strongest anti-junior signal available: restraint executed perfectly.
## 2. Tokens
| Token | Duration | Easing | Use |
|---|---|---|---|
| `instant` | 80ms | linear | Colour/opacity on hover |
| `quick` | 160ms | ease-out (0.2, 0, 0, 1) | Buttons, chips, toggles, focus |
| `standard` | 240ms | ease-out | Card hover lift, menu open, theme cross-fade |
| `entrance` | 400ms | ease-out, slight overshoot-free | Section reveals (opacity + 12px rise) |
| `signature` | 700ms | ease-in-out | Hero trace draw only, once per page load |
Rules: nothing exceeds 700ms; entrances stagger max 3 items × 60ms; **only `transform` and `opacity` animate** (performance budget — no layout/box-shadow/filter animation); parallax capped at 4px (project-card screenshots).
## 3. Reduced motion (first-class variant, not an off-switch)
`prefers-reduced-motion: reduce`
- Entrances become pure opacity fades (120ms), no translation.
- Trace motif renders complete, static.
- Theme switch: instant swap.
- Status-dot pulse: static dot.
- Scroll behaviour: instant anchor jumps, no smooth scroll.
Nothing is *removed* — content parity is exact.
## 4. Loading states
| Context | Design |
|---|---|
| Initial page | No spinner, no splash: text renders immediately (fonts swap-safe), hero trace draws as the "loaded" confirmation. Perceived performance *is* the loading design. |
| Screenshots / gallery | Aspect-ratio-locked skeleton: `surface-2` block + faint dot-grid + mono caption already visible (`loading capture…` / `laster skjermbilde…`); cross-fade 240ms on load. Zero layout shift, ever. |
| Form submit | Button label → inline three-dot mono ellipsis animation (·, ··, ···), button stays same width; success swaps band to confirmation panel (see MICRO_INTERACTIONS §form). |
| Language switch | Content cross-fade 160ms; header/footer chrome does not blink (only text nodes swap). |
| Lightbox open | Backdrop fade 240ms + image scale 0.98→1. |
## 5. Page & theme transitions
- Route changes: outgoing fade 120ms → incoming rise-fade 240ms, header persistent. No full-screen wipes.
- Theme toggle: 240ms token cross-fade; the toggle icon morphs (sun⇄moon path) in `quick`. Screenshots swap themed variants where available (JobTrack/InboxIntel both have dark UIs — capture both if the apps support it, else keep one honest capture).
## 6. Scroll-linked motion (summary — full spec in SCROLL_EXPERIENCE.md)
- Section reveals trigger at 20% viewport entry, once (no re-trigger on scroll-up).
- Header compresses 72→56px between 0120px scroll, linear-mapped.
- No scroll-jacking, no pinned scenes, no horizontal hijack — recruiters skim; the scrollbar must behave like a document's.
+28
View File
@@ -0,0 +1,28 @@
# SPACING_SYSTEM.md
## 1. Scale (8pt base, 4pt half-step)
| Token | px | Use |
|---|---|---|
| `space-1` | 4 | Chip inner gaps, icon-to-label |
| `space-2` | 8 | Inside chips/buttons vertical, tight stacks |
| `space-3` | 12 | Button horizontal padding unit, list gaps |
| `space-4` | 16 | Card inner padding (mobile), paragraph gaps |
| `space-5` | 24 | Card inner padding (desktop), grid gutters |
| `space-6` | 32 | Between related blocks |
| `space-7` | 48 | Between component groups |
| `space-8` | 64 | Section inner top/bottom (mobile) |
| `space-9` | 96 | Section rhythm (desktop) |
| `space-10` | 128 | Hero breathing room, page top/bottom |
Only these ten values exist. No 20px, no 30px, no eyeballing.
## 2. Rules
1. **Section rhythm:** homepage sections separated by `space-9` desktop / `space-8` mobile; the mono section label sits `space-6` above its H2.
2. **Proximity encodes relationship:** gap within a skill group (`space-2`) < gap between groups (`space-5`) < gap between sections (`space-9`). If two things feel related but aren't, increase the gap before adding a divider; hairlines are the *second* resort.
3. **Cards:** `space-5` padding desktop, `space-4` mobile; media flush to card edge (screenshots bleed to the frame), text content padded.
4. **Touch targets:** min 44×44px tap area on mobile (chips visually smaller but hit-area padded); nav/footer link spacing ≥ `space-3` vertically on mobile.
5. **Header:** 72px tall default, compresses to 56px on scroll; content top-padding accounts for it (anchor scroll offset = header height + `space-5`).
6. **Prose:** paragraph gap `space-4`; heading top `space-7`/bottom `space-3` (the 2:1 before/after rule from TYPOGRAPHY_GUIDE).
7. **Density gradient:** marketing-calm at top of pages → denser in technical sections (case-study decision lists, footer). Density shift is achieved by stepping down *one* spacing token, never by shrinking type below `small`.
+43
View File
@@ -0,0 +1,43 @@
# TYPOGRAPHY_GUIDE.md
## 1. Type roles (three voices, strict casting)
| Role | Direction | Character | Used for |
|---|---|---|---|
| **Display / headings** | Modern grotesque with slight technical edge — *Space Grotesk-class* (alternatives in that register: General Sans, Hanken Grotesk) | Confident, engineered, not cold | H1H3, nav, buttons, stat numbers |
| **Body** | Highly-legible humanist/neo-grotesque — *Inter-class* (alternatives: Source Sans 3, Figtree) | Invisible, effortless at 1618px | Paragraphs, lists, form fields |
| **Mono (identity voice)** | *JetBrains Mono-class* (alternatives: IBM Plex Mono, Commit Mono) | The "console seasoning" | Section labels, chips, captions, metadata, diagram labels, footer |
Exact typeface licensing/loading = Phase 2. Design intent: **two families max loaded + mono**, variable weights preferred, `ø æ å` coverage mandatory in all three (Norwegian body text is first-class, not fallback).
## 2. Scale (1.25 major-third, 16px base)
| Token | Size / line-height | Weight | Tracking | Use |
|---|---|---|---|---|
| `display` | 61/1.05 (desktop) · 40/1.1 (mobile) | 600 | 0.02em | Hero name only |
| `h1` | 49/1.1 · 34/1.15 | 600 | 0.015em | Page titles |
| `h2` | 39/1.15 · 28/1.2 | 600 | 0.01em | Section titles |
| `h3` | 31/1.2 · 24/1.25 | 550 | 0 | Card titles, case-study subsections |
| `h4` | 20/1.35 | 550 | 0 | Minor headings |
| `body-lg` | 18/1.6 | 400 | 0 | Hero summary, TL;DR, lead paragraphs |
| `body` | 16/1.65 | 400 | 0 | Default prose |
| `small` | 14/1.5 | 400 | 0 | Secondary info, form hints |
| `mono-label` | 12/1.4 | 500 | +0.08em, UPPERCASE | Section labels (`01 — PROSJEKTER`), chips |
| `mono-meta` | 13/1.5 | 400 | 0 | Captions, dates, file sizes |
**Rule: uppercase + letter-spacing lives only in `mono-label`.** (The old site letter-spaced everything; the CV letter-spaces headings into A T S soup. Reserve the effect and it becomes identity instead of noise.)
## 3. Rhythm & measure
- Prose measure 6072ch; hero summary ≤ 55ch.
- Space *before* a heading ≈ 2× space after (sections cohere downward).
- Baseline rhythm on the 8pt grid — line-heights above are chosen to land on it at 16px base.
- Norwegian headline audit: NO strings run ~1015% longer and compounds don't hyphenate freely (`systemadministrasjon`); every headline layout must be tested with the NO string at the mobile breakpoint. Wrapping rule: allow hyphenation in body NO text, never in headings — reword instead.
## 4. Typographic details (the seniority tells)
- Real dashes ( —), curly quotes per language convention (EN “ ” · NO « »), non-breaking spaces before units and inside `8+ yrs`.
- Numbers in stats use tabular lining figures (mono or `tnum`).
- Links in prose: accent-coloured underline (2px offset), full colour+underline — never colour-only (a11y).
- Code/tech names inline (C#, .NET) stay in body face — mono inline is reserved for actual paths/commands in case studies.
- Focus states use a 2px accent outline + 2px offset, same in both themes.
@@ -0,0 +1,50 @@
# ANIMATION_CONCEPTS.md
Named animation concepts, each mapped to the question it answers. All obey MOTION_GUIDELINES tokens & reduced-motion variants.
---
## A1 · "Trace draw" — hero identity moment
On first paint: a 1.5px accent line draws left→right under the role line (700ms `signature`), steps down 8px mid-way (PCB-trace corner), and terminates in a 4px status dot that blinks once, then stays lit next to `Open to work`.
*Question answered:* "the page is ready — and he's available."
Reduced motion: line + lit dot render complete, static. Repeat visits: draws once per session only.
## A2 · "Section arrive"
Each homepage section reveals on 20% viewport entry: opacity 0→1 + 12px rise (400ms), heading first, content children staggered 60ms (max 3 groups). Fires once per page load.
*Question:* "what's new in view?" — creates reading rhythm without withholding content (text is present for search/reader modes regardless).
## A3 · "Card focus"
Project cards on hover/focus-visible: border → `border-strong`, screenshot shifts up 4px (240ms), title gains accent underline draw (160ms), `Read case study →` arrow nudges 4px right.
*Question:* "is this clickable, and what happens?"
## A4 · "Chip pulse"
The `ACTIVE` status dot pulses twice (scale 1→1.4 opacity fade) when its chip first enters viewport, then rests lit. Amber `IN DEVELOPMENT` dots never pulse (calm hierarchy: live > in-progress).
*Question:* "which of these is running right now?"
## A5 · "Diagram flow" (case-study architecture, desktop only)
Architecture diagrams are static SVGs; hovering a node highlights its connected edges in accent (160ms) and dims unrelated nodes to 60%. No autoplay, no looping data-packet animations.
*Question:* "what talks to what?"
Mobile/touch + reduced motion: full static diagram, always fully legible without interaction.
## A6 · "Theme morph"
Sun⇄moon icon path-morph (160ms) + global 240ms token cross-fade. The hero trace re-tints without redrawing.
## A7 · "Language cross-fade"
On EN⇄NO switch: text nodes fade out 80ms / in 160ms; layout containers keep dimensions during swap (pre-measured against longer NO strings) so nothing jumps. The switch control itself slides its active-segment thumb (160ms).
*Question:* "same page, other language — nothing lost."
## A8 · "Timeline grow"
Experience timeline's vertical hairline draws downward as the section enters (400ms); period dots fade in with their rows.
*Question:* "read this top-down, newest first."
## A9 · "Form confirm"
On successful submit, the form panel cross-fades to a confirmation card: accent check icon draws (240ms stroke), message + "I usually reply within a day" (`Jeg svarer vanligvis innen en dag`).
*Question:* "did it actually send?" — the current site's broken-looking send feedback (raw glyphs) is exactly what this replaces.
## A10 · "404 fray"
The trace motif runs, stutters, and frays into dots at the break point; mono caption `route not found — no such page` / `fant ikke siden`. Static in reduced motion.
*Purpose:* the one place allowed personality-forward motion — errors are where craft shows.
---
### Explicitly rejected concepts (documented so Phase 3 doesn't re-invent them)
Typewriter hero text (junior tell) · particle backgrounds · 3D tilt cards · scroll-pinned storytelling · animated skill counters · magnetic buttons (see CURSOR_INTERACTIONS) · autoplaying screenshot carousels.
@@ -0,0 +1,25 @@
# CURSOR_INTERACTIONS.md
## Verdict: near-zero custom cursor work — by design
Custom cursors (dot followers, magnetic buttons, cursor-morphing) were evaluated and **rejected** for this site. Reasons, documented so the decision survives future redesign itch:
1. **Audience mismatch.** P1 recruiters on trackpads/mobile never see it; P2 engineering managers disproportionately *dislike* cursor gimmicks (input latency, hijacked expectations).
2. **Brand mismatch.** "Kontrollrom" promises precision and restraint; a floating blob promises an agency reel.
3. **Cost.** Pointer-following JS is the classic source of main-thread jank — the exact failure mode this site must never exhibit.
## What ships instead (native cursor semantics, used *correctly*)
| Context | Cursor | Note |
|---|---|---|
| Links, buttons, cards, TOC | `pointer` | Whole project card = pointer (it's one link) |
| Fact chips (non-interactive) | `default` | Honest affordance — no fake clickability |
| Text/prose | `text` | Never suppressed |
| Screenshot in lightbox (zoomable) | `zoom-in` / `zoom-out` | The one "delight" allowed |
| Click-to-copy e-mail | `copy` | Native, semantically perfect, zero JS cost |
| Diagram nodes with hover-highlight | `default` + visual highlight | Hover is enhancement, not interaction — pointer would over-promise |
| Disabled controls | `not-allowed` | |
## The single permitted flourish
On the hero, the status dot at the end of the trace shows a `crosshair`-style precision hover halo (pure CSS, 160ms) revealing a mono tooltip: `status: open to work` / `status: åpen for muligheter`. Desktop-only easter egg; invisible to touch, absent under reduced motion, no positional JS. If it ever measures as jank — cut it without debate.
@@ -0,0 +1,54 @@
# MICRO_INTERACTIONS.md
Component-level interaction specs. Every interactive element defines: rest / hover / active / focus-visible / disabled, keyboard behaviour, and touch behaviour.
---
## Buttons
- **Primary (accent fill):** hover → brightness +6% and 1px rise (transform); active → rise removed (press); focus-visible → 2px accent ring, 2px offset; disabled → surface-2 + ink-faint, no events.
- **Secondary (outline):** hover → border-strong + surface-1 fill.
- **Ghost (nav, footer):** hover → ink (from ink-muted) + accent underline draw.
- Touch: no hover states persist; active state on touchstart (≤80ms feedback).
## Split CV button (header)
- Primary zone downloads locale CV; label: `Download CV` / `Last ned CV`, mono-meta suffix shows size `· pdf 180 kB`.
- Chevron zone (separate 44px hit area) opens a 2-item menu: `English (pdf)` / `Norsk (pdf)` with a subtle flag-free language tag (`EN`/`NO` mono chips — no flag icons anywhere; flags conflate language and nationality).
- Keyboard: button reachable in tab order; chevron = separate stop; menu arrows + Esc. Download triggers a one-time toast-less confirmation: button icon swaps to check for 1.5s.
## Language switch (header, segmented control)
- Two segments `EN | NO`, active segment has surface-1 thumb + ink text; inactive = ink-muted. Hover inactive → ink. Thumb slides 160ms on change (A7).
- Keyboard: single tab stop, arrow keys toggle, Enter/Space activates. `aria` as a proper toggle group; screen reader announces "Norsk — bytt språk / English — switch language" (label in *target* language, the convention that works when you can't read the current one).
- Mobile: inside menu overlay header, full-width segmented control, same behaviour.
- Never a dropdown (two options), never flags, never auto-switching.
## Theme toggle
- Icon-only button, morphing sun⇄moon; tooltip on hover-delay 600ms (`Switch to light mode` / `Bytt til lyst modus` — target-state phrasing).
- First visit follows system; once touched, choice persists and system changes no longer override (standard expectation).
## Nav links
- Rest: ink-muted → hover: ink + 2px accent underline drawing left→right (160ms). Current-page: ink + static accent underline + `aria-current`.
- Scroll-spy on homepage: section currently in view marks its nav item (underline at 60% opacity) — subtle, no jumping.
## Chips
- Fact chips: non-interactive, no hover (cursor default — honesty about affordance).
- Stack chips on project cards: hover shows a 600ms-delay tooltip with one context line ("React 18 — SPA frontend"); tap on mobile: no-op (tooltip content lives in case-study text instead).
## Project cards
- Whole card = one link (single tab stop); internal "Read case study →" is decorative affordance, not a second link. Hover per A3. Focus-visible: ring around entire card.
- Screenshot inside frames: `alt` text written per language, meaningful ("JobTrack applications table with status filters", not "screenshot").
## Timeline items
- Featured item expanded by default. Compact one-liners: entire row hover → surface-1 tint; they are *not* expandable (content lives in CV/experience page — avoid hidden-content traps for scanners).
## Form (contact)
- Fields: 2px border; focus → accent border + ring; label always visible above (no placeholder-as-label). Error on blur-validate: danger border + icon + message under field (`Please enter a valid e-mail` / `Skriv inn en gyldig e-postadresse`); error summary focus-moved on failed submit.
- Submit per A9. Honey-pot antispam invisible to AT (no CAPTCHA — friction kills recruiter conversion).
- Beside the form, equal visual weight: direct e-mail (click-to-copy with `copied ✓ / kopiert ✓` mono feedback) and LinkedIn — many recruiters won't touch forms.
## Screenshot lightbox
- Click/Enter opens; Esc, backdrop-click, or × closes; arrows navigate gallery; focus trapped; caption + `n/N` mono counter. Mobile: pinch-zoom enabled, swipe between captures.
## "On this page" mini-TOC (case studies, ≥1200px)
- Items = ghost links with scroll-spy accent tick; click = smooth scroll (instant under reduced motion) with header-offset anchor.
## Copy affordances
- E-mail and `git.cesnimda.uk` in footer/contact: click-to-copy with mono `copied ✓` swap (1.5s). Phone number: plain `tel:` link, no copy gimmick.
@@ -0,0 +1,45 @@
# SCROLL_EXPERIENCE.md
## 1. Governing rule
**The scrollbar is a contract.** Native scroll physics everywhere: no hijacking, no pinned scenes, no snap-scrolling on prose, no horizontal sections. Recruiters skim with momentum-flicks; anything that interferes reads as hostile.
## 2. Homepage scroll narrative (desktop)
| Scroll position | What happens | Why |
|---|---|---|
| 0 (load) | Hero static except trace draw (A1) | Decision facts readable instantly |
| 0120px | Header compresses 72→56px, gains blur backdrop + hairline bottom border (linear-mapped to scroll, not toggled — no pop) | Reclaims space, signals "we're moving" |
| Each section at 20% entry | A2 section arrive, once | Reading rhythm |
| Proof strip enter | Chip dots light in stagger (accent pulse A4) | Draws eye across the four facts |
| Projects enter | Cards rise in 2-stagger; screenshots have 4px max parallax offset relative to card scroll | Depth without gimmick |
| Experience enter | Timeline draw (A8) | Guides top-down reading |
| Contact band enter | No animation — band is high-contrast already | Calm before conversion action |
| Any position | Header CV button + nav always available | ≤1 interaction to CV rule |
Scroll-spy updates nav underline per section. No "back to top" button on homepage (header is sticky; document is ~5 viewports); case studies (longer) get one after 3 viewports, ghost-style, bottom-right.
## 3. Case-study scroll
- Mini-TOC (≥1200px) tracks reading position with an accent tick; no progress bars (this is a document, not an article funnel — but see next point).
- Long-form sections use generous `space-9` rhythm; architecture diagram gets full-width breakout (max 1200px) beyond the 72ch prose measure.
- Images lazy-load 1 viewport ahead with aspect-locked skeletons — **zero cumulative layout shift** is a hard requirement.
- Section anchors offset by header height + 24px; anchor links in TL;DR jump correctly.
## 4. Mobile scroll
- No parallax, no scroll-linked header compression (fixed 56px slim header from the start); section reveals still fire (opacity-dominant).
- Sticky elements budget: header only. No sticky CTAs covering content (thumb-reach conversion handled by the contact band + header CV icon-button).
- Overscroll behaviour default; PWA-style rubber-banding untouched.
- Gallery swipe is the only horizontal gesture, clearly contained inside the lightbox.
## 5. Deep-link & restoration behaviour
- Anchor navigation (nav click, TOC, TL;DR links): smooth scroll 400ms max — distance-capped so cross-page-length jumps don't take seconds; reduced motion → instant.
- Back/forward restores scroll position (browser default — do not fight it).
- Language switch preserves scroll ratio where section structure matches (A7); if the target page differs structurally, land at top of the equivalent section rather than a mismatched pixel offset.
## 6. Performance constraints on scroll effects
- All scroll-linked effects (header compression, spy, reveals) via passive observers/transforms — no scroll-handler layout reads (jank = instant credibility loss with P2).
- Reveal thresholds via IntersectionObserver semantics (conceptually); everything degrades to "visible" when JS is absent — **content is never gated behind scroll animation.**
@@ -0,0 +1,148 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 1420" font-family="Segoe UI, Arial, sans-serif">
<defs>
<radialGradient id="halo" cx="30%" cy="18%" r="55%">
<stop offset="0%" stop-color="#3ECFAE" stop-opacity="0.07"/>
<stop offset="100%" stop-color="#3ECFAE" stop-opacity="0"/>
</radialGradient>
<pattern id="dots" width="24" height="24" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="1" fill="#E8ECF2" opacity="0.05"/>
</pattern>
</defs>
<!-- page -->
<rect width="1440" height="1420" fill="#0B0E14"/>
<rect width="1440" height="640" fill="url(#dots)"/>
<rect width="1440" height="640" fill="url(#halo)"/>
<!-- ===== HEADER 72px ===== -->
<line x1="0" y1="72" x2="1440" y2="72" stroke="#E8ECF2" stroke-opacity="0.08"/>
<!-- monogram -->
<rect x="120" y="20" width="32" height="32" rx="8" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="136" y="41" fill="#E8ECF2" font-family="Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle">CB</text>
<circle cx="148" cy="24" r="2" fill="#3ECFAE"/>
<!-- nav -->
<text x="420" y="41" fill="#9AA4B2" font-size="14">Projects</text>
<text x="510" y="41" fill="#9AA4B2" font-size="14">Experience</text>
<text x="620" y="41" fill="#9AA4B2" font-size="14">About</text>
<text x="695" y="41" fill="#9AA4B2" font-size="14">Contact</text>
<!-- language segmented -->
<rect x="1000" y="22" width="88" height="28" rx="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="1003" y="25" width="41" height="22" rx="11" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="1023" y="40" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EN</text>
<text x="1066" y="40" fill="#9AA4B2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">NO</text>
<!-- theme toggle -->
<circle cx="1122" cy="36" r="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<path d="M1126 30 a7 7 0 1 0 3 9 a6 6 0 0 1 -3 -9" fill="#9AA4B2"/>
<!-- CV split button -->
<rect x="1156" y="20" width="164" height="32" rx="8" fill="#3ECFAE"/>
<text x="1216" y="41" fill="#062A22" font-size="13" font-weight="600" text-anchor="middle">Download CV</text>
<line x1="1290" y1="26" x2="1290" y2="46" stroke="#062A22" stroke-opacity="0.3"/>
<path d="M1300 33 l6 6 l6 -6" fill="none" stroke="#062A22" stroke-width="1.8"/>
<!-- ===== HERO ===== -->
<text x="120" y="196" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">SYSTEMS DEVELOPER · TØNSBERG, NORWAY</text>
<text x="120" y="262" fill="#E8ECF2" font-size="61" font-weight="600" letter-spacing="-1">Connor Babbington</text>
<text x="120" y="316" fill="#E8ECF2" font-size="26" font-weight="500">I design, build and run web systems.</text>
<text x="120" y="356" fill="#9AA4B2" font-size="18">Eight-plus years delivering internal software for UK local government —</text>
<text x="120" y="384" fill="#9AA4B2" font-size="18">now building full-stack products in Norway.</text>
<!-- trace motif -->
<path d="M120 424 H360 l12 12 H560" fill="none" stroke="#3ECFAE" stroke-width="1.5"/>
<circle cx="566" cy="436" r="4" fill="#3ECFAE"/>
<text x="582" y="441" fill="#9AA4B2" font-family="Consolas, monospace" font-size="12">status: open to work</text>
<!-- chips -->
<g font-family="Consolas, monospace" font-size="12">
<rect x="120" y="466" width="150" height="30" rx="15" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="195" y="485" fill="#9AA4B2" text-anchor="middle">Tønsberg, Norway</text>
<rect x="282" y="466" width="128" height="30" rx="15" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="346" y="485" fill="#9AA4B2" text-anchor="middle">Work permit ✓</text>
<rect x="422" y="466" width="200" height="30" rx="15" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="522" y="485" fill="#9AA4B2" text-anchor="middle">Remote / hybrid / on-site</text>
<rect x="634" y="466" width="212" height="30" rx="15" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="740" y="485" fill="#9AA4B2" text-anchor="middle">English native · Norsk B1</text>
</g>
<!-- CTAs -->
<rect x="120" y="530" width="156" height="44" rx="8" fill="#3ECFAE"/>
<text x="198" y="557" fill="#062A22" font-size="15" font-weight="600" text-anchor="middle">View projects</text>
<rect x="292" y="530" width="196" height="44" rx="8" fill="none" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="390" y="557" fill="#E8ECF2" font-size="15" text-anchor="middle">Download CV · pdf</text>
<!-- portrait -->
<rect x="1020" y="150" width="300" height="375" rx="16" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<circle cx="1170" cy="290" r="72" fill="#242B38"/>
<path d="M1080 525 q90 -110 180 0" fill="#242B38"/>
<text x="1170" y="300" fill="#5C6675" font-size="13" text-anchor="middle">[ outdoor headshot ]</text>
<text x="1020" y="552" fill="#5C6675" font-family="Consolas, monospace" font-size="12">tønsberg, norway · 2025</text>
<!-- ===== PROOF STRIP ===== -->
<g>
<rect x="120" y="620" width="285" height="96" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="144" y="662" fill="#E8ECF2" font-size="28" font-weight="600">8+ yrs</text>
<text x="144" y="692" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">EXPERIENCE</text>
<rect x="425" y="620" width="285" height="96" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="449" y="662" fill="#E8ECF2" font-size="22" font-weight="600">UK public sector</text>
<text x="449" y="692" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">BACKGROUND</text>
<rect x="730" y="620" width="285" height="96" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="754" y="662" fill="#E8ECF2" font-size="22" font-weight="600">.NET · React · Docker</text>
<text x="754" y="692" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">CORE STACK</text>
<rect x="1035" y="620" width="285" height="96" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="1059" y="662" fill="#E8ECF2" font-size="22" font-weight="600">Based in Norway</text>
<text x="1059" y="692" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">WORK PERMIT ✓</text>
</g>
<!-- ===== SKILLS SECTION ===== -->
<text x="120" y="828" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">01 — SKILLS</text>
<text x="120" y="880" fill="#E8ECF2" font-size="39" font-weight="600">What I work with</text>
<text x="120" y="916" fill="#9AA4B2" font-size="16">A practical profile: business value, maintainability, dependable delivery.</text>
<!-- three cards -->
<g>
<rect x="120" y="956" width="384" height="360" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="144" y="996" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">01 — DEVELOPMENT</text>
<text x="144" y="1032" fill="#9AA4B2" font-size="14">Backend-leaning full-stack work in the</text>
<text x="144" y="1052" fill="#9AA4B2" font-size="14">.NET ecosystem.</text>
<g font-family="Consolas, monospace" font-size="12" fill="#E8ECF2">
<rect x="144" y="1080" width="52" height="28" rx="14" fill="#171C26"/><text x="170" y="1098" text-anchor="middle">C#</text>
<rect x="204" y="1080" width="64" height="28" rx="14" fill="#171C26"/><text x="236" y="1098" text-anchor="middle">.NET</text>
<rect x="276" y="1080" width="76" height="28" rx="14" fill="#171C26"/><text x="314" y="1098" text-anchor="middle">Python</text>
<rect x="144" y="1118" width="110" height="28" rx="14" fill="#171C26"/><text x="199" y="1136" text-anchor="middle">TypeScript</text>
<rect x="262" y="1118" width="66" height="28" rx="14" fill="#171C26"/><text x="295" y="1136" text-anchor="middle">React</text>
<rect x="144" y="1156" width="56" height="28" rx="14" fill="#171C26"/><text x="172" y="1174" text-anchor="middle">SQL</text>
<rect x="208" y="1156" width="110" height="28" rx="14" fill="#171C26"/><text x="263" y="1174" text-anchor="middle">JavaScript</text>
</g>
<rect x="528" y="956" width="384" height="360" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="552" y="996" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">02 — DEVOPS &amp; INFRASTRUCTURE</text>
<text x="552" y="1032" fill="#9AA4B2" font-size="14">I deploy and operate what I build —</text>
<text x="552" y="1052" fill="#9AA4B2" font-size="14">professionally and at home.</text>
<g font-family="Consolas, monospace" font-size="12" fill="#E8ECF2">
<rect x="552" y="1080" width="76" height="28" rx="14" fill="#171C26"/><text x="590" y="1098" text-anchor="middle">Docker</text>
<rect x="636" y="1080" width="66" height="28" rx="14" fill="#171C26"/><text x="669" y="1098" text-anchor="middle">Linux</text>
<rect x="710" y="1080" width="66" height="28" rx="14" fill="#171C26"/><text x="743" y="1098" text-anchor="middle">CI/CD</text>
<rect x="552" y="1118" width="130" height="28" rx="14" fill="#171C26"/><text x="617" y="1136" text-anchor="middle">Azure DevOps</text>
<rect x="690" y="1118" width="150" height="28" rx="14" fill="#171C26"/><text x="765" y="1136" text-anchor="middle">Reverse proxies</text>
<rect x="552" y="1156" width="120" height="28" rx="14" fill="#171C26"/><text x="612" y="1174" text-anchor="middle">Monitoring</text>
<rect x="680" y="1156" width="130" height="28" rx="14" fill="#171C26"/><text x="745" y="1174" text-anchor="middle">Self-hosting</text>
</g>
<rect x="936" y="956" width="384" height="360" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="960" y="996" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">03 — PRACTICES</text>
<text x="960" y="1032" fill="#9AA4B2" font-size="14">Production thinking: testing, security,</text>
<text x="960" y="1052" fill="#9AA4B2" font-size="14">and honest communication.</text>
<g font-family="Consolas, monospace" font-size="12" fill="#E8ECF2">
<rect x="960" y="1080" width="86" height="28" rx="14" fill="#171C26"/><text x="1003" y="1098" text-anchor="middle">Testing</text>
<rect x="1054" y="1080" width="130" height="28" rx="14" fill="#171C26"/><text x="1119" y="1098" text-anchor="middle">OAuth2 flows</text>
<rect x="960" y="1118" width="180" height="28" rx="14" fill="#171C26"/><text x="1050" y="1136" text-anchor="middle">Security hardening</text>
<rect x="1148" y="1118" width="150" height="28" rx="14" fill="#171C26"/><text x="1223" y="1136" text-anchor="middle">Prod. support</text>
<rect x="960" y="1156" width="220" height="28" rx="14" fill="#171C26"/><text x="1070" y="1174" text-anchor="middle">Stakeholder comms</text>
</g>
</g>
<!-- annotations -->
<g font-family="Consolas, monospace" font-size="11" fill="#E5A93D">
<text x="120" y="106">A1: trace draws on load (700ms), dot blinks once → stays lit</text>
<text x="920" y="106">hover: nav underline draw · CV button = locale default + chevron menu</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,94 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 760" font-family="Segoe UI, Arial, sans-serif">
<defs>
<radialGradient id="haloL" cx="30%" cy="18%" r="55%">
<stop offset="0%" stop-color="#0E8C72" stop-opacity="0.06"/>
<stop offset="100%" stop-color="#0E8C72" stop-opacity="0"/>
</radialGradient>
<pattern id="dotsL" width="24" height="24" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="1" fill="#171A20" opacity="0.05"/>
</pattern>
<filter id="cardShadow" x="-10%" y="-10%" width="120%" height="130%">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#171A20" flood-opacity="0.06"/>
</filter>
</defs>
<rect width="1440" height="760" fill="#FAFAF8"/>
<rect width="1440" height="640" fill="url(#dotsL)"/>
<rect width="1440" height="640" fill="url(#haloL)"/>
<!-- HEADER -->
<line x1="0" y1="72" x2="1440" y2="72" stroke="#171A20" stroke-opacity="0.1"/>
<rect x="120" y="20" width="32" height="32" rx="8" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.2"/>
<text x="136" y="41" fill="#171A20" font-family="Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle">CB</text>
<circle cx="148" cy="24" r="2" fill="#0E8C72"/>
<text x="420" y="41" fill="#555E6B" font-size="14">Projects</text>
<text x="510" y="41" fill="#555E6B" font-size="14">Experience</text>
<text x="620" y="41" fill="#555E6B" font-size="14">About</text>
<text x="695" y="41" fill="#555E6B" font-size="14">Contact</text>
<rect x="1000" y="22" width="88" height="28" rx="14" fill="#F1F2EF" stroke="#171A20" stroke-opacity="0.1"/>
<rect x="1003" y="25" width="41" height="22" rx="11" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.2"/>
<text x="1023" y="40" fill="#171A20" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EN</text>
<text x="1066" y="40" fill="#555E6B" font-family="Consolas, monospace" font-size="11" text-anchor="middle">NO</text>
<circle cx="1122" cy="36" r="14" fill="#F1F2EF" stroke="#171A20" stroke-opacity="0.1"/>
<circle cx="1122" cy="36" r="5" fill="#555E6B"/>
<g stroke="#555E6B" stroke-width="1.4">
<line x1="1122" y1="26" x2="1122" y2="29"/><line x1="1122" y1="43" x2="1122" y2="46"/>
<line x1="1112" y1="36" x2="1115" y2="36"/><line x1="1129" y1="36" x2="1132" y2="36"/>
</g>
<rect x="1156" y="20" width="164" height="32" rx="8" fill="#0E8C72"/>
<text x="1216" y="41" fill="#FFFFFF" font-size="13" font-weight="600" text-anchor="middle">Download CV</text>
<line x1="1290" y1="26" x2="1290" y2="46" stroke="#FFFFFF" stroke-opacity="0.4"/>
<path d="M1300 33 l6 6 l6 -6" fill="none" stroke="#FFFFFF" stroke-width="1.8"/>
<!-- HERO (Norwegian locale shown — demonstrates NO string lengths + light theme) -->
<text x="120" y="196" fill="#0E8C72" font-family="Consolas, monospace" font-size="12" letter-spacing="2">SYSTEMUTVIKLER · TØNSBERG</text>
<text x="120" y="262" fill="#171A20" font-size="61" font-weight="600" letter-spacing="-1">Connor Babbington</text>
<text x="120" y="316" fill="#171A20" font-size="26" font-weight="500">Jeg utvikler og drifter websystemer.</text>
<text x="120" y="356" fill="#555E6B" font-size="18">Åtte års erfaring med fagsystemer i britisk offentlig sektor —</text>
<text x="120" y="384" fill="#555E6B" font-size="18">nå bygger jeg fullstack-løsninger fra Tønsberg.</text>
<path d="M120 424 H360 l12 12 H560" fill="none" stroke="#0E8C72" stroke-width="1.5"/>
<circle cx="566" cy="436" r="4" fill="#0E8C72"/>
<text x="582" y="441" fill="#555E6B" font-family="Consolas, monospace" font-size="12">status: åpen for muligheter</text>
<g font-family="Consolas, monospace" font-size="12">
<rect x="120" y="466" width="158" height="30" rx="15" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<text x="199" y="485" fill="#555E6B" text-anchor="middle">Bosatt i Tønsberg</text>
<rect x="290" y="466" width="200" height="30" rx="15" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<text x="390" y="485" fill="#555E6B" text-anchor="middle">Gyldig oppholdstillatelse</text>
<rect x="502" y="466" width="230" height="30" rx="15" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<text x="617" y="485" fill="#555E6B" text-anchor="middle">Remote / hybrid / oppmøte</text>
<rect x="744" y="466" width="230" height="30" rx="15" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<text x="859" y="485" fill="#555E6B" text-anchor="middle">Engelsk morsmål · Norsk B1</text>
</g>
<rect x="120" y="530" width="156" height="44" rx="8" fill="#0E8C72"/>
<text x="198" y="557" fill="#FFFFFF" font-size="15" font-weight="600" text-anchor="middle">Se prosjekter</text>
<rect x="292" y="530" width="196" height="44" rx="8" fill="none" stroke="#171A20" stroke-opacity="0.2"/>
<text x="390" y="557" fill="#171A20" font-size="15" text-anchor="middle">Last ned CV · pdf</text>
<!-- portrait -->
<rect x="1020" y="150" width="300" height="375" rx="16" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1" filter="url(#cardShadow)"/>
<circle cx="1170" cy="290" r="72" fill="#E4E6E1"/>
<path d="M1080 525 q90 -110 180 0" fill="#E4E6E1"/>
<text x="1170" y="300" fill="#8A93A0" font-size="13" text-anchor="middle">[ outdoor headshot — natural grade ]</text>
<text x="1020" y="552" fill="#8A93A0" font-family="Consolas, monospace" font-size="12">tønsberg, norge · 2025</text>
<!-- proof strip -->
<g filter="url(#cardShadow)">
<rect x="120" y="620" width="285" height="96" rx="12" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<rect x="425" y="620" width="285" height="96" rx="12" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<rect x="730" y="620" width="285" height="96" rx="12" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
<rect x="1035" y="620" width="285" height="96" rx="12" fill="#FFFFFF" stroke="#171A20" stroke-opacity="0.1"/>
</g>
<text x="144" y="662" fill="#171A20" font-size="28" font-weight="600">8+ år</text>
<text x="144" y="692" fill="#8A93A0" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">ERFARING</text>
<text x="449" y="662" fill="#171A20" font-size="22" font-weight="600">Offentlig sektor (UK)</text>
<text x="449" y="692" fill="#8A93A0" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">BAKGRUNN</text>
<text x="754" y="662" fill="#171A20" font-size="22" font-weight="600">.NET · React · Docker</text>
<text x="754" y="692" fill="#8A93A0" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">KJERNESTACK</text>
<text x="1059" y="662" fill="#171A20" font-size="22" font-weight="600">Bosatt i Norge</text>
<text x="1059" y="692" fill="#8A93A0" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">OPPHOLDSTILLATELSE ✓</text>
<text x="120" y="106" fill="#8A6114" font-family="Consolas, monospace" font-size="11">M2: light theme + Norwegian locale — same geometry as M1, token swap only; accent deepened to #0E8C72 for AA contrast</text>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -0,0 +1,125 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 1720" font-family="Segoe UI, Arial, sans-serif">
<rect width="1440" height="1720" fill="#0B0E14"/>
<!-- header (compressed 56px scrolled state) -->
<rect width="1440" height="56" fill="#0B0E14" fill-opacity="0.9"/>
<line x1="0" y1="56" x2="1440" y2="56" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="120" y="14" width="28" height="28" rx="7" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="134" y="33" fill="#E8ECF2" font-family="Consolas, monospace" font-size="12" font-weight="bold" text-anchor="middle">CB</text>
<text x="430" y="34" fill="#E8ECF2" font-size="13">Projects</text>
<line x1="430" y1="40" x2="478" y2="40" stroke="#3ECFAE" stroke-width="2"/>
<text x="510" y="34" fill="#9AA4B2" font-size="13">Experience</text>
<text x="610" y="34" fill="#9AA4B2" font-size="13">About</text>
<text x="680" y="34" fill="#9AA4B2" font-size="13">Contact</text>
<rect x="1176" y="13" width="144" height="30" rx="8" fill="#3ECFAE"/>
<text x="1238" y="33" fill="#062A22" font-size="12" font-weight="600" text-anchor="middle">Download CV ▾</text>
<!-- case study header -->
<text x="120" y="140" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">CASE STUDY · PERSONAL PRODUCT</text>
<text x="120" y="204" fill="#E8ECF2" font-size="49" font-weight="600" letter-spacing="-0.7">JobTrack</text>
<rect x="340" y="172" width="92" height="28" rx="14" fill="#11151D" stroke="#3ECFAE" stroke-opacity="0.4"/>
<circle cx="358" cy="186" r="3.5" fill="#3ECFAE"/>
<text x="370" y="191" fill="#3ECFAE" font-family="Consolas, monospace" font-size="11">ACTIVE</text>
<text x="120" y="248" fill="#9AA4B2" font-size="18">A job-application workspace covering the full journey from application to follow-up.</text>
<g font-family="Consolas, monospace" font-size="12" fill="#E8ECF2">
<rect x="120" y="276" width="72" height="28" rx="14" fill="#171C26"/><text x="156" y="294" text-anchor="middle">.NET 9</text>
<rect x="200" y="276" width="66" height="28" rx="14" fill="#171C26"/><text x="233" y="294" text-anchor="middle">React</text>
<rect x="274" y="276" width="110" height="28" rx="14" fill="#171C26"/><text x="329" y="294" text-anchor="middle">TypeScript</text>
<rect x="392" y="276" width="76" height="28" rx="14" fill="#171C26"/><text x="430" y="294" text-anchor="middle">SQLite</text>
<rect x="476" y="276" width="86" height="28" rx="14" fill="#171C26"/><text x="519" y="294" text-anchor="middle">FastAPI</text>
<rect x="570" y="276" width="76" height="28" rx="14" fill="#171C26"/><text x="608" y="294" text-anchor="middle">Ollama</text>
<rect x="654" y="276" width="76" height="28" rx="14" fill="#171C26"/><text x="692" y="294" text-anchor="middle">Docker</text>
</g>
<text x="1200" y="294" fill="#3ECFAE" font-size="14">Repository ↗</text>
<!-- hero screenshot: browser frame -->
<rect x="120" y="336" width="1200" height="520" rx="16" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<rect x="120" y="336" width="1200" height="40" rx="16" fill="#11151D"/>
<rect x="120" y="360" width="1200" height="16" fill="#11151D"/>
<circle cx="148" cy="356" r="5" fill="#5C6675"/><circle cx="166" cy="356" r="5" fill="#5C6675"/><circle cx="184" cy="356" r="5" fill="#5C6675"/>
<rect x="560" y="346" width="320" height="20" rx="10" fill="#171C26"/>
<text x="720" y="360" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="middle">jobtrack.local</text>
<!-- mock app content -->
<rect x="150" y="400" width="220" height="426" rx="8" fill="#11151D"/>
<rect x="390" y="400" width="900" height="60" rx="8" fill="#11151D"/>
<text x="410" y="436" fill="#9AA4B2" font-size="14">Applications · filters · search</text>
<g fill="#11151D">
<rect x="390" y="474" width="900" height="52" rx="6"/>
<rect x="390" y="534" width="900" height="52" rx="6"/>
<rect x="390" y="594" width="900" height="52" rx="6"/>
<rect x="390" y="654" width="900" height="52" rx="6"/>
<rect x="390" y="714" width="900" height="52" rx="6"/>
</g>
<g font-family="Consolas, monospace" font-size="11">
<rect x="1160" y="488" width="92" height="24" rx="12" fill="#0F2B24"/><text x="1206" y="504" fill="#3ECFAE" text-anchor="middle">INTERVIEW</text>
<rect x="1160" y="548" width="92" height="24" rx="12" fill="#2E2410"/><text x="1206" y="564" fill="#E5A93D" text-anchor="middle">WAITING</text>
<rect x="1160" y="608" width="92" height="24" rx="12" fill="#171C26"/><text x="1206" y="624" fill="#9AA4B2" text-anchor="middle">APPLIED</text>
</g>
<text x="720" y="800" fill="#5C6675" font-size="13" text-anchor="middle">[ 2× capture: applications table with status filters — seeded demo data ]</text>
<!-- TL;DR box -->
<rect x="120" y="896" width="800" height="180" rx="12" fill="#171C26"/>
<rect x="120" y="896" width="3" height="180" fill="#3ECFAE"/>
<text x="148" y="932" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">TL;DR</text>
<text x="148" y="964" fill="#E8ECF2" font-size="15"><tspan font-weight="600">What</tspan> Full-stack job-application tracker: pipeline, correspondence, attachments, analytics</text>
<text x="148" y="992" fill="#E8ECF2" font-size="15"><tspan font-weight="600">Why</tspan> Real problem — my own job search needed production-grade tooling</text>
<text x="148" y="1020" fill="#E8ECF2" font-size="15"><tspan font-weight="600">Stack</tspan> React + ASP.NET Core + SQLite · FastAPI/Ollama AI service · Docker Compose</text>
<text x="148" y="1048" fill="#E8ECF2" font-size="15"><tspan font-weight="600">Role</tspan> Everything: product, backend, frontend, ops, security</text>
<!-- mini TOC -->
<g font-size="13">
<text x="1080" y="932" fill="#5C6675" font-family="Consolas, monospace" font-size="11" letter-spacing="1.5">ON THIS PAGE</text>
<text x="1080" y="964" fill="#9AA4B2">Problem</text>
<rect x="1064" y="982" width="2" height="16" fill="#3ECFAE"/>
<text x="1080" y="994" fill="#E8ECF2">Architecture</text>
<text x="1080" y="1024" fill="#9AA4B2">Decisions</text>
<text x="1080" y="1054" fill="#9AA4B2">Security</text>
<text x="1080" y="1084" fill="#9AA4B2">Screenshots</text>
<text x="1080" y="1114" fill="#9AA4B2">What's next</text>
</g>
<!-- Architecture section -->
<text x="120" y="1160" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">ARCHITECTURE</text>
<text x="120" y="1204" fill="#E8ECF2" font-size="31" font-weight="600">Four services, one compose file</text>
<!-- diagram -->
<rect x="120" y="1240" width="1200" height="300" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<!-- nodes -->
<g>
<rect x="170" y="1330" width="180" height="72" rx="8" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="260" y="1360" fill="#E8ECF2" font-size="14" text-anchor="middle">React SPA</text>
<text x="260" y="1382" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="middle">nginx · PWA</text>
<rect x="440" y="1330" width="200" height="72" rx="8" fill="#171C26" stroke="#3ECFAE" stroke-opacity="0.5"/>
<text x="540" y="1360" fill="#E8ECF2" font-size="14" text-anchor="middle">ASP.NET Core API</text>
<text x="540" y="1382" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="middle">.NET 9 · auth · rules</text>
<rect x="730" y="1276" width="180" height="64" rx="8" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="820" y="1302" fill="#E8ECF2" font-size="14" text-anchor="middle">SQLite + files</text>
<text x="820" y="1322" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EF Core · attachments</text>
<rect x="730" y="1364" width="180" height="64" rx="8" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="820" y="1390" fill="#E8ECF2" font-size="14" text-anchor="middle">AI service</text>
<text x="820" y="1410" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="middle">FastAPI → Ollama</text>
<rect x="1000" y="1330" width="180" height="72" rx="8" fill="#171C26" stroke="#E5A93D" stroke-opacity="0.5"/>
<text x="1090" y="1360" fill="#E8ECF2" font-size="14" text-anchor="middle">Gmail API</text>
<text x="1090" y="1382" fill="#8A6114" font-family="Consolas, monospace" font-size="11" text-anchor="middle">external · OAuth2</text>
</g>
<!-- edges -->
<g stroke="#3ECFAE" stroke-width="1.5" fill="none">
<path d="M350 1366 H440"/>
<path d="M640 1352 L730 1308"/>
<path d="M640 1380 L730 1396"/>
</g>
<path d="M640 1366 H960 M960 1366 H1000" stroke="#E5A93D" stroke-width="1.5" stroke-dasharray="4 4" fill="none"/>
<text x="380" y="1356" fill="#5C6675" font-family="Consolas, monospace" font-size="10">/api</text>
<text x="930" y="1356" fill="#8A6114" font-family="Consolas, monospace" font-size="10">import</text>
<text x="148" y="1276" fill="#5C6675" font-family="Consolas, monospace" font-size="11">hover: node highlights its edges, others dim to 60% (desktop only — fully legible static)</text>
<!-- decisions preview -->
<text x="120" y="1600" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">KEY DECISIONS &amp; TRADE-OFFS</text>
<text x="120" y="1644" fill="#E8ECF2" font-size="16"><tspan fill="#3ECFAE" font-family="Consolas, monospace">01</tspan> Local AI (Ollama) over a cloud API — privacy of job-search data + zero per-call cost, traded against setup complexity.</text>
<text x="120" y="1676" fill="#E8ECF2" font-size="16"><tspan fill="#3ECFAE" font-family="Consolas, monospace">02</tspan> No offline cache in the PWA — deliberate: frequent deploys made stale builds a worse failure than offline gaps.</text>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

+133
View File
@@ -0,0 +1,133 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 1240" font-family="Segoe UI, Arial, sans-serif">
<rect width="900" height="1240" fill="#05070B"/>
<text x="60" y="48" fill="#E5A93D" font-family="Consolas, monospace" font-size="12">M4: mobile 390px — homepage (left) · nav overlay open (right)</text>
<!-- ============ PHONE 1: homepage ============ -->
<rect x="60" y="80" width="390" height="1100" rx="24" fill="#0B0E14" stroke="#E8ECF2" stroke-opacity="0.16"/>
<!-- slim header 56 -->
<line x1="60" y1="136" x2="450" y2="136" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="84" y="94" width="28" height="28" rx="7" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="98" y="113" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" font-weight="bold" text-anchor="middle">CB</text>
<!-- CV icon button -->
<rect x="330" y="94" width="40" height="28" rx="7" fill="#3ECFAE"/>
<path d="M350 100 v10 m-4 -4 l4 4 l4 -4 M344 114 h12" stroke="#062A22" stroke-width="1.6" fill="none"/>
<!-- burger -->
<g stroke="#E8ECF2" stroke-width="1.6">
<line x1="390" y1="102" x2="414" y2="102"/><line x1="390" y1="110" x2="414" y2="110"/><line x1="390" y1="118" x2="414" y2="118"/>
</g>
<!-- hero -->
<text x="84" y="192" fill="#3ECFAE" font-family="Consolas, monospace" font-size="10" letter-spacing="1.5">SYSTEMS DEVELOPER · NORWAY</text>
<text x="84" y="234" fill="#E8ECF2" font-size="34" font-weight="600">Connor</text>
<text x="84" y="272" fill="#E8ECF2" font-size="34" font-weight="600">Babbington</text>
<text x="84" y="308" fill="#9AA4B2" font-size="15">I design, build and run web systems.</text>
<text x="84" y="332" fill="#9AA4B2" font-size="15">8+ years in UK local government —</text>
<text x="84" y="356" fill="#9AA4B2" font-size="15">now building full-stack in Norway.</text>
<path d="M84 384 H240 l8 8 H330" fill="none" stroke="#3ECFAE" stroke-width="1.5"/>
<circle cx="335" cy="392" r="3.5" fill="#3ECFAE"/>
<!-- chips 2 rows -->
<g font-family="Consolas, monospace" font-size="10.5">
<rect x="84" y="410" width="128" height="26" rx="13" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="148" y="427" fill="#9AA4B2" text-anchor="middle">Tønsberg, Norway</text>
<rect x="220" y="410" width="108" height="26" rx="13" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="274" y="427" fill="#9AA4B2" text-anchor="middle">Work permit ✓</text>
<rect x="84" y="444" width="150" height="26" rx="13" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="159" y="461" fill="#9AA4B2" text-anchor="middle">Remote/hybrid/on-site</text>
<rect x="242" y="444" width="150" height="26" rx="13" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="317" y="461" fill="#9AA4B2" text-anchor="middle">EN native · NO B1</text>
</g>
<!-- stacked CTAs full width, 48px -->
<rect x="84" y="494" width="342" height="48" rx="8" fill="#3ECFAE"/>
<text x="255" y="524" fill="#062A22" font-size="15" font-weight="600" text-anchor="middle">View projects</text>
<rect x="84" y="552" width="342" height="48" rx="8" fill="none" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="255" y="582" fill="#E8ECF2" font-size="15" text-anchor="middle">Download CV · pdf</text>
<!-- proof 2x2 -->
<g>
<rect x="84" y="628" width="165" height="72" rx="10" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="100" y="660" fill="#E8ECF2" font-size="18" font-weight="600">8+ yrs</text>
<text x="100" y="682" fill="#5C6675" font-family="Consolas, monospace" font-size="9" letter-spacing="1">EXPERIENCE</text>
<rect x="261" y="628" width="165" height="72" rx="10" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="277" y="660" fill="#E8ECF2" font-size="15" font-weight="600">Public sector</text>
<text x="277" y="682" fill="#5C6675" font-family="Consolas, monospace" font-size="9" letter-spacing="1">BACKGROUND</text>
<rect x="84" y="710" width="165" height="72" rx="10" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="100" y="742" fill="#E8ECF2" font-size="15" font-weight="600">.NET · React</text>
<text x="100" y="764" fill="#5C6675" font-family="Consolas, monospace" font-size="9" letter-spacing="1">CORE STACK</text>
<rect x="261" y="710" width="165" height="72" rx="10" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<text x="277" y="742" fill="#E8ECF2" font-size="15" font-weight="600">In Norway</text>
<text x="277" y="764" fill="#5C6675" font-family="Consolas, monospace" font-size="9" letter-spacing="1">PERMIT ✓</text>
</g>
<!-- project card -->
<text x="84" y="836" fill="#3ECFAE" font-family="Consolas, monospace" font-size="10" letter-spacing="1.5">02 — PROJECTS</text>
<rect x="84" y="856" width="342" height="290" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="84" y="856" width="342" height="170" rx="12" fill="#171C26"/>
<rect x="84" y="1010" width="342" height="16" fill="#171C26"/>
<text x="255" y="948" fill="#5C6675" font-size="11" text-anchor="middle">[ JobTrack screenshot 16:10 ]</text>
<rect x="342" y="872" width="70" height="24" rx="12" fill="#0B0E14" stroke="#3ECFAE" stroke-opacity="0.4"/>
<circle cx="356" cy="884" r="3" fill="#3ECFAE"/>
<text x="366" y="889" fill="#3ECFAE" font-family="Consolas, monospace" font-size="9.5">ACTIVE</text>
<text x="104" y="1058" fill="#E8ECF2" font-size="18" font-weight="600">JobTrack</text>
<text x="104" y="1082" fill="#9AA4B2" font-size="13">A workspace for the whole job-application journey.</text>
<g font-family="Consolas, monospace" font-size="10" fill="#9AA4B2">
<rect x="104" y="1096" width="56" height="22" rx="11" fill="#171C26"/><text x="132" y="1111" text-anchor="middle">.NET 9</text>
<rect x="168" y="1096" width="52" height="22" rx="11" fill="#171C26"/><text x="194" y="1111" text-anchor="middle">React</text>
<rect x="228" y="1096" width="60" height="22" rx="11" fill="#171C26"/><text x="258" y="1111" text-anchor="middle">Docker</text>
</g>
<text x="104" y="1136" fill="#3ECFAE" font-size="13">Read case study →</text>
<!-- ============ PHONE 2: nav overlay ============ -->
<rect x="480" y="80" width="390" height="1100" rx="24" fill="#0B0E14" stroke="#E8ECF2" stroke-opacity="0.16"/>
<rect x="480" y="80" width="390" height="1100" rx="24" fill="#11151D" fill-opacity="0.98"/>
<!-- overlay header -->
<rect x="504" y="94" width="28" height="28" rx="7" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="518" y="113" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" font-weight="bold" text-anchor="middle">CB</text>
<g stroke="#E8ECF2" stroke-width="1.8">
<line x1="812" y1="98" x2="832" y2="118"/><line x1="832" y1="98" x2="812" y2="118"/>
</g>
<!-- language + theme row -->
<rect x="504" y="152" width="240" height="44" rx="22" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="508" y="156" width="116" height="36" rx="18" fill="#0B0E14" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="566" y="179" fill="#E8ECF2" font-family="Consolas, monospace" font-size="13" text-anchor="middle">EN</text>
<text x="686" y="179" fill="#9AA4B2" font-family="Consolas, monospace" font-size="13" text-anchor="middle">NO</text>
<circle cx="800" cy="174" r="22" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.08"/>
<path d="M806 166 a9 9 0 1 0 4 12 a8 8 0 0 1 -4 -12" fill="#9AA4B2"/>
<!-- nav stack: large targets -->
<g font-size="28" font-weight="600">
<text x="504" y="292" fill="#E8ECF2">Home</text>
<text x="504" y="368" fill="#E8ECF2">Projects</text>
<text x="504" y="444" fill="#E8ECF2">Experience</text>
<text x="504" y="520" fill="#E8ECF2">About</text>
<text x="504" y="596" fill="#E8ECF2">Contact</text>
</g>
<g font-family="Consolas, monospace" font-size="11" fill="#5C6675">
<text x="504" y="316">/</text>
<text x="504" y="392">/projects · 3</text>
<text x="504" y="468">/experience</text>
<text x="504" y="544">/about</text>
<text x="504" y="620">/contact</text>
</g>
<line x1="504" y1="660" x2="846" y2="660" stroke="#E8ECF2" stroke-opacity="0.08"/>
<!-- CV buttons -->
<rect x="504" y="692" width="342" height="48" rx="8" fill="#3ECFAE"/>
<text x="675" y="722" fill="#062A22" font-size="15" font-weight="600" text-anchor="middle">Download CV — English</text>
<rect x="504" y="752" width="342" height="48" rx="8" fill="none" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="675" y="782" fill="#E8ECF2" font-size="15" text-anchor="middle">Last ned CV — Norsk</text>
<!-- contact shortcuts -->
<g font-family="Consolas, monospace" font-size="13" fill="#9AA4B2">
<text x="504" y="852">connor.babbington@cesnimda.co.uk ⧉</text>
<text x="504" y="888">linkedin ↗</text>
<text x="504" y="924">git.cesnimda.uk ↗</text>
</g>
<text x="504" y="1150" fill="#5C6675" font-family="Consolas, monospace" font-size="11">overlay: full-screen, body scroll locked, Esc/× closes,</text>
<text x="504" y="1168" fill="#5C6675" font-family="Consolas, monospace" font-size="11">focus trapped · targets ≥ 44px</text>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

+132
View File
@@ -0,0 +1,132 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 1040" font-family="Segoe UI, Arial, sans-serif">
<rect width="1440" height="1040" fill="#05070B"/>
<text x="60" y="44" fill="#E5A93D" font-family="Consolas, monospace" font-size="12">M5: navigation &amp; control states</text>
<!-- (a) default header -->
<text x="60" y="86" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(a) default · 72px</text>
<rect x="60" y="100" width="1320" height="72" fill="#0B0E14" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="90" y="120" width="32" height="32" rx="8" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="106" y="141" fill="#E8ECF2" font-family="Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle">CB</text>
<text x="380" y="141" fill="#9AA4B2" font-size="14">Projects</text>
<text x="470" y="141" fill="#9AA4B2" font-size="14">Experience</text>
<text x="580" y="141" fill="#9AA4B2" font-size="14">About</text>
<text x="655" y="141" fill="#9AA4B2" font-size="14">Contact</text>
<rect x="960" y="122" width="88" height="28" rx="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="963" y="125" width="41" height="22" rx="11" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="983" y="140" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EN</text>
<text x="1026" y="140" fill="#9AA4B2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">NO</text>
<circle cx="1082" cy="136" r="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="1116" y="120" width="164" height="32" rx="8" fill="#3ECFAE"/>
<text x="1176" y="141" fill="#062A22" font-size="13" font-weight="600" text-anchor="middle">Download CV</text>
<line x1="1250" y1="126" x2="1250" y2="146" stroke="#062A22" stroke-opacity="0.3"/>
<path d="M1258 133 l6 6 l6 -6" fill="none" stroke="#062A22" stroke-width="1.8"/>
<!-- (b) scrolled -->
<text x="60" y="216" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(b) scrolled · 56px · backdrop blur + hairline · hover on "Projects" (underline draw) · current page "About"</text>
<rect x="60" y="230" width="1320" height="56" fill="#0B0E14" fill-opacity="0.85" stroke="#E8ECF2" stroke-opacity="0.12"/>
<rect x="90" y="244" width="28" height="28" rx="7" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="104" y="263" fill="#E8ECF2" font-family="Consolas, monospace" font-size="12" font-weight="bold" text-anchor="middle">CB</text>
<text x="380" y="264" fill="#E8ECF2" font-size="13">Projects</text>
<line x1="380" y1="271" x2="410" y2="271" stroke="#3ECFAE" stroke-width="2"/>
<text x="470" y="264" fill="#9AA4B2" font-size="13">Experience</text>
<text x="570" y="264" fill="#E8ECF2" font-size="13">About</text>
<line x1="570" y1="271" x2="608" y2="271" stroke="#3ECFAE" stroke-width="2" stroke-opacity="0.6"/>
<text x="645" y="264" fill="#9AA4B2" font-size="13">Contact</text>
<rect x="1136" y="243" width="144" height="30" rx="8" fill="#3ECFAE"/>
<text x="1198" y="263" fill="#062A22" font-size="12" font-weight="600" text-anchor="middle">Download CV ▾</text>
<!-- (c) CV split open -->
<text x="60" y="336" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(c) CV split-button open · locale default first · file sizes · keyboard: ↓↑ Esc</text>
<rect x="60" y="350" width="164" height="32" rx="8" fill="#3ECFAE"/>
<text x="120" y="371" fill="#062A22" font-size="13" font-weight="600" text-anchor="middle">Download CV</text>
<line x1="194" y1="356" x2="194" y2="376" stroke="#062A22" stroke-opacity="0.3"/>
<path d="M202 366 l6 -6 l6 6" fill="none" stroke="#062A22" stroke-width="1.8"/>
<rect x="60" y="392" width="280" height="104" rx="12" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<rect x="68" y="402" width="264" height="40" rx="8" fill="#11151D"/>
<rect x="80" y="412" width="30" height="20" rx="10" fill="#0F2B24"/>
<text x="95" y="426" fill="#3ECFAE" font-family="Consolas, monospace" font-size="10" text-anchor="middle">EN</text>
<text x="122" y="427" fill="#E8ECF2" font-size="13">English CV</text>
<text x="322" y="427" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="end">pdf · 180 kB</text>
<rect x="80" y="454" width="30" height="20" rx="10" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="95" y="468" fill="#9AA4B2" font-family="Consolas, monospace" font-size="10" text-anchor="middle">NO</text>
<text x="122" y="469" fill="#9AA4B2" font-size="13">Norsk CV</text>
<text x="322" y="469" fill="#5C6675" font-family="Consolas, monospace" font-size="11" text-anchor="end">pdf · 172 kB</text>
<!-- (d) language switch states -->
<text x="480" y="336" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(d) language switch · EN active / NO active / focus-visible · thumb slides 160ms · aria toggle group</text>
<g>
<rect x="480" y="352" width="88" height="28" rx="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="483" y="355" width="41" height="22" rx="11" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="503" y="370" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EN</text>
<text x="546" y="370" fill="#9AA4B2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">NO</text>
<rect x="600" y="352" width="88" height="28" rx="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="644" y="355" width="41" height="22" rx="11" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="620" y="370" fill="#9AA4B2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EN</text>
<text x="664" y="370" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">NO</text>
<rect x="720" y="352" width="88" height="28" rx="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="718" y="350" width="92" height="32" rx="16" fill="none" stroke="#3ECFAE" stroke-width="2"/>
<rect x="723" y="355" width="41" height="22" rx="11" fill="#171C26" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="743" y="370" fill="#E8ECF2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">EN</text>
<text x="786" y="370" fill="#9AA4B2" font-family="Consolas, monospace" font-size="11" text-anchor="middle">NO</text>
</g>
<!-- (e) first-visit language hint -->
<text x="480" y="430" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(e) first-visit hint (browser prefers nb-NO) · shown once · dismissible · never auto-redirects</text>
<rect x="480" y="444" width="620" height="44" rx="8" fill="#171C26" stroke="#3ECFAE" stroke-opacity="0.3"/>
<text x="500" y="471" fill="#E8ECF2" font-size="13">Denne siden finnes på norsk</text>
<rect x="700" y="452" width="66" height="28" rx="8" fill="#3ECFAE"/>
<text x="733" y="471" fill="#062A22" font-size="12" font-weight="600" text-anchor="middle">Bytt</text>
<text x="1076" y="472" fill="#9AA4B2" font-size="14" text-anchor="end"></text>
<!-- (f) buttons row -->
<text x="60" y="560" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(f) buttons: primary rest/hover/focus · secondary · ghost · disabled</text>
<rect x="60" y="576" width="150" height="44" rx="8" fill="#3ECFAE"/>
<text x="135" y="603" fill="#062A22" font-size="14" font-weight="600" text-anchor="middle">Primary</text>
<rect x="230" y="572" width="150" height="44" rx="8" fill="#54DBBC"/>
<text x="305" y="599" fill="#062A22" font-size="14" font-weight="600" text-anchor="middle">Hover +1px ↑</text>
<rect x="400" y="576" width="150" height="44" rx="8" fill="#3ECFAE"/>
<rect x="396" y="572" width="158" height="52" rx="10" fill="none" stroke="#3ECFAE" stroke-width="2"/>
<text x="475" y="603" fill="#062A22" font-size="14" font-weight="600" text-anchor="middle">Focus ring</text>
<rect x="590" y="576" width="150" height="44" rx="8" fill="none" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="665" y="603" fill="#E8ECF2" font-size="14" text-anchor="middle">Secondary</text>
<text x="790" y="603" fill="#9AA4B2" font-size="14">Ghost link</text>
<line x1="790" y1="610" x2="856" y2="610" stroke="#3ECFAE" stroke-width="2"/>
<rect x="900" y="576" width="150" height="44" rx="8" fill="#171C26"/>
<text x="975" y="603" fill="#5C6675" font-size="14" text-anchor="middle">Disabled</text>
<!-- (g) chips & status -->
<text x="60" y="690" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(g) status semantics: accent=active · amber=in development · muted=archived (dot + label, never colour-only)</text>
<g font-family="Consolas, monospace" font-size="11">
<rect x="60" y="706" width="92" height="28" rx="14" fill="#11151D" stroke="#3ECFAE" stroke-opacity="0.4"/>
<circle cx="78" cy="720" r="3.5" fill="#3ECFAE"/>
<text x="90" y="725" fill="#3ECFAE">ACTIVE</text>
<rect x="168" y="706" width="150" height="28" rx="14" fill="#11151D" stroke="#E5A93D" stroke-opacity="0.4"/>
<circle cx="186" cy="720" r="3.5" fill="#E5A93D"/>
<text x="198" y="725" fill="#E5A93D">IN DEVELOPMENT</text>
<rect x="334" y="706" width="104" height="28" rx="14" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<circle cx="352" cy="720" r="3.5" fill="#5C6675"/>
<text x="364" y="725" fill="#9AA4B2">ARCHIVED</text>
</g>
<!-- (h) form states -->
<text x="60" y="800" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(h) form: rest · focus · error (icon + message, danger ≥4.5:1) · submit-sending · success panel</text>
<text x="60" y="832" fill="#9AA4B2" font-size="13">E-mail</text>
<rect x="60" y="842" width="260" height="44" rx="8" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<text x="340" y="832" fill="#E8ECF2" font-size="13">E-mail</text>
<rect x="340" y="842" width="260" height="44" rx="8" fill="#11151D" stroke="#3ECFAE" stroke-width="2"/>
<text x="620" y="832" fill="#E8ECF2" font-size="13">E-mail</text>
<rect x="620" y="842" width="260" height="44" rx="8" fill="#11151D" stroke="#E5606B" stroke-width="2"/>
<text x="620" y="908" fill="#E5606B" font-size="12">⚠ Please enter a valid e-mail address</text>
<rect x="920" y="842" width="180" height="44" rx="8" fill="#3ECFAE"/>
<text x="1010" y="869" fill="#062A22" font-family="Consolas, monospace" font-size="14" text-anchor="middle">···</text>
<rect x="1130" y="830" width="250 " height="70" rx="12" fill="#171C26" stroke="#3ECFAE" stroke-opacity="0.4"/>
<text x="1156" y="860" fill="#3ECFAE" font-size="14">✓ Message sent</text>
<text x="1156" y="882" fill="#9AA4B2" font-size="12">I usually reply within a day.</text>
<!-- (i) skip link -->
<text x="60" y="972" fill="#5C6675" font-family="Consolas, monospace" font-size="11">(i) skip-link, visible on first Tab</text>
<rect x="60" y="984" width="220" height="40" rx="8" fill="#3ECFAE"/>
<text x="170" y="1009" fill="#062A22" font-size="13" font-weight="600" text-anchor="middle">Skip to content ↵</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,91 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 1560" font-family="Segoe UI, Arial, sans-serif">
<rect width="1440" height="1560" fill="#0B0E14"/>
<text x="120" y="44" fill="#E5A93D" font-family="Consolas, monospace" font-size="12">M6: homepage lower half — projects · experience timeline · about teaser · contact band</text>
<!-- ===== PROJECTS ===== -->
<text x="120" y="110" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">02 — PROJECTS</text>
<text x="120" y="162" fill="#E8ECF2" font-size="39" font-weight="600">Built, shipped, and running</text>
<!-- card 1: JobTrack (hover state shown) -->
<rect x="120" y="200" width="588" height="470" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.16"/>
<rect x="120" y="200" width="588" height="280" rx="12" fill="#171C26"/>
<rect x="120" y="464" width="588" height="16" fill="#171C26"/>
<text x="414" y="348" fill="#5C6675" font-size="12" text-anchor="middle">[ JobTrack — applications table screenshot, 4px hover shift ]</text>
<rect x="600" y="220" width="88" height="26" rx="13" fill="#0B0E14" stroke="#3ECFAE" stroke-opacity="0.4"/>
<circle cx="616" cy="233" r="3.5" fill="#3ECFAE"/>
<text x="628" y="238" fill="#3ECFAE" font-family="Consolas, monospace" font-size="10">ACTIVE</text>
<text x="148" y="524" fill="#E8ECF2" font-size="24" font-weight="600">JobTrack</text>
<line x1="148" y1="532" x2="248" y2="532" stroke="#3ECFAE" stroke-width="2"/>
<text x="148" y="556" fill="#9AA4B2" font-size="14">One workspace for the whole job-application journey —</text>
<text x="148" y="576" fill="#9AA4B2" font-size="14">pipeline, correspondence, attachments, AI summaries.</text>
<g font-family="Consolas, monospace" font-size="11" fill="#9AA4B2">
<rect x="148" y="594" width="60" height="24" rx="12" fill="#171C26"/><text x="178" y="610" text-anchor="middle">.NET 9</text>
<rect x="216" y="594" width="56" height="24" rx="12" fill="#171C26"/><text x="244" y="610" text-anchor="middle">React</text>
<rect x="280" y="594" width="66" height="24" rx="12" fill="#171C26"/><text x="313" y="610" text-anchor="middle">SQLite</text>
<rect x="354" y="594" width="66" height="24" rx="12" fill="#171C26"/><text x="387" y="610" text-anchor="middle">Ollama</text>
<rect x="428" y="594" width="66" height="24" rx="12" fill="#171C26"/><text x="461" y="610" text-anchor="middle">Docker</text>
</g>
<text x="148" y="646" fill="#3ECFAE" font-size="14">Read case study →→</text>
<!-- card 2: InboxIntel (rest state) -->
<rect x="732" y="200" width="588" height="470" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<rect x="732" y="200" width="588" height="280" rx="12" fill="#171C26"/>
<rect x="732" y="464" width="588" height="16" fill="#171C26"/>
<text x="1026" y="348" fill="#5C6675" font-size="12" text-anchor="middle">[ InboxIntel — draggable analytics dashboard screenshot ]</text>
<rect x="1156" y="220" width="146" height="26" rx="13" fill="#0B0E14" stroke="#E5A93D" stroke-opacity="0.4"/>
<circle cx="1172" cy="233" r="3.5" fill="#E5A93D"/>
<text x="1184" y="238" fill="#E5A93D" font-family="Consolas, monospace" font-size="10">IN DEVELOPMENT</text>
<text x="760" y="524" fill="#E8ECF2" font-size="24" font-weight="600">InboxIntel</text>
<text x="760" y="556" fill="#9AA4B2" font-size="14">Gmail analytics and safe bulk cleanup — Clean Architecture,</text>
<text x="760" y="576" fill="#9AA4B2" font-size="14">encrypted OAuth tokens, preview-before-delete by design.</text>
<g font-family="Consolas, monospace" font-size="11" fill="#9AA4B2">
<rect x="760" y="594" width="60" height="24" rx="12" fill="#171C26"/><text x="790" y="610" text-anchor="middle">.NET 8</text>
<rect x="828" y="594" width="100" height="24" rx="12" fill="#171C26"/><text x="878" y="610" text-anchor="middle">PostgreSQL</text>
<rect x="936" y="594" width="56" height="24" rx="12" fill="#171C26"/><text x="964" y="610" text-anchor="middle">React</text>
<rect x="1000" y="594" width="96" height="24" rx="12" fill="#171C26"/><text x="1048" y="610" text-anchor="middle">Gmail API</text>
</g>
<text x="760" y="646" fill="#3ECFAE" font-size="14">Read case study →</text>
<!-- slim homelab card -->
<rect x="120" y="694" width="1200" height="92" rx="12" fill="#11151D" stroke="#E8ECF2" stroke-opacity="0.08"/>
<g stroke="#3ECFAE" stroke-width="1.3" fill="none" opacity="0.8">
<rect x="152" y="722" width="24" height="16" rx="2"/><rect x="152" y="744" width="24" height="16" rx="2"/>
<path d="M176 730 h20 v22 h-20 M186 730 v-8 h30"/>
</g>
<text x="240" y="732" fill="#E8ECF2" font-size="18" font-weight="600">Self-hosted infrastructure lab</text>
<text x="240" y="758" fill="#9AA4B2" font-size="14">Ubuntu · Docker services · reverse proxy · auth · monitoring · Gitea (this site's own git remote)</text>
<text x="1180" y="746" fill="#3ECFAE" font-size="14">Explore →</text>
<!-- ===== EXPERIENCE ===== -->
<text x="120" y="884" fill="#3ECFAE" font-family="Consolas, monospace" font-size="12" letter-spacing="2">03 — EXPERIENCE</text>
<text x="120" y="936" fill="#E8ECF2" font-size="39" font-weight="600">Where I've worked</text>
<!-- timeline spine -->
<line x1="160" y1="972" x2="160" y2="1270" stroke="#E8ECF2" stroke-opacity="0.12" stroke-width="1.5"/>
<!-- featured -->
<circle cx="160" cy="990" r="5" fill="#3ECFAE"/>
<text x="196" y="996" fill="#E8ECF2" font-size="20" font-weight="600">System Developer — Warwickshire County Council</text>
<text x="196" y="1020" fill="#5C6675" font-family="Consolas, monospace" font-size="12">2015 2023 · UK public sector · full-stack</text>
<text x="196" y="1050" fill="#9AA4B2" font-size="15">· Replaced spreadsheet-heavy workflows with reliable internal systems used across the council</text>
<text x="196" y="1076" fill="#9AA4B2" font-size="15">· Built and maintained full-stack applications in C#, Python, Ruby on Rails, SQL and JavaScript</text>
<text x="196" y="1102" fill="#9AA4B2" font-size="15">· Owned deployments, permissions hardening and production troubleshooting on live systems</text>
<!-- compact items -->
<circle cx="160" cy="1150" r="4" fill="#5C6675"/>
<text x="196" y="1156" fill="#9AA4B2" font-size="14">2017 2021 · Sales Representative · Royal Vapes</text>
<circle cx="160" cy="1188" r="4" fill="#5C6675"/>
<text x="196" y="1194" fill="#9AA4B2" font-size="14">2016 2018 · Bartender · The Hodcarrier</text>
<circle cx="160" cy="1226" r="4" fill="#5C6675"/>
<text x="196" y="1232" fill="#9AA4B2" font-size="14">2014 2015 · Receptionist · Nuffield Health</text>
<circle cx="160" cy="1264" r="4" fill="#5C6675"/>
<text x="196" y="1270" fill="#9AA4B2" font-size="14">2012 2015 · Extended Diploma NVQ L3 in ICT · Warwickshire College</text>
<text x="196" y="1300" fill="#5C6675" font-family="Consolas, monospace" font-size="12">earlier customer-facing roles — kept for honesty, de-emphasised · timeline draws on scroll (A8)</text>
<!-- ===== CONTACT BAND ===== -->
<rect x="120" y="1360" width="1200" height="140" rx="16" fill="#171C26"/>
<text x="160" y="1418" fill="#E8ECF2" font-size="28" font-weight="600">Looking for a systems developer in Norway?</text>
<text x="160" y="1450" fill="#9AA4B2" font-size="15">I usually reply within a day — e-mail is fastest.</text>
<rect x="880" y="1404" width="300" height="48" rx="8" fill="#3ECFAE"/>
<text x="1030" y="1434" fill="#062A22" font-size="15" font-weight="600" text-anchor="middle">connor.babbington@cesnimda.co.uk ⧉</text>
<text x="1216" y="1434" fill="#062A22" font-size="0">copy</text>
<text x="1230" y="1432" fill="#9AA4B2" font-size="14">Contact form →</text>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

+116
View File
@@ -0,0 +1,116 @@
# MOCKUPS.md
Master mockup document. High-fidelity SVG boards live beside this file (`M1…M6.svg`); this document specifies every required state, including those not economical to board. All measurements reference SPACING_SYSTEM tokens; colours reference COLOUR_SYSTEM tokens.
Boards:
- **M1** `M1-home-desktop-dark.svg` — homepage hero + proof strip + skills (dark, 1440)
- **M2** `M2-home-desktop-light.svg` — same region, light theme
- **M3** `M3-case-study-dark.svg` — JobTrack case-study page (dark, 1440)
- **M4** `M4-mobile-dark.svg` — mobile homepage + open nav overlay (390)
- **M5** `M5-header-states.svg` — navigation states, language switch, CV split button
- **M6** `M6-projects-experience-contact.svg` — homepage lower half (projects cards, timeline, contact band)
---
## 1. Homepage — hero + narrative flow (M1/M2)
```
┌────────────────────────────────────────────────────────────────────────┐
│ HEADER 72px: [CB●] Projects Experience About Contact │ EN|NO ☾ [CV ▾] │
├────────────────────────────────────────────────────────────────────────┤
│ (faint dot-grid + accent halo, top-left biased) │
│ │
│ mono-label: SYSTEMS DEVELOPER · TØNSBERG, NORWAY ┌──────────┐ │
│ display: Connor Babbington │ portrait │ │
│ h3-weight: I design, build and run web systems. │ hairline │ │
│ body-lg: Eight-plus years delivering internal │ frame, │ │
│ software for UK local government — now │ mono │ │
│ building full-stack products in Norway. │ caption │ │
│ ── trace line ──┐___________ ● status: open to work └──────────┘ │
│ │
│ chips: [Tønsberg, Norway] [Work permit ✓] [Remote/hybrid/on-site] │
│ [English native · Norsk B1] │
│ CTAs: [■ View projects] [□ Download CV · pdf] │
├────────────────────────────────────────────────────────────────────────┤
│ PROOF STRIP (4 tiles, surface-1, hairline): 8+ YRS · PUBLIC SECTOR · │
│ .NET/REACT/DOCKER · BASED IN NORWAY — mono labels, stat numerals │
└────────────────────────────────────────────────────────────────────────┘
```
- Hero text column 7/12 cols; portrait 4/12, offset 1. Portrait: outdoor headshot, 4:5, radius 16, duotone in dark.
- Scroll behaviour: header compresses at 120px; sections reveal per A2.
- Hover states: CTAs per MICRO_INTERACTIONS buttons; portrait has no hover (non-interactive).
## 2. Skills section (M1 lower)
Three `surface-1` cards, 4/12 each, mono-labels `01 — DEVELOPMENT` / `02 — DEVOPS & INFRASTRUCTURE` / `03 — PRACTICES`; one context sentence (ink-muted) + chip rows. Card hover: border-strong. No proficiency bars.
## 3. Projects section — case-study cards (M6)
Two large cards (6/12 each): screenshot bleeding to top frame edge (browser-chrome mock), status chip overlaid top-right (`ACTIVE` accent / `IN DEVELOPMENT` amber), h3 title, 1-line problem statement, stack chips, ghost link `Read case study →`. Third slim full-width card for homelab: no screenshot; small topology glyph + one line. Hover per A3 (4px screenshot shift, border glow, arrow nudge).
## 4. Experience timeline (M6)
Left hairline spine with period dots. Featured item (System Developer, Warwickshire County Council, 20152023): expanded — role h4, employer + location mono-meta, 3 outcome bullets. Earlier roles: compact one-liners at 60% emphasis (`20172021 · Sales Representative · Royal Vapes` etc.). Education node at bottom. Timeline draws on entry (A8).
## 5. About section
7/12 text (3 short paragraphs per CONTENT_STRATEGY), 4/12 photo (same portrait, natural grade in light). Language chips repeated (`English — native` `Norsk — B1, aktiv utvikling`). Interests one mono-meta line, no icons.
## 6. Contact (M6 band + page)
Homepage band: `surface-2`, h2 "Looking for a systems developer in Norway?" / NO: "Ser dere etter en systemutvikler?", primary e-mail button (click-to-copy), secondary "Contact form →".
Contact page: 6/12 form (name/e-mail/message, labels above, per MICRO_INTERACTIONS form), 5/12 direct channels card: e-mail (copy), LinkedIn, git.cesnimda.uk, response-time note. Success state per A9.
## 7. Navigation states (M5)
Boards show: (a) default header, (b) scrolled/compressed with blur, (c) CV split-button open (EN/NO menu with file sizes), (d) language switch EN-active vs NO-active with sliding thumb, (e) focus-visible rings on nav item + toggle, (f) mobile header + full-screen overlay (nav stack, language segmented control, theme toggle, CV buttons, contact shortcuts).
## 8. Mobile layouts (M4)
390px reference. Order: slim header (56px: monogram · CV icon-button · burger) → mono-label + name (40px) → summary → chips (wrap, 2 rows) → CTAs stacked full-width → proof strip 2×2 tiles → skills cards stacked → project cards full-width (screenshot 16:10) → timeline (spine indented 16px) → about → contact band → footer. Touch targets ≥44px; no parallax; reveals opacity-only.
## 9. Dark + light (M1 vs M2)
Identical geometry; token swap only. Light: shadows-card active on cards, dot-grid at 5% instead of 8%, accent `#0E8C72`, portrait natural. Boards demonstrate both to prove parity.
## 10. Loading states
Specified in MOTION_GUIDELINES §4; visually: skeleton = `surface-2` rounded block, aspect-locked, mono caption `loading capture…` bottom-left, no shimmer sweep (shimmer reads as ad-tech; static + cross-fade is calmer). Form submit dots and language cross-fade per A7/A9.
## 11. Language switch UX (M5 detail)
- Segmented `EN | NO` control, header right cluster, thumb slide 160ms.
- Switching maps current page → same page other locale (URL strategy per IA §5); content cross-fade per A7; scroll ratio preserved.
- First-visit hint (browser prefers nb): dismissible one-line banner under header: `Denne siden finnes på norsk → [Bytt] [×]` — shown once, never auto-redirect.
- All chrome strings localise with content (nav labels, CV button, form labels, alt text, skip-link).
## 12. Case-study page (M3)
```
┌ header ────────────────────────────────────────────────────────────────┐
│ mono-label: CASE STUDY · PERSONAL PRODUCT │
│ h1: JobTrack [ACTIVE ●] │
│ body-lg: A job-application workspace covering the full journey │
│ from application to follow-up. │
│ chips: .NET 9 · React · TypeScript · SQLite · FastAPI · Ollama · │
│ Docker links: [Repository ↗] │
├─ hero screenshot (browser frame, full-bleed to 1200) ─────────────────┤
├─ TL;DR box (surface-2, 3px accent left edge): What/Why/Stack/Role ────┤
│ ┌ mini-TOC (sticky) ──┐ │
│ ## Problem & context (72ch) │ Problem │ │
│ ## Architecture │ Architecture ● │ │
│ [diagram breakout 1200px] │ Decisions │ │
│ ## Key decisions & trade-offs │ Security │ │
│ numbered blocks: choice/alt/why │ Outcome │ │
│ ## Security & production notes │ Next │ │
│ ## Screenshots (lightbox gallery) └─────────────────────┘ │
│ ## What I'd improve next │
├─ footer nav: ← InboxIntel | All projects ─────────────────────────────┤
```
Diagram style per COLOUR_SYSTEM §3.5: surface nodes, accent flow, amber external systems (Gmail API), mono labels.
## 13. Asset integration notes
- **Portraits:** outdoor headshot → hero/about; square crop → favicon-adjacent/OG fallback; suit photo → not used on site (kept for LinkedIn/CV).
- **JobTrack media plan** (from `website_details.md`): applications table, job-details dialog, Gmail import view, dashboard analytics — 4 captures + 1 architecture diagram + workflow diagram.
- **InboxIntel media plan:** dashboard grid, cleanup preview/confirm flow, architecture diagram (Clean Architecture layers), sync-worker sequence sketch.
- Screenshots need a capture pass at 2× on seeded demo data — **flagged as a Phase 3 prerequisite task** (no real personal job-hunt data on the public site; seed fictional companies).
+38
View File
@@ -0,0 +1,38 @@
# Portfolio Redesign — Phase 1 Deliverables (Design, no code)
Status: **complete, awaiting approval** · July 2026
## Index
### 01 — Research & strategy
- [DESIGN_RESEARCH.md](01-research/DESIGN_RESEARCH.md) — audit of cesnimda.co.uk, portfolio/SaaS pattern research, strategic conclusions
- [USER_PERSONAS.md](01-research/USER_PERSONAS.md) — recruiter, eng manager, Norwegian CTO, interviewer
- [RECRUITER_BEHAVIOUR_ANALYSIS.md](01-research/RECRUITER_BEHAVIOUR_ANALYSIS.md) — scan behaviour, two-lane rule, Norway specifics, acceptance criteria
### 02 — UX structure
- [INFORMATION_ARCHITECTURE.md](02-ux/INFORMATION_ARCHITECTURE.md) — site map, nav model, bilingual URL strategy, content model
- [USER_JOURNEYS.md](02-ux/USER_JOURNEYS.md) — five validated journeys + requirements checklist
- [CONTENT_STRATEGY.md](02-ux/CONTENT_STRATEGY.md) — voice, hero copy drafts (EN/NO), case-study angles, CV improvement plan
- [CANONICAL_CONTENT.md](02-ux/CANONICAL_CONTENT.md) — master CV facts (provided by Connor, July 2026); single source for all copy
### 03 — Visual design system ("Kontrollrom")
- [DESIGN_SYSTEM.md](03-visual/DESIGN_SYSTEM.md) — direction, identity, surfaces, components
- [TYPOGRAPHY_GUIDE.md](03-visual/TYPOGRAPHY_GUIDE.md) · [COLOUR_SYSTEM.md](03-visual/COLOUR_SYSTEM.md) · [SPACING_SYSTEM.md](03-visual/SPACING_SYSTEM.md) · [MOTION_GUIDELINES.md](03-visual/MOTION_GUIDELINES.md)
### 04 — Interaction design
- [ANIMATION_CONCEPTS.md](04-interaction/ANIMATION_CONCEPTS.md) — A1A10 named concepts + rejected list
- [MICRO_INTERACTIONS.md](04-interaction/MICRO_INTERACTIONS.md) — per-component states & keyboard behaviour
- [SCROLL_EXPERIENCE.md](04-interaction/SCROLL_EXPERIENCE.md) · [CURSOR_INTERACTIONS.md](04-interaction/CURSOR_INTERACTIONS.md)
### 05 — Mockups
- [MOCKUPS.md](05-mockups/MOCKUPS.md) — master spec covering all 11 required mockup scopes
- Boards: M1 home desktop dark · M2 home desktop light (NO locale) · M3 case study · M4 mobile + nav overlay · M5 header/nav/control states · M6 projects/timeline/contact
## Key decisions requiring sign-off before Phase 2
1. **Positioning:** "Systems Developer, 8+ years" — no "senior" self-label; seniority shown via evidence (CONTENT_STRATEGY §1)
2. **IA:** multi-page (home + case studies + about/experience/contact/cv) replacing the one-pager (IA §1)
3. **Bilingual URLs:** EN at root, NO under `/no/` with localised slugs; switch maps page↔page; no auto-redirect (IA §5)
4. **Design direction:** "Kontrollrom" — dark-first, aurora-teal single accent, mono metadata, trace motif (DESIGN_SYSTEM)
5. **InboxIntel framed as `IN DEVELOPMENT`** (honest status) (CONTENT_STRATEGY §4)
6. **CV rework:** ATS-safe single-column variant becomes the download default (CONTENT_STRATEGY §6)
7. **Screenshot capture pass on seeded demo data** = Phase 3 prerequisite (MOCKUPS §13)