diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..2c0c13e --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,36 @@ +name: site + +# Publishes site/ to GitHub Pages. Requires Settings → Pages → Source: GitHub Actions. +# The site is static: no build step, no dependencies, no third-party requests at runtime. + +on: + push: + branches: [main, master] + paths: + - 'site/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: site + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..e985853 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/site/app.js b/site/app.js new file mode 100644 index 0000000..eed33b5 --- /dev/null +++ b/site/app.js @@ -0,0 +1,121 @@ +/* primer — site behaviour. Zero dependencies, zero network calls. */ +(function () { + 'use strict'; + + var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + var raf = window.requestAnimationFrame.bind(window); + + /* ── scroll progress + nav state ───────────────────── */ + var bar = document.querySelector('.progress i'); + var nav = document.querySelector('.nav'); + var ticking = false; + function onScroll() { + if (ticking) return; + ticking = true; + raf(function () { + var max = document.documentElement.scrollHeight - window.innerHeight; + var p = max > 0 ? window.scrollY / max : 0; + if (bar) bar.style.width = (p * 100).toFixed(2) + '%'; + if (nav) nav.classList.toggle('stuck', window.scrollY > 12); + ticking = false; + }); + } + window.addEventListener('scroll', onScroll, { passive: true }); + onScroll(); + + /* ── pointer aura + per-card glow ──────────────────── */ + if (!reduced && window.matchMedia('(pointer: fine)').matches) { + document.body.classList.add('has-pointer'); + var root = document.documentElement; + window.addEventListener('mousemove', function (e) { + root.style.setProperty('--mx', e.clientX + 'px'); + root.style.setProperty('--my', e.clientY + 'px'); + }, { passive: true }); + + document.querySelectorAll('.card').forEach(function (card) { + card.addEventListener('mousemove', function (e) { + var r = card.getBoundingClientRect(); + card.style.setProperty('--cx', (e.clientX - r.left) + 'px'); + card.style.setProperty('--cy', (e.clientY - r.top) + 'px'); + }, { passive: true }); + }); + } + + /* ── reveal on scroll ──────────────────────────────── */ + var revealables = document.querySelectorAll('.reveal'); + if (reduced || !('IntersectionObserver' in window)) { + revealables.forEach(function (el) { el.classList.add('in'); }); + } else { + var revealer = new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + if (!e.isIntersecting) return; + e.target.classList.add('in'); + revealer.unobserve(e.target); + }); + }, { rootMargin: '0px 0px -6% 0px', threshold: 0.06 }); + revealables.forEach(function (el) { revealer.observe(el); }); + } + + /* helper: run fn once, when el first scrolls into view */ + function whenSeen(el, fn, threshold) { + if (!el) return; + if (!('IntersectionObserver' in window)) { fn(); return; } + var obs = new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + if (!e.isIntersecting) return; + fn(); + obs.disconnect(); + }); + }, { threshold: threshold || 0.25 }); + obs.observe(el); + } + + /* ── record console prints line by line ────────────── */ + var record = document.querySelector('[data-record]'); + if (record) { + var lines = record.querySelectorAll('[data-line]'); + whenSeen(record, function () { + if (reduced) { lines.forEach(function (l) { l.classList.add('in'); }); return; } + lines.forEach(function (l, i) { + setTimeout(function () { l.classList.add('in'); }, i * 200); + }); + }, 0.3); + } + + /* ── counters ──────────────────────────────────────── */ + var scale = document.querySelector('[data-scale]'); + if (scale) { + var fmt = function (n) { + if (n >= 1000000) return (n / 1000000).toFixed(n >= 10000000 ? 0 : 1).replace(/\.0$/, '') + 'M'; + return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); + }; + whenSeen(scale, function () { + scale.querySelectorAll('[data-count-to]').forEach(function (el) { + var target = parseInt(el.getAttribute('data-count-to'), 10); + if (reduced) { el.textContent = fmt(target); return; } + var t0 = performance.now(), dur = 1500; + (function step(now) { + var p = Math.min(1, (now - t0) / dur); + el.textContent = fmt(Math.round(target * (1 - Math.pow(1 - p, 3)))); + if (p < 1) raf(step); + })(t0); + }); + }, 0.3); + } + + /* ── click-to-copy install line ────────────────────── */ + var term = document.querySelector('[data-copy]'); + if (term) { + var hint = term.querySelector('.copy-hint'); + term.addEventListener('click', function () { + var text = term.querySelector('code').textContent.replace(/^\s*\$\s*/, '').trim(); + if (!navigator.clipboard) return; + navigator.clipboard.writeText(text).then(function () { + if (!hint) return; + hint.textContent = 'copied ✓'; + hint.style.opacity = '1'; + setTimeout(function () { hint.textContent = 'click to copy'; hint.style.opacity = ''; }, 1400); + }).catch(function () { /* clipboard unavailable — leave the UI alone */ }); + }); + } +})(); diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..961338d --- /dev/null +++ b/site/index.html @@ -0,0 +1,406 @@ + + + + + +primer — the memory layer for AI tutoring + + + + + + + + + + + + + + + + + + +
+
+ + + YC Fall 2026 RFS · The Primer + + + +

+ The Primer gets built
+ either way. +

+ +

+ The only open question is who ends up holding the file — a company, or the family. + primer is the open, local-first learner record it should run on. One SQLite file + on your machine, an MCP server so any tutor can read it, and a review gate no + model can lift. +

+ + + +
+
+ + install + click to copy +
+
$ curl -fsSL https://raw.githubusercontent.com/VedSoni-dev/primer/main/install.sh | sh
+
+ + +
+ + +
+ + + + + +
+
+

01 / the ask

+

YC asked for it by name.

+

+ Fall 2026 Requests for Startups. Thirteen ideas YC wants funded, and the first is + called The Primer — a straight description of Neal Stephenson's + A Young Lady's Illustrated Primer, shipped as a consumer product. It is a + public page. Nothing here is leaked or inferred. +

+ +
+
+ While we think this begins as something a parent buys to help their child learn + basic skills, it is also the entry point to far greater ambitions. +
+
+ Y Combinator, Requests for Startups — The Primer + ycombinator.com/rfs +
+
+ +
+
+

Read as an investor

+

Large market, long retention, an expanding surface area starting from a wedge product parents already pay for.

+
+
+

Read as a parent

+

The reading app is the wedge. The wedge goes somewhere. Nobody wrote down where.

+
+
+ +

+ Both readings are correct, and the honest half of the pitch is genuinely true: every + child deserves the tutoring only wealthy children get today. That is exactly why this + gets built. So the argument worth having is not whether — it is on what terms. +

+
+
+ + +
+
+

02 / the stakes

+

A primer is the book you read first.

+

+ To prime someone is to decide, before they decide anything, what will + come easily to their mind later. To teach a child well you have to know things about + her nobody writes down. A good tutor holds all of it for one year. A record holds it + forever. That is why it teaches — and why it is worth being careful about. +

+ +
+
+ + learner_context("Ada") + 28 tools · one call +
+
    +
  • misconceptionreads every short e as short i4 sessions
  • +
  • frustrationshuts down when a timer is visible2 markers
  • +
  • interestdinosaurs — works twice as longstable 6w
  • +
  • trustworks for dad, not for the app's voiceobserved
  • +
  • self_belief“I'm bad at reading” after two missesverbatim
  • +
  • attentionloses her at minute 11n=37
  • +
  • accommodationno countdowns, no streaksparent-set
  • +
  • ↳Same rows. In other hands, not a lesson plan — a map of where she gives.
  • +
+
+ +
+
+
1child
+

One adult holds the whole picture and carries it for a year. This is what a good tutor already is, and it is fine.

+
+
+
0days · three years
+

The record stops being a snapshot and becomes a trajectory — not what she knows, but how she bends.

+
+
+
0childhoods · one login
+

Now scale it the way a company must. Nobody has to be malicious. Engagement to defend, renewals to protect, a board to answer to. Ordinary incentives are enough.

+
+
+ +
+
+ 01 +

The company dies, the child starts over

+

Ship an interface, treat the learning history as exhaust. Pivot, acquisition, shutdown — the history goes with it. Every teacher already knows this, because they have re-assessed the same child three years running.

+
+
+ 02 +

Export becomes a churn button

+

Once one company holds a few years of a few million childhoods, portability is a feature that costs retention. No growth team ships a churn button. Terms are settled early or not at all.

+
+
+ 03 +

The teaching disappears into the weights

+

Pedagogy locked in a proprietary model cannot be read, forked, or proven wrong. A bad prerequisite edge quietly mis-teaches a real child, and no outsider can even file the bug.

+
+
+ 04 +

Engagement machinery, aimed at a six-year-old

+

Streaks, coins, leaderboards, countdowns. A twelve-minute session that ends on a win beats forty minutes that ends in tears — but only one of those numbers reaches a board deck.

+
+
+
+
+ + +
+
+

03 / the terms

+

Open source here is not a license. It is the threat model.

+

+ None of this is a values page. Each item is a constraint in the repository, and most + have a test asserting them. That is the only kind of commitment that survives an + acquisition. +

+ +
    +
  1. The record is the product

    There is no app in the repo, on purpose. The tutor generates a page for one child for one afternoon and throws it away. Anything that makes the record depend on a UI is a bug.

  2. +
  3. One file, on your machine

    SQLite, 20 tables, in the family's home directory. No account, no server, no API key left lying around a family's laptop.

  4. +
  5. Evidence over scores

    Every number derives from append-only observations kept forever. evidence_for returns the raw attempts behind any estimate, verbatim.

  6. +
  7. Export carries everything

    Observations, sessions, generated activities, and the curriculum packs the history depends on — documented and versioned in FORMAT.md so this project is not the only thing that can read a child's record.

  8. +
  9. The child never talks to a model

    The fast loop never calls one. She meets a static page an adult approved. The model works on the record, not on the child.

  10. +
  11. A review gate that cannot self-lift

    Everything generated lands in pending_review. Rejections are read back on the next planning run, so saying no teaches the tutor instead of just blocking it.

  12. +
  13. The unattended run is caged

    No shell, no filesystem, no web, no inherited config, an explicit tool allowlist, and a budget ceiling the runtime enforces rather than our own accounting.

  14. +
  15. No engagement machinery, structurally

    No streaks, coins, leaderboards, timers — and nothing in the schema to optimize minutes-on-task with.

  16. +
  17. Deletable, and it means it

    primer delete --learner <name> --yes removes every row, plus that child's recordings and generated activities on disk, and reports any file it could not remove.

  18. +
+ +
+
what the unattended tutor may touchasserted by tests
+
claude -p --tools ""                 // every built-in tool off — no bash, fs, or web
+       --strict-mcp-config           // cannot see your other MCP servers
+       --setting-sources ""          // no CLAUDE.md, no hooks, no plugins
+       --mcp-config <primer only>    // this record and nothing else
+       --allowedTools mcp__primer__learner_context,…
+       --max-budget-usd <left today> // ceiling enforced outside our accounting
+
+ +
+

What the autonomous loop cannot do

+
+ create or delete learners + change accommodations + set goals +
+

Those tools exist where a human is asking for them, and are absent from the unattended loop. If the tutor thinks an accommodation is wrong it writes a note, and a human decides.

+
+
+
+ + +
+
+

04 / receipts

+

Nobody in this story has to be a villain.

+

+ That is the uncomfortable part. There is no cackling — there is a cap table, a growth + target, and a renewal date, and those are enough. So this is not about anyone's + character. It is about incentives, addressed to the people who actually decide. +

+ +
+
+ To YC +

You wrote the honest sentence out loud. Good — now fund the version where the ambition is not owning the child's file. One question for every founder pitching this RFS: can the family walk out with the whole record, in a documented format, on day one? If it needs a slide, it is a no.

+
+
+ To the founder who wins it +

You will be told portability is a v2 feature. It is the one feature that cannot be added later, because by v2 you will have a retention number that depends on it not existing. Build on an open record while it costs you a weekend.

+
+
+ To the labs +

The model is not the moat and you know it. The moat about to get built is the memory of a childhood. If long-horizon tutoring is coming, the state should be a file the family holds and any model can read — including a competitor's.

+
+
+ To edtech +

A decade of products treated a child's learning history as exhaust, then took it down with them. That is not a rare tragedy, it is the default outcome. Export is not a courtesy. It is the product.

+
+
+ To parents +

The question is not “is it adaptive.” It is where does the file live, can I read it, can I take it, and who else has a copy? If the answer is a dashboard, the answer is no. You are the last gate — this is built to keep you in that job.

+
+
+ To teachers +

213 skills, pre-K to grade 3, English only, and somewhere in that graph a prerequisite edge is wrong. Wrong edges quietly mis-teach real children. You are the only people who can find them, and no startup can outspend you on it.

+
+
+ To us +

One child. One week. Curriculum stops at grade 3. The BKT parameters are principled defaults, and the fitting code refuses to run on a sample this small rather than print a confident number. Releases currently ship a Windows binary only. Check every claim on this page against the repo — including this one.

+
+
+
+
+ + +
+
+

05 / what it is

+

A record and a server. That is the whole trick.

+ +
+
+

Open Learner Record

+

20-table SQLite schema, append-only evidence, forward-only migrations. Full spec in SPEC.md, interchange format in FORMAT.md.

+
+
+

Mastery model

+

Bayesian Knowledge Tracing over a two-component forgetting curve. Mastery is a cache — replayable from raw evidence at any time.

+
+
+

Scheduler

+

Ranks lapsed / due / stuck / frontier across strands, so a session is never eight vowel drills.

+
+
+

Placement

+

A cold start is placed, not assumed. Probes at the band age suggests, bisects to where the child actually is.

+
+
+

Curriculum

+

213 skills, pre-K–grade 3, reading / writing / math, with a prerequisite graph that crosses domains.

+
+
+

MCP server · 28 tools

+

learner_context returns the whole child in one call: mastery, what slipped, misconceptions, interests, accommodations, and which past interfaces actually worked. Same implementations for the human-driven and unattended paths.

+
+
+

Autonomous tutor

+

Drives claude -p on a loop. Your existing login, no API key. Queue, review gate, budget.

+
+
+

Surface

+

/k/<name> is the only URL a child needs. /review is the adult gate. Localhost by default.

+
+
+ +

+ One runtime dependency: the MCP SDK. Storage is node:sqlite, built into Node 22+. + The model is reached through the claude binary you already have. +

+
+
+ + +
+
+

06 / build

+

The proprietary version will have polish and a marketing budget.

+

+ That is a real fight, and pretending otherwise is stupid. The open version wins on the + three things a company structurally cannot offer — your record is yours, the teaching is + inspectable, the curriculum was improved by a thousand teachers instead of a growth + team. All three are made of contributions. +

+ +
+
most needed

Curriculum beyond grade 3, and beyond English. A content job, not an engineering one.

+
most needed

Teachers and reading specialists auditing the prerequisite graph.

+
needed

macOS and Linux release binaries. CI has never successfully run.

+
needed

Real evidence samples, so BKT parameters can be fitted instead of assumed.

+
+ + + +

Terms are settled early or not at all.
This is early.

+
+
+ + + + + + diff --git a/site/styles.css b/site/styles.css new file mode 100644 index 0000000..dc2c1c8 --- /dev/null +++ b/site/styles.css @@ -0,0 +1,498 @@ +/* primer — dark, quiet, expensive. No external requests of any kind. */ + +:root { + --bg: #060608; + --bg-soft: #0a0a0e; + --text: #ECECEF; + --dim: #9A9AA6; + --faint: #64646F; + --line: rgba(255,255,255,0.075); + --line-2: rgba(255,255,255,0.13); + --panel: rgba(255,255,255,0.024); + --panel-2: rgba(255,255,255,0.045); + --violet: #7C5CFF; + --indigo: #4F46E5; + --cyan: #22D3EE; + --rose: #FF4D6D; + --amber: #FFB020; + --sans: "Inter var", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Display", + "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif; + --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo, Consolas, monospace; + --r: 14px; + --ease: cubic-bezier(.22,.61,.36,1); +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--sans); + font-size: 16px; + line-height: 1.62; + letter-spacing: -0.011em; + -webkit-font-smoothing: antialiased; + overflow-x: hidden; +} + +::selection { background: rgba(124,92,255,0.35); } + +a { color: inherit; text-decoration: none; } + +.mono { font-family: var(--mono); } +.dim { color: var(--faint); } + +code { + font-family: var(--mono); + font-size: 0.86em; + background: rgba(255,255,255,0.055); + border: 1px solid var(--line); + border-radius: 5px; + padding: 0.08em 0.36em; + color: #CFC7FF; +} + +/* ── ambience ─────────────────────────────────────────── */ + +.aura { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; } +.blob { position: absolute; border-radius: 50%; filter: blur(80px); opacity: 0.72; } +.b1 { width: 48vw; height: 48vw; left: -10vw; top: -14vw; background: radial-gradient(circle, rgba(124,92,255,.72), transparent 64%); animation: drift1 26s ease-in-out infinite; } +.b2 { width: 42vw; height: 42vw; right: -8vw; top: 2vh; background: radial-gradient(circle, rgba(34,211,238,.42), transparent 64%); animation: drift2 32s ease-in-out infinite; } +.b3 { width: 54vw; height: 54vw; left: 20vw; top: 42vh; background: radial-gradient(circle, rgba(255,77,109,.28), transparent 66%); animation: drift3 38s ease-in-out infinite; } +@keyframes drift1 { 0%,100%{transform:translate(0,0) scale(1)} 50%{transform:translate(6vw,4vh) scale(1.12)} } +@keyframes drift2 { 0%,100%{transform:translate(0,0) scale(1)} 50%{transform:translate(-5vw,7vh) scale(.9)} } +@keyframes drift3 { 0%,100%{transform:translate(0,0) scale(1)} 50%{transform:translate(3vw,-6vh) scale(1.08)} } + +.mesh { + position: fixed; inset: 0; z-index: 1; pointer-events: none; + background-image: + linear-gradient(to right, rgba(255,255,255,0.028) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255,255,255,0.028) 1px, transparent 1px); + background-size: 72px 72px; + mask-image: radial-gradient(120% 90% at 50% 0%, #000 0%, transparent 72%); + -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 0%, transparent 72%); +} + +.spotlight { + position: fixed; inset: 0; z-index: 2; pointer-events: none; opacity: 0; + transition: opacity .5s ease; + background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%), rgba(124,92,255,0.10), transparent 65%); +} +body.has-pointer .spotlight { opacity: 1; } + +body > section, body > header, body > footer, body > .marquee { position: relative; z-index: 3; } + +.progress { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 200; pointer-events: none; } +.progress i { + display: block; height: 100%; width: 0%; + background: linear-gradient(90deg, var(--violet), var(--cyan)); + box-shadow: 0 0 14px rgba(124,92,255,.7); +} + +/* ── nav ──────────────────────────────────────────────── */ + +.nav { + position: sticky; top: 0; z-index: 150; + display: flex; align-items: center; gap: 1.5rem; + padding: 0.85rem clamp(1rem, 4vw, 2.6rem); + background: rgba(6,6,8,0.62); + backdrop-filter: blur(14px) saturate(150%); + -webkit-backdrop-filter: blur(14px) saturate(150%); + border-bottom: 1px solid transparent; + transition: border-color .3s, background .3s; +} +.nav.stuck { border-bottom-color: var(--line); background: rgba(6,6,8,0.84); } + +.brand { display: inline-flex; align-items: center; gap: 0.55rem; font-weight: 600; letter-spacing: -0.02em; } +.mark { + width: 18px; height: 18px; border-radius: 6px; + background: linear-gradient(140deg, var(--violet), var(--cyan)); + box-shadow: 0 0 18px rgba(124,92,255,.55); +} + +.nav-links { display: flex; gap: 1.5rem; margin-left: auto; font-size: 14px; color: var(--dim); } +.nav-links a { position: relative; transition: color .2s; } +.nav-links a::after { + content: ""; position: absolute; left: 0; right: 100%; bottom: -4px; height: 1px; + background: linear-gradient(90deg, var(--violet), var(--cyan)); transition: right .3s var(--ease); +} +.nav-links a:hover { color: var(--text); } +.nav-links a:hover::after { right: 0; } + +/* ── buttons ──────────────────────────────────────────── */ + +.btn { + display: inline-flex; align-items: center; gap: 0.45rem; + font-size: 14.5px; font-weight: 500; letter-spacing: -0.01em; + padding: 0.7rem 1.15rem; border-radius: 10px; + border: 1px solid var(--line-2); background: var(--panel); + color: var(--text); white-space: nowrap; + transition: transform .18s var(--ease), border-color .2s, background .2s, box-shadow .25s; +} +.btn:hover { transform: translateY(-1px); border-color: rgba(255,255,255,.26); background: var(--panel-2); } +.btn.sm { padding: 0.45rem 0.8rem; font-size: 13px; margin-left: 0.4rem; } +.btn.primary { + background: linear-gradient(180deg, #8E72FF, #6A46F5); + border-color: rgba(255,255,255,.18); color: #fff; font-weight: 560; + box-shadow: 0 8px 30px -8px rgba(124,92,255,.75), inset 0 1px 0 rgba(255,255,255,.22); +} +.btn.primary:hover { box-shadow: 0 12px 40px -8px rgba(124,92,255,.95), inset 0 1px 0 rgba(255,255,255,.3); } + +.row { display: flex; flex-wrap: wrap; gap: 0.7rem; margin: 2rem 0 2.4rem; } +.row.center { justify-content: center; } + +/* ── hero ─────────────────────────────────────────────── */ + +.hero { padding: clamp(4rem, 11vh, 8rem) 0 clamp(3rem, 7vh, 5rem); } +.shell { max-width: 1080px; margin: 0 auto; padding: 0 clamp(1.15rem, 5vw, 2.6rem); } +.shell.narrow { max-width: 760px; } + +.badge { + display: inline-flex; align-items: center; gap: 0.55rem; + font-size: 13px; color: var(--dim); + padding: 0.42rem 0.85rem; border-radius: 999px; + border: 1px solid var(--line-2); background: rgba(255,255,255,.035); + backdrop-filter: blur(8px); + transition: border-color .25s, color .25s, transform .2s var(--ease); +} +.badge strong { color: var(--text); font-weight: 560; } +.badge:hover { border-color: rgba(124,92,255,.6); color: var(--text); transform: translateY(-1px); } +.badge-arrow { transition: transform .25s var(--ease); } +.badge:hover .badge-arrow { transform: translateX(3px); } +.ping { width: 6px; height: 6px; border-radius: 50%; background: #34D399; box-shadow: 0 0 0 0 rgba(52,211,153,.7); animation: ping 2.4s infinite; } +@keyframes ping { 70% { box-shadow: 0 0 0 8px rgba(52,211,153,0); } 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); } } + +.display { + font-size: clamp(2.9rem, 8.2vw, 5.6rem); + line-height: 0.99; letter-spacing: -0.042em; font-weight: 620; + margin: 1.6rem 0 1.4rem; max-width: 22ch; +} +.grad { + background: linear-gradient(96deg, #FFFFFF 4%, #C6B6FF 42%, #7C5CFF 72%, #35C8F0 100%); + -webkit-background-clip: text; background-clip: text; color: transparent; +} + +.sub { font-size: clamp(1.02rem, 2.1vw, 1.22rem); color: var(--dim); max-width: 60ch; margin: 0; } +.sub strong { color: var(--text); font-weight: 540; } + +/* terminal */ +.terminal { + border: 1px solid var(--line); border-radius: 12px; overflow: hidden; + background: linear-gradient(180deg, rgba(255,255,255,.035), rgba(255,255,255,.012)); + backdrop-filter: blur(8px); max-width: 780px; cursor: copy; + transition: border-color .25s, box-shadow .3s; +} +.terminal:hover { border-color: rgba(124,92,255,.45); box-shadow: 0 20px 60px -30px rgba(124,92,255,.8); } +.term-bar { + display: flex; align-items: center; gap: 6px; + padding: 0.55rem 0.85rem; border-bottom: 1px solid var(--line); + font-family: var(--mono); font-size: 11.5px; color: var(--faint); +} +.tdot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,.16); } +.term-title { margin-left: 0.6rem; } +.copy-hint { margin-left: auto; opacity: 0; transition: opacity .2s; } +.terminal:hover .copy-hint { opacity: 1; } +.terminal pre { margin: 0; padding: 0.95rem 1rem; overflow-x: auto; } +.terminal code { + background: none; border: none; padding: 0; color: #D8D3F0; + font-size: clamp(11px, 1.55vw, 13px); white-space: pre; +} +.terminal .p { color: var(--violet); margin-right: 0.5rem; } + +.trust { + display: flex; flex-wrap: wrap; gap: 0.5rem 1.4rem; list-style: none; + margin: 1.5rem 0 0; padding: 0; + font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em; color: var(--faint); +} +.trust li { display: flex; align-items: center; gap: 0.5rem; } +.trust li::before { content: ""; width: 4px; height: 4px; border-radius: 50%; background: var(--violet); opacity: .8; } + +.scroll-cue { display: flex; justify-content: center; margin-top: clamp(2rem, 6vh, 4rem); } +.scroll-cue span { + width: 20px; height: 32px; border: 1px solid var(--line-2); border-radius: 12px; position: relative; +} +.scroll-cue span::after { + content: ""; position: absolute; left: 50%; top: 7px; width: 3px; height: 6px; margin-left: -1.5px; + border-radius: 2px; background: var(--violet); animation: cue 1.9s var(--ease) infinite; +} +@keyframes cue { 0%{opacity:0; transform:translateY(-3px)} 40%{opacity:1} 100%{opacity:0; transform:translateY(11px)} } + +/* ── marquee ──────────────────────────────────────────── */ + +.marquee { + border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); + padding: 0.85rem 0; overflow: hidden; + background: rgba(255,255,255,.014); + mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); + -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); +} +.marquee-track { + display: flex; align-items: center; gap: 1.6rem; width: max-content; + animation: scrollx 46s linear infinite; + font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em; color: var(--faint); + text-transform: lowercase; +} +.marquee-track i { color: var(--violet); font-style: normal; font-size: 8px; } +@keyframes scrollx { to { transform: translateX(-50%); } } + +/* ── sections ─────────────────────────────────────────── */ + +.section { padding: clamp(4.5rem, 12vh, 8.5rem) 0; } +.section.closing { padding-bottom: clamp(5rem, 14vh, 9rem); } + +.label { + font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase; + color: var(--violet); margin: 0 0 1rem; opacity: .95; +} + +h2 { + font-size: clamp(1.85rem, 4.6vw, 3.05rem); line-height: 1.06; letter-spacing: -0.033em; + font-weight: 600; margin: 0 0 1.3rem; max-width: 21ch; +} +h2.center { max-width: 24ch; margin-left: auto; margin-right: auto; text-align: center; } +h3 { font-size: 1.02rem; font-weight: 580; letter-spacing: -0.017em; margin: 0 0 0.4rem; } + +.body { color: var(--dim); max-width: 66ch; margin: 0 0 1.1rem; font-size: 1.02rem; } +.body strong { color: var(--text); font-weight: 540; } +.body em { color: var(--text); font-style: italic; } +.body.small { font-size: 0.92rem; } +.body.center { text-align: center; margin-left: auto; margin-right: auto; } + +/* ── quote ────────────────────────────────────────────── */ + +.quote { + margin: 2.6rem 0; padding: clamp(1.6rem, 4vw, 2.4rem); + border: 1px solid var(--line); border-radius: var(--r); + background: linear-gradient(150deg, rgba(124,92,255,.10), rgba(255,255,255,.02) 55%); + position: relative; overflow: hidden; +} +.quote::before { + content: ""; position: absolute; inset: 0; pointer-events: none; + background: radial-gradient(70% 120% at 0% 0%, rgba(124,92,255,.22), transparent 60%); +} +.quote blockquote { + margin: 0; font-size: clamp(1.15rem, 2.9vw, 1.75rem); line-height: 1.32; + letter-spacing: -0.025em; color: var(--text); position: relative; +} +.quote mark { + background: linear-gradient(180deg, transparent 62%, rgba(255,77,109,.35) 62%); + color: var(--text); padding: 0 .1em; +} +.quote figcaption { + margin-top: 1.2rem; font-family: var(--mono); font-size: 12px; color: var(--faint); + display: flex; flex-wrap: wrap; gap: 0.35rem 0.7rem; align-items: baseline; position: relative; +} +.quote cite { font-style: normal; } +.quote figcaption a { color: var(--violet); } +.quote figcaption a:hover { color: #A38BFF; } + +.split { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); margin: 2rem 0; } +.split-col { border: 1px solid var(--line); border-radius: var(--r); padding: 1.3rem 1.4rem; background: var(--panel); } +.split-col h3 { color: var(--dim); font-family: var(--mono); font-size: 11.5px; letter-spacing: .12em; text-transform: uppercase; margin-bottom: 0.6rem; } +.split-col p { margin: 0; color: var(--text); font-size: 1rem; } +.split-col.accent { border-color: rgba(255,77,109,.32); background: linear-gradient(160deg, rgba(255,77,109,.09), rgba(255,255,255,.02)); } +.split-col.accent h3 { color: var(--rose); } + +/* ── console ──────────────────────────────────────────── */ + +.console { + border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; margin: 2.4rem 0; + background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.012)); + backdrop-filter: blur(10px); +} +.console-bar { + display: flex; align-items: center; gap: 0.65rem; + padding: 0.7rem 1rem; border-bottom: 1px solid var(--line); + font-size: 12px; color: var(--dim); background: rgba(255,255,255,.02); +} +.console-meta { margin-left: auto; color: var(--faint); } +.live { width: 7px; height: 7px; border-radius: 50%; background: var(--cyan); box-shadow: 0 0 10px var(--cyan); animation: livep 2s ease-in-out infinite; } +@keyframes livep { 50% { opacity: .35; } } + +.console-body { list-style: none; margin: 0; padding: 0.5rem 0; font-family: var(--mono); font-size: 12.8px; } +.console-body li { + display: grid; grid-template-columns: 13ch 1fr auto; gap: 0.9rem; align-items: baseline; + padding: 0.44rem 1rem; opacity: 0; transform: translateY(4px); +} +.console-body li.in { opacity: 1; transform: none; transition: opacity .45s var(--ease), transform .45s var(--ease); } +.console-body .key { color: var(--violet); } +.console-body .val { color: var(--text); } +.console-body .tag { color: var(--faint); font-size: 11px; } +.console-body li.verdict { + margin-top: 0.5rem; padding-top: 0.9rem; border-top: 1px solid var(--line); + background: linear-gradient(90deg, rgba(255,77,109,.08), transparent 75%); +} +.console-body li.verdict .key { color: var(--rose); } +.console-body li.verdict .val { color: #FFC4CE; } + +/* ── stats ────────────────────────────────────────────── */ + +.stats { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); margin: 2.4rem 0; } +.stat { + border: 1px solid var(--line); border-radius: var(--r); padding: 1.5rem 1.4rem; + background: var(--panel); position: relative; overflow: hidden; +} +.stat-n { + font-family: var(--mono); font-size: clamp(1.8rem, 4.6vw, 2.5rem); font-weight: 600; + letter-spacing: -0.03em; line-height: 1; margin-bottom: 0.85rem; + background: linear-gradient(180deg, #fff, #9E8BFF); -webkit-background-clip: text; background-clip: text; color: transparent; +} +.stat-n em { display: block; font-family: var(--sans); font-style: normal; font-size: 11px; + letter-spacing: .13em; text-transform: uppercase; color: var(--faint); margin-top: 0.5rem; + background: none; -webkit-text-fill-color: var(--faint); } +.stat p { margin: 0; color: var(--dim); font-size: 0.95rem; } +.stat p strong { color: var(--text); font-weight: 560; } +.stat.hot { border-color: rgba(255,77,109,.3); background: linear-gradient(160deg, rgba(255,77,109,.10), rgba(255,255,255,.02)); } +.stat.hot .stat-n { background: linear-gradient(180deg, #FFD1D9, #FF4D6D); -webkit-background-clip: text; background-clip: text; } + +/* ── cards ────────────────────────────────────────────── */ + +.cards, .letters { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); margin-top: 2.2rem; } + +.card { + position: relative; border: 1px solid var(--line); border-radius: var(--r); + padding: 1.45rem; background: var(--panel); overflow: hidden; + transition: border-color .25s, transform .25s var(--ease), background .25s; +} +.card::before { + content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity .3s; + background: radial-gradient(340px circle at var(--cx,50%) var(--cy,0%), rgba(124,92,255,.16), transparent 62%); +} +.card:hover { transform: translateY(-3px); border-color: var(--line-2); } +.card:hover::before { opacity: 1; } +.card p { margin: 0; color: var(--dim); font-size: 0.96rem; } +.card strong { color: var(--text); font-weight: 540; } +.card em { color: var(--text); font-style: italic; } + +.card.risk .chip { + display: inline-block; font-family: var(--mono); font-size: 11px; color: var(--rose); + border: 1px solid rgba(255,77,109,.32); border-radius: 6px; padding: 0.06rem 0.4rem; margin-bottom: 0.75rem; +} +.card.risk h3 { margin-bottom: 0.55rem; } + +.letter .to { + display: block; font-family: var(--mono); font-size: 11.5px; letter-spacing: .11em; + text-transform: uppercase; color: var(--violet); margin-bottom: 0.7rem; +} +.letter.self { border-color: rgba(255,176,32,.28); background: linear-gradient(160deg, rgba(255,176,32,.07), rgba(255,255,255,.02)); } +.letter.self .to { color: var(--amber); } + +/* ── terms ────────────────────────────────────────────── */ + +.terms { list-style: none; counter-reset: t; margin: 2.2rem 0; padding: 0; border-top: 1px solid var(--line); } +.terms li { + counter-increment: t; position: relative; + padding: 1.35rem 0 1.35rem 4.2rem; border-bottom: 1px solid var(--line); + transition: background .25s, padding-left .25s var(--ease); +} +.terms li::before { + content: counter(t, decimal-leading-zero); + position: absolute; left: 0.9rem; top: 1.5rem; + font-family: var(--mono); font-size: 12px; color: var(--faint); transition: color .25s; +} +.terms li:hover { background: rgba(255,255,255,.022); padding-left: 4.6rem; } +.terms li:hover::before { color: var(--violet); } +.terms p { margin: 0; color: var(--dim); font-size: 0.97rem; max-width: 72ch; } + +/* ── code ─────────────────────────────────────────────── */ + +.codeblock { + border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; margin: 2.2rem 0; + background: rgba(255,255,255,.02); +} +.code-bar { + display: flex; align-items: center; justify-content: space-between; gap: 1rem; + padding: 0.65rem 1rem; border-bottom: 1px solid var(--line); font-size: 11.5px; color: var(--dim); +} +.codeblock pre { margin: 0; padding: 1.1rem; overflow-x: auto; } +.codeblock code { + background: none; border: none; padding: 0; color: #D5D0E8; + font-size: 12.4px; line-height: 1.8; white-space: pre; +} +.codeblock .c { color: var(--faint); } + +.denylist { border: 1px solid rgba(255,77,109,.25); border-radius: var(--r); padding: 1.5rem; background: rgba(255,77,109,.045); } +.denylist h3 { margin-bottom: 0.9rem; } +.deny-items { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; } +.deny-items span { + font-family: var(--mono); font-size: 12px; color: #FFB9C4; + border: 1px solid rgba(255,77,109,.3); border-radius: 8px; padding: 0.3rem 0.65rem; + text-decoration: line-through; text-decoration-color: rgba(255,77,109,.7); +} +.denylist p { margin: 0; color: var(--dim); font-size: 0.96rem; } + +/* ── bento ────────────────────────────────────────────── */ + +.bento { display: grid; gap: 0.85rem; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); margin: 2.2rem 0; } +.tile { + border: 1px solid var(--line); border-radius: var(--r); padding: 1.35rem; background: var(--panel); + transition: border-color .25s, transform .25s var(--ease), background .25s; +} +.tile:hover { border-color: var(--line-2); transform: translateY(-2px); background: var(--panel-2); } +.tile.wide { grid-column: span 2; } +.tile h3 { margin-bottom: 0.45rem; } +.tile p { margin: 0; color: var(--dim); font-size: 0.93rem; } + +/* ── needs ────────────────────────────────────────────── */ + +.needs { display: grid; gap: 0.7rem; margin: 2.2rem 0; } +.need { + display: flex; gap: 1rem; align-items: baseline; + border: 1px solid var(--line); border-radius: 12px; padding: 0.95rem 1.15rem; background: var(--panel); +} +.pri { + font-family: var(--mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; + color: #0A0616; background: linear-gradient(180deg, #A78BFA, #7C5CFF); + border-radius: 6px; padding: 0.2rem 0.45rem; white-space: nowrap; font-weight: 600; +} +.pri.sec { background: none; color: var(--faint); border: 1px solid var(--line-2); } +.need p { margin: 0; color: var(--dim); font-size: 0.95rem; } + +.kicker-end { + text-align: center; font-size: clamp(1.2rem, 3vw, 1.7rem); line-height: 1.35; + letter-spacing: -0.025em; color: var(--dim); margin: 3rem 0 0; +} +.kicker-end .grad { font-weight: 600; } + +/* ── footer ───────────────────────────────────────────── */ + +footer { border-top: 1px solid var(--line); padding: 2.6rem 0 3.4rem; background: rgba(255,255,255,.012); } +.foot-row { display: flex; flex-wrap: wrap; gap: 1.4rem; justify-content: space-between; align-items: flex-start; margin-bottom: 1.8rem; } +.foot-row p { margin: 0.6rem 0 0; font-size: 11.5px; } +.foot-links { display: flex; flex-wrap: wrap; gap: 1.3rem; font-size: 13.5px; color: var(--dim); } +.foot-links a:hover { color: var(--text); } +.fine { font-size: 11.5px; color: var(--faint); line-height: 1.75; max-width: 78ch; margin: 0; font-family: var(--mono); } +.fine em { font-style: italic; } + +/* ── reveal ───────────────────────────────────────────── */ + +.reveal { opacity: 0; transform: translateY(16px); } +.reveal.in { opacity: 1; transform: none; transition: opacity .7s var(--ease), transform .7s var(--ease); } + +/* ── responsive ───────────────────────────────────────── */ + +@media (max-width: 860px) { + .tile.wide { grid-column: span 1; } + .nav-links { display: none; } + .nav .btn.sm { margin-left: auto; } +} +@media (max-width: 620px) { + .console-body li { grid-template-columns: 1fr; gap: 0.15rem; } + .console-body .tag { display: none; } + .terms li { padding-left: 3.2rem; } + .terms li::before { left: 0.4rem; } + .display { letter-spacing: -0.035em; } +} + +/* ── reduced motion ───────────────────────────────────── */ + +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto; } + .blob, .marquee-track, .ping, .live, .scroll-cue span::after { animation: none !important; } + .spotlight { display: none; } + .reveal, .console-body li { opacity: 1 !important; transform: none !important; transition: none !important; } + * { transition-duration: .01ms !important; } +}