Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions artifacts/four-cuts-1.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@

<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<script src="/assets/js/site.js" defer></script>
<style>
:root{
--ink:#14171c; --ink-2:#1b1f26; --ink-3:#20242c;
--brass:#b8935f; --brass-dim:#8a7150;
--rust:#c1502e; --rust-dim:#8f3c22;
--slate:#5b6472; --text:#e8e6de; --text-dim:#93979f; --hair:#2a2f38;
--rust:#f08a63; --rust-dim:#8f3c22;
--slate:#aeb4c0; --text:#e8e6de; --text-dim:#93979f; --hair:#2a2f38;
}
*{box-sizing:border-box; margin:0; padding:0;}
html,body{ background:var(--ink); color:var(--text); font-family:'Inter',sans-serif; -webkit-font-smoothing:antialiased; }
Expand Down Expand Up @@ -107,6 +108,10 @@
.section-label::after{ content:''; flex:1; height:1px; background:var(--hair); }

footer{ font-family:'JetBrains Mono',monospace; font-size:10px; color:var(--slate); line-height:1.7; padding-top:16px; border-top:1px solid var(--hair); }
.copy-field{ position:relative; }
.copy-button{ position:absolute; top:10px; right:10px; z-index:3; min-height:28px; padding:6px 8px; border:1px solid var(--hair); border-radius:5px; background:var(--ink); color:var(--text-dim); font:600 10px/1 'JetBrains Mono',monospace; cursor:pointer; }
.copy-button:hover{ border-color:var(--brass); color:var(--text); }
.copy-status{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; }
@media (max-width:380px){ h1{ font-size:21px; } }
</style>
</head>
Expand All @@ -117,7 +122,7 @@
<h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
<p class="sub">You asked to see wealth and tax burden per bracket, total and per person, as stacks of <b>$</b>. Two of these are cleanly measurable by tax bracket; two are not. Rather than fake the alignment, each measure sits on the finest axis its real data supports — and the seam between them is marked, not hidden. Tap <b>⤢</b> on any field to focus it full-screen.</p>

<div class="bracket-ref">
<div class="bracket-ref" data-copy-field>
<div class="ref-title">2026 federal brackets — single filer · matched income group</div>
<div class="bracket-row"><span class="rate">10%</span><span class="range">$0 – $12,400</span><span class="grp">inc <20%</span></div>
<div class="bracket-row"><span class="rate">12%</span><span class="range">$12,401 – $50,400</span><span class="grp">20–40%</span></div>
Expand All @@ -131,7 +136,7 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
<!-- ===== TAX SIDE (7 brackets, real IRS) ===== -->
<div class="section-label">tax — by marginal bracket · real</div>

<div class="measure">
<div class="measure" data-copy-field>
<div class="m-head">
<div class="m-title tax">1 · Total tax generated per bracket</div>
<div class="m-unit">each $ = $1 billion of federal income tax</div>
Expand All @@ -149,7 +154,7 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
</div>
</div>

<div class="measure">
<div class="measure" data-copy-field>
<div class="m-head">
<div class="m-title tax">2 · Tax per person, per bracket</div>
<div class="m-unit">each $ = $1,000 of tax generated at that rate, per return reaching it</div>
Expand All @@ -170,7 +175,7 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
<!-- ===== WEALTH SIDE (6 income sextiles, real SCF) ===== -->
<div class="section-label">wealth — by income group · real</div>

<div class="measure">
<div class="measure" data-copy-field>
<div class="m-head">
<div class="m-title wealth">3 · Total net dollars held per income group</div>
<div class="m-unit">each $ = $1 billion of household net dollars (assets − debts)</div>
Expand All @@ -188,7 +193,7 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
</div>
</div>

<div class="measure">
<div class="measure" data-copy-field>
<div class="m-head">
<div class="m-title wealth">4 · Net dollars held per person</div>
<div class="m-unit">each $ = $1,000 a person <em>holds</em> (assets − debts) — not what they're worth</div>
Expand All @@ -206,7 +211,7 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
</div>
</div>

<div class="hmmm">
<div class="hmmm" data-copy-field>
<span class="tag">hmmm</span>
<p>The two axes are <b>not</b> the same cut. Tax data (1, 2) is real, ranked by <b>marginal rate</b> — IRS reports exactly how much each rate generates and who reaches it. Wealth data (3, 4) is real, ranked by <b>income group</b> — the SCF reports net worth by income sextile. The bracket table up top pairs them by income range, but a taxpayer's <b>taxable</b> income (after deductions) and a family's <b>usual</b> income aren't the same number, so the pairing is a guide, not an identity.</p>
<p><span class="flag">Why wealth isn't drawn per bracket at all:</span> the wealth-by-tax-bracket cross-tab isn't published, and any estimate would collapse at the top — the SCF's top income decile holds mean net dollars of $6.63M, which hides the top 1% holding near $44M. Forcing wealth onto the 7 brackets would <b>understate the tail so badly it would lie.</b> The income-sextile cut is the finest honest one, and even it buries that top-1% tail inside the 90–100 slice.</p>
Expand All @@ -216,7 +221,7 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
<!-- X-ready section: makes sharing / threading on X frictionless -->
<div class="section-label">ready for X · copy-paste numbers & thread starter</div>

<div class="x-ready" style="border:1px solid var(--hair); border-radius:8px; padding:16px 16px 18px; background:var(--ink-2); margin-bottom:28px; font-size:13px; line-height:1.55;">
<div class="x-ready" data-copy-field style="border:1px solid var(--hair); border-radius:8px; padding:16px 16px 18px; background:var(--ink-2); margin-bottom:28px; font-size:13px; line-height:1.55;">
<p style="color:var(--text-dim); margin-bottom:14px;">The interactive fields above (especially when expanded ⤢) make strong screenshots for threads. Below are the clean numbers + a suggested starter post you can copy straight into X. Feel free to quote any pie or field directly.</p>

<div style="background:#0f1218; border-radius:6px; padding:12px 14px; margin-bottom:14px; font-family:'JetBrains Mono',monospace; font-size:11.5px; color:#c8c6be; line-height:1.65;">
Expand Down Expand Up @@ -504,4 +509,4 @@ <h1>Wealth and tax, <em>each cut its own honest way</em>.</h1>
})();
</script>
</body>
</html>
</html>
2 changes: 1 addition & 1 deletion fallback/fallback.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fallback/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>The Interdependent Way — emergency static edition</title><meta name="description" content="Dependency-free emergency entrance to The Interdependent Way."><link rel="stylesheet" href="fallback.css"></head><body><header><p class="flag">Emergency static edition</p><h1>The Interdependent Way</h1><p>This dependency-free page exists for explicit recovery deployment. The last successful full site should remain live when ordinary builds fail.</p></header><main><section><h2>Begin</h2><p>The Interdependent Way is a dense living text and a constellation of projects attempting to explain, test, implement, challenge, and preserve it.</p></section><section><h2>Primary links</h2><ul><li><a href="/">Return to the full site</a></li><li><a href="https://github.com/The-Interdependency/The-Interdependency.github.io/blob/main/canon/the_interdependent_way.md">Read the repository canon mirror</a></li><li><a href="https://github.com/The-Interdependency">Open The Interdependency organization</a></li><li><a href="https://github.com/The-Interdependency/ucns">UCNS</a></li><li><a href="https://github.com/The-Interdependency/skill-lib">skill-lib</a></li><li><a href="https://github.com/The-Interdependency/a0">a0</a></li></ul></section><section class="hmmm"><h2>hmmm</h2><p>This edition contains no dynamic project snapshot, search index, Article Lab, or external dependencies. It is a recovery floor, not the preferred public experience.</p></section></main><footer><p>Apache-2.0 unless an individual artifact states otherwise. Contact: curious@interdependentway.org</p></footer></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>The Interdependent Way — emergency static edition</title><meta name="description" content="Static emergency entrance to The Interdependent Way."><link rel="stylesheet" href="fallback.css"><script src="../assets/js/site.js" defer></script></head><body><header data-copy-field><p class="flag">Emergency static edition</p><h1>The Interdependent Way</h1><p>This static page exists for explicit recovery deployment. It remains readable without JavaScript; copy actions enhance it where supported. The last successful full site should remain live when ordinary builds fail.</p></header><main><section data-copy-field><h2>Begin</h2><p>The Interdependent Way is a dense living text and a constellation of projects attempting to explain, test, implement, challenge, and preserve it.</p></section><section data-copy-field><h2>Primary links</h2><ul><li><a href="/">Return to the full site</a></li><li><a href="https://github.com/The-Interdependency/The-Interdependency.github.io/blob/main/canon/the_interdependent_way.md">Read the repository canon mirror</a></li><li><a href="https://github.com/The-Interdependency">Open The Interdependency organization</a></li><li><a href="https://github.com/The-Interdependency/ucns">UCNS</a></li><li><a href="https://github.com/The-Interdependency/skill-lib">skill-lib</a></li><li><a href="https://github.com/The-Interdependency/a0">a0</a></li></ul></section><section class="hmmm" data-copy-field><h2>hmmm</h2><p>This edition contains no dynamic project snapshot, search index, Article Lab, or external dependencies. It is a recovery floor, not the preferred public experience.</p></section></main><footer data-copy-field><p>Apache-2.0 unless an individual artifact states otherwise. Contact: curious@interdependentway.org</p></footer></body></html>
1 change: 1 addition & 0 deletions src/_includes/layouts/splash.njk
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<link rel="alternate" type="text/markdown" href="/eai/aicontext.md" title="AI context">
<link rel="stylesheet" href="/assets/css/site.css">
<link rel="stylesheet" href="/assets/css/awakening.css">
<script src="/assets/js/site.js" defer></script>
</head>
<body class="awakening-body">
<a class="skip-link" href="#content">Skip to Awakening</a>
Expand Down
12 changes: 11 additions & 1 deletion src/assets/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ h3 { font-size: clamp(1.2rem, 2vw, 1.55rem); }
.actions { display: flex; gap: .75rem; flex-wrap: wrap; margin-top: 1.7rem; }
.button { display: inline-flex; align-items: center; justify-content: center; min-height: 2.8rem; padding: .7rem 1rem; border: 1px solid var(--violet); border-radius: .75rem; color: var(--starlight); background: rgba(155, 135, 245, .1); text-decoration: none; font-weight: 700; }
.button.secondary { border-color: var(--line); background: transparent; color: var(--silver); }
.copy-field { position: relative; }
.copy-button { float: right; min-height: 2.2rem; margin: 0 0 .75rem .75rem; padding: .4rem .65rem; border: 1px solid var(--line); border-radius: .55rem; color: var(--silver); background: var(--night); font: 700 .73rem/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; cursor: pointer; }
.copy-button:hover { color: var(--starlight); border-color: var(--violet); background: var(--night-soft); }
.copy-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.copy-field-link { position: relative; min-width: 0; }
.copy-field-link > .card { height: 100%; }
.copy-field-link > .copy-button { position: absolute; z-index: 2; top: .75rem; right: .75rem; float: none; margin: 0; }
details.copy-field { padding-top: 1.2rem; }
details.copy-field > .copy-button { position: absolute; top: .65rem; right: .65rem; float: none; margin: 0; }
details.copy-field > summary { padding-right: 4.75rem; }
.section-intro { max-width: 65ch; color: var(--silver); }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); gap: 1rem; margin: 1.4rem 0 3rem; }
.card { display: block; position: relative; padding: 1.25rem; color: inherit; text-decoration: none; background: linear-gradient(145deg, rgba(22, 33, 58, .9), rgba(16, 24, 42, .86)); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 0 0 rgba(0,0,0,0); transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; }
Expand Down Expand Up @@ -174,4 +184,4 @@ summary { cursor: pointer; font-weight: 800; }
.hmmm-boundary { grid-column: auto; grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; } }
@media print { body { background: white; color: black; } body::before, .site-header, .actions, .noscript { display: none; } .site-main { max-width: none; padding: 0; } .card, .panel, .provenance, .hmmm, details, .textbook-chapter { background: white; color: black; box-shadow: none; } a { color: black; } }
@media print { body { background: white; color: black; } body::before, .site-header, .actions, .noscript, .copy-button { display: none; } .site-main { max-width: none; padding: 0; } .card, .panel, .provenance, .hmmm, details, .textbook-chapter { background: white; color: black; box-shadow: none; } a { color: black; } }
103 changes: 102 additions & 1 deletion src/assets/js/site.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// === MODULE_BUILD ===
// id: optional_site_enhancement
// purpose: Add a compact mobile navigation toggle without hiding static content.
// purpose: Add compact mobile navigation and one-copy-action-per-text-field without hiding static content.
// entrypoint: loaded with defer from the base layout
// tests: tests/site-contract.test.mjs
// === END MODULE_BUILD ===
Expand All @@ -17,3 +17,104 @@ if (button && nav) {
nav.dataset.open = String(!open);
});
}

// A public field is a deliberate, self-contained reading unit: not a heading,
// navigation link, or decorative label. Keep this list aligned with the site
// component vocabulary so new content using an established field class gains a
// copy action automatically. `data-copy-field` is the escape hatch for a new
// field type that has not yet earned a shared class.
const COPYABLE_FIELD_SELECTOR = [
'[data-copy-field]',
'#content .hero',
'#content .page-head',
'#content .panel',
'#content .provenance',
'#content .hmmm',
'#content .reading',
'#content .textbook-chapter',
'#content .source-block',
'#content .criterion',
'#content .journal-entry',
'#content details',
'#content .card',
'#content .awakening-copy',
'#content .awakening-text',
'#content .awakening-provenance',
'.site-footer .footer-provenance',
'.site-footer .hmmm-boundary'
].join(', ');

function textForCopy(field) {
const copy = field.cloneNode(true);
copy.querySelectorAll('.copy-button, .copy-status, script, style, template, [data-copy-ignore]').forEach(node => node.remove());
return copy.innerText.replace(/\n{3,}/g, '\n\n').trim();
}

function fieldLabel(field) {
const heading = field.querySelector('h1, h2, h3, summary, strong');
const label = heading?.textContent?.replace(/\s+/g, ' ').trim();
return label ? `Copy ${label}` : 'Copy field text';
}

async function writeText(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}

const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.cssText = 'position:fixed;opacity:0;pointer-events:none';
document.body.append(textarea);
textarea.select();
const copied = document.execCommand('copy');
textarea.remove();
if (!copied) throw new Error('Clipboard copy is unavailable.');
}

function addCopyControl(field) {
if (field.dataset.copyReady === 'true' || !textForCopy(field)) return;

const control = document.createElement('button');
control.type = 'button';
control.className = 'copy-button';
control.textContent = 'Copy';
control.setAttribute('aria-label', fieldLabel(field));

const status = document.createElement('span');
status.className = 'copy-status';
status.setAttribute('role', 'status');
status.setAttribute('aria-live', 'polite');

let controlField = field;
if (field.matches('a')) {
// An anchor cannot contain an interactive descendant. Its copy action is
// therefore a sibling in a small wrapper, while the copied text remains
// exactly the card/link field.
const wrapper = document.createElement('div');
wrapper.className = 'copy-field copy-field-link';
field.replaceWith(wrapper);
wrapper.append(control, status, field);
controlField = wrapper;
} else if (field.matches('details')) {
field.append(control, status);
} else {
field.prepend(control, status);
}

controlField.classList.add('copy-field');
field.dataset.copyReady = 'true';
control.addEventListener('click', async () => {
try {
await writeText(textForCopy(field));
control.textContent = 'Copied';
status.textContent = 'Copied to clipboard.';
window.setTimeout(() => { control.textContent = 'Copy'; }, 1600);
} catch {
status.textContent = 'Copy failed. Select the text and copy it manually.';
}
});
}

document.querySelectorAll(COPYABLE_FIELD_SELECTOR).forEach(addCopyControl);
2 changes: 1 addition & 1 deletion tests/accessibility.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if (!firstLabUnit) throw new Error(`missing Lab canon unit ${articleLab[0].unit_
for (const route of [
'/', '/home/', '/preamble/', '/chapters/', '/chapters/chapter-zero/', '/chapters/chapter-seven/',
'/articles/', '/articles/article-two/', '/narratives/', '/narratives/jack-and-diane/', '/way/', '/lab/', `/lab/${firstLabUnit.routeSlug}/`, '/projects/',
'/artifacts/edcm-mathematics/', '/research/method/'
'/artifacts/edcm-mathematics/', '/artifacts/four-cuts/', '/fallback/', '/research/method/'
]) {
test(`${route} has no serious or critical automated accessibility violations`, async ({ page }) => {
await page.goto(route);
Expand Down
Loading
Loading