diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 4ca4f85..af87484 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -47,7 +47,7 @@ if (rulesHtml.includes('script src="http') || rulesHtml.includes('rel="styleshee for (const token of ['@media(min-width:2200px)','@media(max-width:900px)','@media(max-width:600px)','prefers-reduced-motion']) if (!rulesCss.includes(token)) throw new Error(`missing rules responsive contract ${token}`); console.log('rules standalone verification passed'); for (const token of ['id="catalog"','id="skill-filter"','id="skill-list"','id="detail"','?author=Name&skill=skill-id&view=improved']) if (!reviewHtml.includes(token)) throw new Error(`missing review page content ${token}`); -for (const token of ["from './catalog.js'", "from './files.js'",'function renderList','function renderDetail','loadSelectedFile','function selectFromUrl','function syncUrl','URLSearchParams','navigator.clipboard','document.execCommand','download','data-file','searchParams.set(\'file\'']) if (!reviewJs.includes(token)) throw new Error(`missing review interaction ${token}`); +for (const token of ["from './catalog.js'", "from './files.js'",'function renderList','function renderDetail','loadSelectedFile','schedulePackageSearch','fetchSource','packageSearchText','diffMarkup','diffRows','data-diff','searchParams.set(\'compare\'','markdownMarkup','data-render','searchParams.set(\'render\'','AUTHOR ·','SKILL ·','function selectFromUrl','function syncUrl','URLSearchParams','navigator.clipboard','document.execCommand','download','data-file','searchParams.set(\'file\'']) if (!reviewJs.includes(token)) throw new Error(`missing review interaction ${token}`); for (const token of ['ndo-repro','gfiber-logging','confluence-page','diagram-plantuml','page-reviewer','unslop','reference.md','files =']) if (!`${reviewFiles}\n${read('skills-review/submitted-files.js')}`.includes(token)) throw new Error(`missing review file manifest ${token}`); if ((reviewCatalog.match(/id:'/g) || []).length + (read('skills-review/submitted-catalog.js').match(/id:'/g) || []).length !== 21) throw new Error('review catalog does not cover all submissions'); if (!reviewCatalog.includes('hardcoded password') || !reviewCatalog.includes('safety-redacted') || !reviewJs.includes('[REDACTED]') || !reviewJs.includes('[REDACTED LOCAL USER]') || !reviewJs.includes('[REDACTED USER]')) throw new Error('review catalog does not record secret safety handling'); @@ -78,5 +78,7 @@ for (const token of ['currentContent','unchangedDraft','state.preview = button.d if (reviewJs.includes("state.preview = 'original'; syncUrl(); renderDetail(); loadSelectedFile();")) throw new Error('file selection still resets improved mode'); console.log('review file-mode verification passed'); for (const token of ['const changeRows','function lensMarkup','data-lens','CHANGE LENS','What changed — and why.']) if (!reviewJs.includes(token)) throw new Error(`review change-lens contract missing ${token}`); -for (const token of ['.change-lens','.change-rows','@media(max-width:620px)','prefers-reduced-motion']) if (!reviewLensCss.includes(token)) throw new Error(`review change-lens CSS missing ${token}`); +for (const token of ['.change-lens','.change-rows','.skill-diff','.diff-lines','@media(max-width:620px)','prefers-reduced-motion']) if (!reviewLensCss.includes(token)) throw new Error(`review change-lens CSS missing ${token}`); console.log('review change-lens verification passed'); +for (const token of ['.markdown-preview','max-height:540px','.markdown-table-wrap','.markdown-frontmatter']) if (!read('skills-review/styles.css').includes(token)) throw new Error(`review markdown preview contract missing ${token}`); +console.log('review markdown preview verification passed'); diff --git a/skills-review/app.js b/skills-review/app.js index fe127df..d6b58e0 100644 --- a/skills-review/app.js +++ b/skills-review/app.js @@ -1,7 +1,7 @@ import { catalog } from './catalog.js'; import { files } from './files.js'; -const state = { selected: catalog[0], query: '', preview: 'original', file: null, sourceByPath: new Map(), lens: false }; +const state = { selected: catalog[0], query: '', preview: 'original', file: null, sourceByPath: new Map(), lens: false, rendered: false, diff: false, searching: false, contentMatches: new Set(), searchTimer: null, searchRequest: 0 }; const $ = (selector) => document.querySelector(selector); const escape = (value) => value.replace(/[&<>"']/g, (character) => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[character]); const redact = (value, entry) => { @@ -14,9 +14,39 @@ const copy = async (content) => { const textarea = document.createElement('textarea'); textarea.value = content; textarea.setAttribute('readonly', ''); textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.append(textarea); textarea.select(); document.execCommand('copy'); textarea.remove(); }; const packageFiles = (entry = state.selected) => files[entry.id] || [{ name: 'SKILL.md', path: entry.path, kind: 'skill' }]; +const packageSearchText = (entry) => packageFiles(entry).map((file) => `${file.name} ${file.kind}`).join(' '); const unchangedDraft = (file) => `# ${file.name}\n\n> Kept as-is in the improved package\n\nThis ${file.kind} file was not rewritten. Select **Change lens** to see why the improved draft concentrates its changes in the main skill contract.`; const currentSource = () => state.sourceByPath.get(state.file.path); const currentContent = () => state.preview === 'original' ? (currentSource() || 'Loading original file…') : (state.file.improved || (state.file.name === 'SKILL.md' ? state.selected.improved : (currentSource() ? `# ${state.file.name}\n\n> Kept as-is in the improved package\n\n${currentSource()}` : unchangedDraft(state.file)))); +const inlineMarkdown = (value) => escape(value) + .replace(/`([^`]+)`/g, '$1') + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/(?$1') + .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '$1 ↗'); +const tableCells = (line) => line.trim().replace(/^\||\|$/g, '').split('|').map((cell) => cell.trim()); +const isTableDivider = (line) => /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line); +function markdownMarkup(markdown) { + const lines = markdown.replace(/\r/g, '').split('\n'); let index = 0; const out = []; + if (lines[0] === '---') { + const end = lines.indexOf('---', 1); + if (end > 0) { out.push(`
${lines.slice(1, end).map((line) => { const [key, ...rest] = line.split(':'); return rest.length ? `
${escape(key)}
${inlineMarkdown(rest.join(':').trim())}
` : ''; }).join('')}
`); index = end + 1; } + } + const startsBlock = (line, next) => !line || /^#{1,6}\s+/.test(line) || /^```/.test(line) || /^[-*+]\s+/.test(line) || /^\d+\.\s+/.test(line) || /^>\s?/.test(line) || /^---+$/.test(line) || (line.includes('|') && isTableDivider(next || '')); + while (index < lines.length) { + const line = lines[index]; + if (!line.trim()) { index += 1; continue; } + const heading = line.match(/^(#{1,6})\s+(.+)$/); + if (heading) { const level = heading[1].length; out.push(`${inlineMarkdown(heading[2])}`); index += 1; continue; } + if (/^```/.test(line)) { const language = line.slice(3).trim(); const code = []; index += 1; while (index < lines.length && !/^```/.test(lines[index])) code.push(lines[index++]); if (index < lines.length) index += 1; out.push(`
${escape(code.join('\n'))}
`); continue; } + if (line.includes('|') && isTableDivider(lines[index + 1] || '')) { const headings = tableCells(line); index += 2; const rows = []; while (index < lines.length && lines[index].includes('|') && lines[index].trim()) rows.push(tableCells(lines[index++])); out.push(`
${headings.map((cell) => ``).join('')}${rows.map((row) => `${headings.map((_, cell) => ``).join('')}`).join('')}
${inlineMarkdown(cell)}
${inlineMarkdown(row[cell] || '')}
`); continue; } + const list = line.match(/^([-*+]|\d+\.)\s+(.+)$/); + if (list) { const ordered = /\d+\./.test(list[1]); const items = []; while (index < lines.length) { const item = lines[index].match(ordered ? /^\d+\.\s+(.+)$/ : /^[-*+]\s+(.+)$/); if (!item) break; items.push(`
  • ${inlineMarkdown(item[1])}
  • `); index += 1; } out.push(`<${ordered ? 'ol' : 'ul'}>${items.join('')}`); continue; } + if (/^>\s?/.test(line)) { const quote = []; while (index < lines.length && /^>\s?/.test(lines[index])) quote.push(lines[index++].replace(/^>\s?/, '')); out.push(`
    ${inlineMarkdown(quote.join(' '))}
    `); continue; } + if (/^---+$/.test(line)) { out.push('
    '); index += 1; continue; } + const paragraph = [line]; index += 1; while (index < lines.length && !startsBlock(lines[index], lines[index + 1])) paragraph.push(lines[index++]); out.push(`

    ${inlineMarkdown(paragraph.join(' '))}

    `); + } + return out.join(''); +} const changeRows = (entry) => entry.improve.map((why, index) => ({ kind: ['SAFETY', 'SCOPE', 'EVIDENCE', 'STRUCTURE'][index] || 'CLARITY', before: index === 0 ? 'The submitted guidance leaves a material decision implicit.' : 'The submitted package carries detail without a clear boundary.', @@ -24,7 +54,7 @@ const changeRows = (entry) => entry.improve.map((why, index) => ({ why })); -function visible() { return catalog.filter((item) => `${item.author} ${item.title} ${item.focus}`.toLowerCase().includes(state.query)); } +function visible() { return catalog.filter((item) => `${item.author} ${item.title} ${item.id} ${item.focus} ${packageSearchText(item)}`.toLowerCase().includes(state.query) || state.contentMatches.has(item.id)); } function syncUrl() { const url = new URL(window.location.href); url.searchParams.set('author', state.selected.author); @@ -32,6 +62,8 @@ function syncUrl() { url.searchParams.set('view', state.preview); if (state.file && state.file.name !== 'SKILL.md') url.searchParams.set('file', state.file.name); else url.searchParams.delete('file'); if (state.preview === 'improved' && state.lens) url.searchParams.set('lens', 'changes'); else url.searchParams.delete('lens'); + if (state.rendered) url.searchParams.set('render', 'preview'); else url.searchParams.delete('render'); + if (state.diff) url.searchParams.set('compare', 'diff'); else url.searchParams.delete('compare'); history.replaceState({}, '', url); } function selectFromUrl() { @@ -43,43 +75,79 @@ function selectFromUrl() { state.query = byAuthor ? state.selected.author.toLowerCase() : ''; state.preview = view === 'improved' ? 'improved' : 'original'; state.lens = state.preview === 'improved' && params.get('lens') === 'changes'; + state.rendered = params.get('render') === 'preview'; + state.diff = params.get('compare') === 'diff'; state.file = packageFiles().find((item) => item.name === params.get('file')) || packageFiles()[0]; $('#skill-filter').value = byAuthor ? state.selected.author : ''; + $('#submission-count').textContent = `${catalog.length} submissions`; } function renderList() { const items = visible(); - $('#count').textContent = `${items.length} of ${catalog.length} reviewed`; - $('#skill-list').innerHTML = items.map((item) => ``).join(''); + $('#count').textContent = state.searching ? `Searching package files… ${items.length} of ${catalog.length}` : `${items.length} of ${catalog.length} reviewed`; + $('#skill-list').innerHTML = items.map((item) => ``).join(''); $('#skill-list').querySelectorAll('button').forEach((button) => button.addEventListener('click', () => { - state.selected = catalog.find((item) => item.id === button.dataset.id); state.file = packageFiles()[0]; state.preview = 'original'; state.lens = false; syncUrl(); renderList(); renderDetail(); loadSelectedFile(); + state.selected = catalog.find((item) => item.id === button.dataset.id); state.file = packageFiles()[0]; state.preview = 'original'; state.lens = false; state.rendered = false; state.diff = false; syncUrl(); renderList(); renderDetail(); loadSelectedFile(); })); } -async function loadSelectedFile() { - const entry = state.selected; const file = state.file; +async function fetchSource(entry, file) { if (state.sourceByPath.has(file.path)) return state.sourceByPath.get(file.path); try { state.sourceByPath.set(file.path, redact(await (await fetch(file.path)).text(), entry)); } catch { state.sourceByPath.set(file.path, '# Original preview unavailable\n\nServe this site from the repository root to load the submitted source.'); } + return state.sourceByPath.get(file.path); +} +async function loadSelectedFile() { + const entry = state.selected; const file = state.file; + await fetchSource(entry, file); if (state.selected.id === entry.id && state.file.path === file.path) renderDetail(); return state.sourceByPath.get(file.path); } +function schedulePackageSearch() { + clearTimeout(state.searchTimer); const query = state.query; const request = ++state.searchRequest; state.contentMatches.clear(); + if (query.length < 3) { state.searching = false; renderList(); return; } + state.searchTimer = setTimeout(async () => { + state.searching = true; renderList(); + await Promise.all(catalog.flatMap((entry) => packageFiles(entry).map((file) => fetchSource(entry, file)))); + if (request !== state.searchRequest) return; + state.contentMatches = new Set(catalog.filter((entry) => packageFiles(entry).some((file) => state.sourceByPath.get(file.path)?.toLowerCase().includes(query))).map((entry) => entry.id)); + state.searching = false; renderList(); + }, 180); +} function lensMarkup(entry) { return `
    CHANGE LENS

    What changed — and why.

    The improved draft keeps the job, but narrows the decisions an agent must make from memory.

    ${changeRows(entry).map((change, index) => `
    0${index + 1} / ${change.kind}
    − Before

    ${escape(change.before)}

    + After

    ${escape(change.after)}

    `).join('')}
    `; } +function diffRows(before, after) { + const oldLines = before.split('\n'); const newLines = after.split('\n'); const rows = []; let oldIndex = 0; let newIndex = 0; + while (oldIndex < oldLines.length || newIndex < newLines.length) { + if (oldLines[oldIndex] === newLines[newIndex]) { rows.push(`

    ${oldIndex + 1}${escape(oldLines[oldIndex] || '')}

    `); oldIndex += 1; newIndex += 1; continue; } + const oldAhead = oldLines.slice(oldIndex + 1, oldIndex + 9).indexOf(newLines[newIndex]); const newAhead = newLines.slice(newIndex + 1, newIndex + 9).indexOf(oldLines[oldIndex]); + if (newIndex < newLines.length && (oldIndex >= oldLines.length || (oldAhead === -1 && newAhead !== -1) || newAhead < oldAhead)) { rows.push(`

    +${escape(newLines[newIndex++])}

    `); continue; } + if (oldIndex < oldLines.length) { rows.push(`

    ${escape(oldLines[oldIndex++])}

    `); continue; } + } + return rows.join(''); +} +function diffMarkup(entry) { + if (state.file.name !== 'SKILL.md') return `
    PACKAGE DIFF

    Supporting file unchanged.

    This review only rewrites the main skill contract. The selected ${escape(state.file.kind)} file remains available in its original form.

    `; + return `
    SKILL DIFF

    Original → improved draft

    Green lines are additions; red lines are removals. Unmarked lines are shared context.

    ${diffRows(currentSource() || 'Loading original Markdown…', entry.improved)}
    `; +} function previewMarkup(entry, available) { if (state.preview === 'improved' && state.lens) return lensMarkup(entry); + if (state.diff) return diffMarkup(entry); const label = state.preview === 'original' ? 'ORIGINAL / SAFETY-REDACTED WHERE NEEDED' : 'IMPROVED DRAFT / PACKAGE-AWARE'; - return `
    ${label}
    ${state.preview === 'improved' ? '' : ''}
    ${escape(currentContent())}
    `; + const body = state.rendered ? `
    ${markdownMarkup(currentContent())}
    ` : `
    ${escape(currentContent())}
    `; + return `
    ${label}
    ${state.preview === 'improved' ? '' : ''}
    ${body}
    `; } function renderDetail() { const entry = state.selected; const available = packageFiles(entry); $('#detail').innerHTML = `
    ${escape(entry.status)}

    ${escape(entry.title)}

    Submitted by ${escape(entry.author)} ·

    THE JOB

    ${escape(entry.focus)}

    WHAT'S ALREADY WORKING
    HIGHEST-VALUE IMPROVEMENTS
    ${previewMarkup(entry, available)}`; - $('#detail').querySelectorAll('[data-file]').forEach((button) => button.addEventListener('click', () => { state.file = available.find((item) => item.name === button.dataset.file) || available[0]; syncUrl(); renderDetail(); loadSelectedFile(); })); - $('#detail').querySelectorAll('[data-preview]').forEach((button) => button.addEventListener('click', () => { state.preview = button.dataset.preview; state.lens = false; syncUrl(); renderDetail(); loadSelectedFile(); })); - $('#detail').querySelectorAll('[data-lens]').forEach((button) => button.addEventListener('click', () => { state.lens = !state.lens; syncUrl(); renderDetail(); })); + $('#detail').querySelectorAll('[data-file]').forEach((button) => button.addEventListener('click', () => { state.file = available.find((item) => item.name === button.dataset.file) || available[0]; state.rendered = false; state.diff = false; syncUrl(); renderDetail(); loadSelectedFile(); })); + $('#detail').querySelectorAll('[data-preview]').forEach((button) => button.addEventListener('click', () => { state.preview = button.dataset.preview; state.lens = false; state.rendered = false; state.diff = false; syncUrl(); renderDetail(); loadSelectedFile(); })); + $('#detail').querySelectorAll('[data-lens]').forEach((button) => button.addEventListener('click', () => { state.lens = !state.lens; state.rendered = false; state.diff = false; syncUrl(); renderDetail(); })); + $('#detail').querySelectorAll('[data-diff]').forEach((button) => button.addEventListener('click', async () => { await loadSelectedFile(); state.diff = !state.diff; state.rendered = false; state.lens = false; syncUrl(); renderDetail(); })); + $('[data-render]')?.addEventListener('click', async () => { await loadSelectedFile(); state.rendered = !state.rendered; syncUrl(); renderDetail(); }); $('[data-copy]')?.addEventListener('click', async () => { await loadSelectedFile(); await copy(currentContent()); $('[data-copy]').textContent = 'Copied'; }); $('[data-download]')?.addEventListener('click', async () => { await loadSelectedFile(); download(`${entry.id}-${state.file.name.replaceAll('/', '-')}-${state.preview}.md`, currentContent()); }); } -$('#skill-filter').addEventListener('input', (event) => { state.query = event.target.value.toLowerCase().trim(); renderList(); }); +$('#skill-filter').addEventListener('input', (event) => { state.query = event.target.value.toLowerCase().trim(); renderList(); schedulePackageSearch(); }); window.addEventListener('popstate', () => { selectFromUrl(); renderList(); renderDetail(); loadSelectedFile(); }); selectFromUrl(); renderList(); renderDetail(); loadSelectedFile(); diff --git a/skills-review/change-lens.css b/skills-review/change-lens.css index ca537b2..a97b603 100644 --- a/skills-review/change-lens.css +++ b/skills-review/change-lens.css @@ -1 +1,4 @@ .change-lens{border:1px solid var(--ink);background:#123042;color:var(--paper);animation:lens-enter .28s ease both}.change-lens>header{display:flex;justify-content:space-between;gap:20px;align-items:start;padding:22px 24px;border-bottom:1px solid #466274}.change-lens span{color:var(--gold);font:700 10px ui-monospace,monospace;letter-spacing:.1em}.change-lens h3{margin:7px 0 0;font-size:clamp(24px,3vw,40px);line-height:1.02;letter-spacing:-.05em}.change-lens>header button{padding:9px 11px;border:1px solid #557080;color:var(--paper);background:transparent;cursor:pointer;font:700 10px ui-monospace,monospace}.change-lens>header button:hover{color:var(--ink);background:var(--gold)}.change-lens>p{max-width:67ch;margin:0;padding:19px 24px;color:#c6d2d7}.change-rows{display:grid;gap:1px;background:#466274}.change-rows article{display:grid;grid-template-columns:120px minmax(0,1fr) minmax(0,1fr) minmax(220px,.85fr);gap:1px;background:#466274}.change-rows article>*{min-width:0;margin:0;padding:17px;background:#173b4f}.change-rows article>span{color:var(--gold);font:700 10px/1.4 ui-monospace,monospace}.change-rows b{font:700 10px ui-monospace,monospace;letter-spacing:.07em;text-transform:uppercase}.change-rows div:first-of-type b{color:#e89a8e}.change-rows div:nth-of-type(2) b{color:#9bcba7}.change-rows aside{background:#1d455b}.change-rows aside b{color:var(--gold)}.change-rows p{margin:7px 0 0;color:#d4dfe3;font-size:12px;line-height:1.55}.preview header [data-lens]{color:var(--gold);border-color:var(--gold)}@keyframes lens-enter{from{opacity:.15;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:1000px){.change-rows article{grid-template-columns:100px 1fr 1fr}.change-rows aside{grid-column:2/-1}}@media(max-width:620px){.change-lens>header{display:block}.change-lens>header button{margin-top:14px}.change-rows article{grid-template-columns:1fr}.change-rows article>span{padding-bottom:6px}.change-rows aside{grid-column:auto}.change-lens>p{padding:17px}.change-lens>header{padding:18px}.change-rows p{font-size:13px}}@media(prefers-reduced-motion:reduce){.change-lens{animation:none}} + +/* A compact, scrollable comparison keeps long skill revisions reviewable. */ +.skill-diff{border:1px solid var(--ink);background:#102b3a;color:var(--paper);animation:lens-enter .28s ease both}.skill-diff>header{display:flex;justify-content:space-between;gap:20px;align-items:start;padding:22px 24px;border-bottom:1px solid #466274}.skill-diff span{color:var(--gold);font:700 10px ui-monospace,monospace;letter-spacing:.1em}.skill-diff h3{margin:7px 0 0;font-size:clamp(24px,3vw,40px);line-height:1.02;letter-spacing:-.05em}.skill-diff>header button{padding:9px 11px;border:1px solid #557080;color:var(--paper);background:transparent;cursor:pointer;font:700 10px ui-monospace,monospace}.skill-diff>header button:hover{color:var(--ink);background:var(--gold)}.skill-diff>p{margin:0;padding:17px 24px;color:#c6d2d7}.diff-lines{max-height:540px;overflow:auto;border-top:1px solid #466274;font:12px/1.55 ui-monospace,monospace}.diff-lines p{display:grid;grid-template-columns:42px minmax(0,1fr);gap:11px;margin:0;padding:4px 16px;white-space:pre-wrap;overflow-wrap:anywhere}.diff-lines span{color:#91aab7}.diff-lines .added{color:#d5f1d6;background:#1a4b42}.diff-lines .added span{color:#a9e3ae}.diff-lines .removed{color:#ffd7d0;background:#572f32}.diff-lines .removed span{color:#ffb5a8}.preview header [data-diff]{color:#c6d2d7;border-color:#557080}@media(max-width:620px){.skill-diff>header{display:block}.skill-diff>header button{margin-top:14px}.diff-lines p{grid-template-columns:30px minmax(0,1fr);padding:4px 12px}}@media(prefers-reduced-motion:reduce){.skill-diff{animation:none}} diff --git a/skills-review/index.html b/skills-review/index.html index c0eb0e9..99a2f4a 100644 --- a/skills-review/index.html +++ b/skills-review/index.html @@ -10,7 +10,7 @@
    -
    ← field guideSUBMITTED SKILLS / REVIEW DESK16 submissions
    +
    ← field guideSUBMITTED SKILLS / REVIEW DESKsubmissions

    A friendly path from draft to dependable

    Every skill deserves
    a clear job.

    diff --git a/skills-review/styles.css b/skills-review/styles.css index 5fe4b2f..0fa452e 100644 --- a/skills-review/styles.css +++ b/skills-review/styles.css @@ -1,3 +1,6 @@ :root{--ink:#122534;--paper:#f6f3ed;--line:#d0d5d2;--muted:#65717a;--blue:#215675;--gold:#ebbf58;--violet:#6b668f}*{box-sizing:border-box}body{margin:0;color:var(--ink);background:var(--paper);font:15px/1.6 Arial,sans-serif}main{max-width:1500px;margin:auto;padding:0 4vw}.topbar{display:flex;justify-content:space-between;gap:20px;padding:24px 0;border-bottom:1px solid var(--line);color:var(--muted);font:700 10px monospace;letter-spacing:.08em;text-transform:uppercase}.topbar a{color:var(--ink);text-decoration:none}.hero{max-width:1040px;padding:105px 0 75px}.eyebrow,.status,.purpose span,.review-grid span,.extras span,.preview header span{font:700 10px monospace;letter-spacing:.1em}.eyebrow{color:#a7483f}.hero h1,.method h2{margin:14px 0;font-size:clamp(48px,8vw,112px);line-height:.92;letter-spacing:-.07em}.hero em,.method em{color:#a7483f;font-family:Georgia,serif;font-weight:400}.hero p:last-child{max-width:620px;color:var(--muted);font-size:18px}.principles{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);background:var(--line);gap:1px}.principles article{display:grid;gap:10px;min-height:175px;padding:22px;background:var(--paper)}.principles b{color:#a7483f;font:22px monospace}.principles strong{font-size:18px}.principles span{color:var(--muted);font-size:13px}.method{display:grid;grid-template-columns:1fr 1fr;gap:70px;align-items:end;padding:120px 0 55px}.method h2{font-size:clamp(40px,5vw,70px)}.method ol{margin:0;padding-left:20px;color:var(--muted)}.method li+li{margin-top:9px}.catalog{display:grid;grid-template-columns:320px minmax(0,1fr);border:1px solid var(--line);background:var(--line);gap:1px}.catalog aside{padding:24px;background:#e9eeed}.catalog label{display:block;margin:32px 0 7px;font:700 10px monospace;letter-spacing:.08em;text-transform:uppercase}.catalog input{width:100%;padding:12px;border:1px solid #9ba7a5;background:var(--paper);font:inherit}.count{color:var(--muted);font:11px monospace}#skill-list{display:grid;gap:1px;border-top:1px solid var(--line)}#skill-list button{display:grid;grid-template-columns:1fr auto;gap:3px;padding:14px;border:0;border-bottom:1px solid var(--line);color:var(--ink);background:transparent;text-align:left;cursor:pointer}#skill-list button span{grid-column:1/-1;color:var(--muted);font:10px monospace}#skill-list button strong{font-size:13px}#skill-list button small{color:#a7483f;font:9px monospace;text-transform:uppercase}#skill-list button:hover,#skill-list button.active{color:var(--paper);background:var(--ink)}#skill-list button.active span,#skill-list button.active small{color:var(--gold)}.detail{min-width:0;padding:38px;background:var(--paper)}.detail>header{display:flex;justify-content:space-between;gap:25px;align-items:start}.status{color:#a7483f}.detail h2{margin:5px 0;font-size:clamp(30px,4vw,58px);letter-spacing:-.06em}.detail header p{margin:0;color:var(--muted)}.switch{display:flex;border:1px solid var(--ink)}button{font:inherit}.switch button,.preview button{padding:9px 11px;border:0;background:transparent;cursor:pointer;font:700 10px monospace}.switch button.active{color:var(--paper);background:var(--ink)}.purpose{display:grid;grid-template-columns:150px 1fr;gap:20px;margin:45px 0 20px;padding:20px;background:var(--gold)}.purpose p{margin:0;font-size:18px;line-height:1.4}.review-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line)}.review-grid section{padding:22px;background:var(--paper)}.review-grid span{color:#a7483f}.review-grid ul{margin:14px 0 0;padding-left:20px}.review-grid li+li{margin-top:9px}.extras{margin:1px 0 25px;padding:18px 22px;color:var(--paper);background:var(--blue)}.extras span{display:block;margin-bottom:8px;color:var(--gold)}.preview{border:1px solid var(--ink);background:var(--ink)}.preview header{display:flex;justify-content:space-between;gap:20px;padding:14px;color:var(--paper);border-bottom:1px solid #486175}.preview header span{color:var(--gold)}.preview button{color:var(--paper);border:1px solid #486175}.preview button:hover{background:#29455a}.preview pre{max-height:540px;margin:0;padding:24px;overflow:auto;color:#d6e1e4;background:#0c1a25}.preview code{font:12px/1.65 ui-monospace,monospace;white-space:pre-wrap}.research{margin:100px 0;padding:35px;color:var(--paper);background:var(--violet)}.research .eyebrow{color:var(--gold)}.research>p:not(.eyebrow){max-width:850px;font:20px/1.45 Georgia,serif}.research div{display:flex;flex-wrap:wrap;gap:12px}.research a{padding:8px 10px;color:var(--paper);border:1px solid #ffffff66;font:10px monospace;text-decoration:none}footer{padding:10px 0 50px;color:var(--muted);font-size:12px}footer code{color:var(--ink)}button:focus-visible,input:focus-visible,a:focus-visible{outline:3px solid #a7483f;outline-offset:2px}@media(max-width:850px){.principles{grid-template-columns:1fr 1fr}.catalog{grid-template-columns:1fr}.method{grid-template-columns:1fr;gap:25px;padding-top:80px}.detail{padding:24px}.review-grid{grid-template-columns:1fr}}@media(max-width:530px){main{padding:0 16px}.topbar span{display:none}.hero{padding:65px 0 45px}.principles{grid-template-columns:1fr}.detail>header,.preview header{display:block}.switch{margin-top:18px;width:max-content}.purpose{grid-template-columns:1fr}.preview header div{margin-top:12px}} /* Review surface overrides: keep suggestions readable and packages navigable. */ .extras{color:var(--ink);background:#e5eeeb;border-left:4px solid var(--blue)}.extras span{color:var(--blue)}.extras p{margin:0}.file-tabs{display:flex;gap:1px;overflow-x:auto;padding:10px 14px;background:#122534;border-bottom:1px solid #486175}.file-tabs button{display:grid;gap:1px;min-width:max-content;padding:7px 10px;color:#d6e1e4;background:transparent;border:1px solid #486175;cursor:pointer;text-align:left;font:11px ui-monospace,monospace}.file-tabs button span{color:#ebbf58;font-size:9px;text-transform:uppercase}.file-tabs button.active,.file-tabs button:hover{color:#122534;background:#ebbf58}.file-tabs button.active span,.file-tabs button:hover span{color:#122534} + +/* Rendered Markdown stays inside the same bounded reading surface as source. */ +.markdown-preview{max-height:540px;overflow:auto;padding:24px;color:#d6e1e4;background:#0c1a25}.markdown-preview>:first-child{margin-top:0}.markdown-preview h1,.markdown-preview h2,.markdown-preview h3,.markdown-preview h4,.markdown-preview h5,.markdown-preview h6{margin:1.5em 0 .5em;color:#fff;line-height:1.15}.markdown-preview h1{font-size:1.8em}.markdown-preview h2{font-size:1.45em}.markdown-preview h3{font-size:1.2em}.markdown-preview p,.markdown-preview li{max-width:78ch}.markdown-preview li+li{margin-top:.35em}.markdown-preview a{color:var(--gold)}.markdown-preview code{padding:.12em .3em;color:#fff;background:#29455a;white-space:break-spaces}.markdown-preview pre{max-height:none;margin:1em 0;padding:14px;border:1px solid #486175}.markdown-preview pre code{padding:0;background:transparent}.markdown-preview blockquote{margin:1em 0;padding:.3em 1em;border-left:3px solid var(--gold);color:#b9c8d0}.markdown-preview hr{border:0;border-top:1px solid #486175}.markdown-frontmatter{display:grid;grid-template-columns:max-content 1fr;gap:3px 14px;margin:0 0 24px;padding:12px;border:1px solid #486175;font:11px/1.5 ui-monospace,monospace}.markdown-frontmatter dt{color:var(--gold)}.markdown-frontmatter dd{margin:0}.markdown-table-wrap{max-width:100%;overflow:auto;margin:1em 0;border:1px solid #486175}.markdown-preview table{width:100%;min-width:460px;border-collapse:collapse;font-size:13px}.markdown-preview th,.markdown-preview td{padding:9px 11px;border:1px solid #486175;text-align:left;vertical-align:top}.markdown-preview th{color:var(--gold);background:#173046}