Knowledge Lifecycle
Browse files- index.html +27 -16
- main.js +50 -31
- style.css +66 -0
index.html
CHANGED
|
@@ -29,8 +29,8 @@
|
|
| 29 |
<header>
|
| 30 |
<h1>LLM Knowledge Lifecycle</h1>
|
| 31 |
<p class="subtitle">
|
| 32 |
-
A
|
| 33 |
-
|
| 34 |
<em>The Knowledge Lifecycle of Large Language Models</em>.
|
| 35 |
</p>
|
| 36 |
<p class="badges">
|
|
@@ -41,8 +41,8 @@
|
|
| 41 |
</p>
|
| 42 |
<div class="alert a-important">
|
| 43 |
<span class="alert-title">Important</span>
|
| 44 |
-
The model runs
|
| 45 |
-
|
| 46 |
</div>
|
| 47 |
</header>
|
| 48 |
|
|
@@ -62,10 +62,8 @@
|
|
| 62 |
<section id="probe" class="panel active">
|
| 63 |
|
| 64 |
<p class="lede">
|
| 65 |
-
Each preset is a
|
| 66 |
-
|
| 67 |
-
prompt holds the new one. Pick a case, press Measure, and watch which memory controls the
|
| 68 |
-
answer.
|
| 69 |
</p>
|
| 70 |
|
| 71 |
<div class="presets">
|
|
@@ -102,7 +100,7 @@
|
|
| 102 |
<p id="load-status" class="dim"></p>
|
| 103 |
</div>
|
| 104 |
|
| 105 |
-
<div id="results" hidden>
|
| 106 |
|
| 107 |
<div class="verdict-row">
|
| 108 |
<span id="verdict-chip" class="chip"></span>
|
|
@@ -112,13 +110,13 @@
|
|
| 112 |
<p id="narrative" class="narrative"></p>
|
| 113 |
|
| 114 |
<h3>The two distributions, side by side</h3>
|
| 115 |
-
<p class="dim small">
|
| 116 |
-
The correct answer is outlined in green
|
| 117 |
<div id="bars"></div>
|
| 118 |
|
| 119 |
<div class="gauge-block">
|
| 120 |
<div class="gauge-label">
|
| 121 |
-
<span>D<sub>sync</sub> <span class="dim">how lost the correct answer is</span></span>
|
| 122 |
<strong id="r-dsync"></strong>
|
| 123 |
</div>
|
| 124 |
<div class="gauge">
|
|
@@ -130,7 +128,7 @@
|
|
| 130 |
|
| 131 |
<div class="gauge-block">
|
| 132 |
<div class="gauge-label">
|
| 133 |
-
<span>I<sub>ctx</sub> <span class="dim">how much the document moved the model</span></span>
|
| 134 |
<strong id="r-ictx"></strong>
|
| 135 |
</div>
|
| 136 |
<div class="gauge slim">
|
|
@@ -142,7 +140,7 @@
|
|
| 142 |
|
| 143 |
<div class="temp-block">
|
| 144 |
<div class="gauge-label">
|
| 145 |
-
<span>Sampling temperature <span class="dim">(
|
| 146 |
<strong id="t-value">1.0</strong>
|
| 147 |
</div>
|
| 148 |
<input id="temp" type="range" min="0.2" max="2.0" step="0.1" value="1.0">
|
|
@@ -155,8 +153,8 @@
|
|
| 155 |
<tr><td>P(answer | query alone)</td><td id="r-p0"></td></tr>
|
| 156 |
<tr><td>P(answer | context + query)</td><td id="r-p1"></td></tr>
|
| 157 |
<tr><td>Answer tokenization</td><td id="r-tok"></td></tr>
|
| 158 |
-
<tr><td>D<sub>sync</sub> definition</td><td>−ln P(answer
|
| 159 |
-
<tr><td>I<sub>ctx</sub> definition</td><td>
|
| 160 |
</table>
|
| 161 |
</details>
|
| 162 |
</div>
|
|
@@ -194,6 +192,19 @@
|
|
| 194 |
<tr><td><span class="chip chip-influence">drift</span></td><td><strong>Twitter rename.</strong> Context lifts the right answer by orders of magnitude and still loses to " Twitter".</td></tr>
|
| 195 |
</table>
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
<h2>Build your own probe</h2>
|
| 198 |
<p>
|
| 199 |
Any fact change with a single-word answer works. Three rules make a clean probe: the query
|
|
|
|
| 29 |
<header>
|
| 30 |
<h1>LLM Knowledge Lifecycle</h1>
|
| 31 |
<p class="subtitle">
|
| 32 |
+
A model holds two memories: what it learned in training, and what you put in its prompt.
|
| 33 |
+
When they disagree, which one wins? Companion demonstration for
|
| 34 |
<em>The Knowledge Lifecycle of Large Language Models</em>.
|
| 35 |
</p>
|
| 36 |
<p class="badges">
|
|
|
|
| 41 |
</p>
|
| 42 |
<div class="alert a-important">
|
| 43 |
<span class="alert-title">Important</span>
|
| 44 |
+
The model runs in your browser. Nothing leaves your machine, and identical inputs always
|
| 45 |
+
give identical numbers. The first measurement downloads the model once, 128 MB.
|
| 46 |
</div>
|
| 47 |
</header>
|
| 48 |
|
|
|
|
| 62 |
<section id="probe" class="panel active">
|
| 63 |
|
| 64 |
<p class="lede">
|
| 65 |
+
Each preset is a documented fact change from after this model was trained. Its weights hold
|
| 66 |
+
the old world, the document in the prompt holds the new one. Press Measure to see which wins.
|
|
|
|
|
|
|
| 67 |
</p>
|
| 68 |
|
| 69 |
<div class="presets">
|
|
|
|
| 100 |
<p id="load-status" class="dim"></p>
|
| 101 |
</div>
|
| 102 |
|
| 103 |
+
<div id="results" role="region" aria-live="polite" aria-label="Measurement results" hidden>
|
| 104 |
|
| 105 |
<div class="verdict-row">
|
| 106 |
<span id="verdict-chip" class="chip"></span>
|
|
|
|
| 110 |
<p id="narrative" class="narrative"></p>
|
| 111 |
|
| 112 |
<h3>The two distributions, side by side</h3>
|
| 113 |
+
<p class="dim small">Probability of each candidate without the document (grey) and with it (blue).
|
| 114 |
+
The correct answer is outlined in green. If its bar is invisible, that is the finding.</p>
|
| 115 |
<div id="bars"></div>
|
| 116 |
|
| 117 |
<div class="gauge-block">
|
| 118 |
<div class="gauge-label">
|
| 119 |
+
<span>D<sub>sync</sub> <span class="dim">how lost the correct answer is, in nats</span></span>
|
| 120 |
<strong id="r-dsync"></strong>
|
| 121 |
</div>
|
| 122 |
<div class="gauge">
|
|
|
|
| 128 |
|
| 129 |
<div class="gauge-block">
|
| 130 |
<div class="gauge-label">
|
| 131 |
+
<span>I<sub>ctx</sub> <span class="dim">how much the document moved the model, in nats</span></span>
|
| 132 |
<strong id="r-ictx"></strong>
|
| 133 |
</div>
|
| 134 |
<div class="gauge slim">
|
|
|
|
| 140 |
|
| 141 |
<div class="temp-block">
|
| 142 |
<div class="gauge-label">
|
| 143 |
+
<span>Sampling temperature <span class="dim">(how randomly the model picks its answer)</span></span>
|
| 144 |
<strong id="t-value">1.0</strong>
|
| 145 |
</div>
|
| 146 |
<input id="temp" type="range" min="0.2" max="2.0" step="0.1" value="1.0">
|
|
|
|
| 153 |
<tr><td>P(answer | query alone)</td><td id="r-p0"></td></tr>
|
| 154 |
<tr><td>P(answer | context + query)</td><td id="r-p1"></td></tr>
|
| 155 |
<tr><td>Answer tokenization</td><td id="r-tok"></td></tr>
|
| 156 |
+
<tr><td>D<sub>sync</sub> definition</td><td>−ln P(answer given context and query): the negative natural logarithm of the correct answer's probability</td></tr>
|
| 157 |
+
<tr><td>I<sub>ctx</sub> definition</td><td>Kullback-Leibler divergence between the with-document and without-document distributions, over all 50,257 tokens</td></tr>
|
| 158 |
</table>
|
| 159 |
</details>
|
| 160 |
</div>
|
|
|
|
| 192 |
<tr><td><span class="chip chip-influence">drift</span></td><td><strong>Twitter rename.</strong> Context lifts the right answer by orders of magnitude and still loses to " Twitter".</td></tr>
|
| 193 |
</table>
|
| 194 |
|
| 195 |
+
|
| 196 |
+
<h2>Terms used on this page</h2>
|
| 197 |
+
<table class="glossary">
|
| 198 |
+
<tr><td><strong>Token</strong></td><td>The unit a model reads and writes. Roughly a word or word fragment; " withdrawn" is one token, and the leading space is part of it.</td></tr>
|
| 199 |
+
<tr><td><strong>Parametric memory</strong></td><td>What the model absorbed into its weights during training. Fixed after training, and the model cannot tell you when it learned any of it.</td></tr>
|
| 200 |
+
<tr><td><strong>Context</strong></td><td>What you put in the prompt right now, including any document retrieved for the model to read.</td></tr>
|
| 201 |
+
<tr><td><strong>Nat</strong></td><td>A unit of information, measured with natural logarithms. Here it converts directly to probability: a value of <em>n</em> nats means the correct answer holds probability e<sup>−n</sup>. One nat is roughly 37%, nine nats is roughly one in ten thousand.</td></tr>
|
| 202 |
+
<tr><td><strong>Surprisal</strong></td><td>How surprised the model is by an answer, written as −ln P. Low when the model expected it, high when it did not. D<sub>sync</sub> is the surprisal of the correct answer.</td></tr>
|
| 203 |
+
<tr><td><strong>KL divergence</strong></td><td>A measure of how far one probability distribution sits from another. I<sub>ctx</sub> uses it to ask whether the document changed the model's mind about anything at all.</td></tr>
|
| 204 |
+
<tr><td><strong>Temperature</strong></td><td>How randomly a model picks among candidate answers. Low values make it repeat its favorite; high values spread the choice out.</td></tr>
|
| 205 |
+
<tr><td><strong>Quantized</strong></td><td>Weights stored at reduced precision so the model downloads and runs faster. This page uses 8-bit weights, which shift individual probabilities slightly without changing any conclusion.</td></tr>
|
| 206 |
+
</table>
|
| 207 |
+
|
| 208 |
<h2>Build your own probe</h2>
|
| 209 |
<p>
|
| 210 |
Any fact change with a single-word answer works. Three rules make a clean probe: the query
|
main.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
| 1 |
// =============================================================================
|
| 2 |
// File : main.js
|
| 3 |
// Project : The Knowledge Lifecycle of Large Language Models
|
| 4 |
-
// Purpose : Browser-side
|
| 5 |
-
// conditions, and renders the narrative,
|
| 6 |
-
//
|
|
|
|
|
|
|
| 7 |
// Authors : Amey Thakur (https://github.com/Amey-Thakur)
|
| 8 |
// Sarvesh Talele (https://github.com/sarveshtalele)
|
| 9 |
// Repository : https://github.com/Amey-Thakur/LLM-KNOWLEDGE-LIFECYCLE
|
|
@@ -23,19 +25,19 @@ const PRESETS = {
|
|
| 23 |
query: "Question: Is Vioxx safe to prescribe? Answer: Vioxx is considered",
|
| 24 |
context: "Context: In September 2004, Merck voluntarily withdrew Vioxx after trials revealed increased cardiovascular risks.",
|
| 25 |
answer: " withdrawn",
|
| 26 |
-
reading: "The
|
| 27 |
},
|
| 28 |
monarch: {
|
| 29 |
query: "Question: Who is the current British monarch? Answer: The current British monarch is",
|
| 30 |
context: "Context: Queen Elizabeth II died in September 2022. Charles III acceded to the throne and is the reigning King of the United Kingdom.",
|
| 31 |
answer: " Charles",
|
| 32 |
-
reading: "A subtler failure. The
|
| 33 |
},
|
| 34 |
twitter: {
|
| 35 |
query: "Question: What is the social network Twitter called today? Answer: Twitter is now called",
|
| 36 |
context: "Context: In July 2023, Twitter was rebranded as X under Elon Musk's ownership.",
|
| 37 |
answer: " X",
|
| 38 |
-
reading: "
|
| 39 |
},
|
| 40 |
};
|
| 41 |
|
|
@@ -115,18 +117,24 @@ function softmax(row, temperature = 1.0) {
|
|
| 115 |
return probs;
|
| 116 |
}
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
function topK(probs, k) {
|
| 119 |
-
const
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
idx.push(best);
|
| 128 |
}
|
| 129 |
-
return
|
| 130 |
}
|
| 131 |
|
| 132 |
// ---------- rendering ----------
|
|
@@ -158,24 +166,35 @@ function renderBars(pPlain, pCtx, target) {
|
|
| 158 |
+ rows.join("");
|
| 159 |
}
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
function narrative(m) {
|
| 162 |
-
const
|
| 163 |
-
const topCtxTok = fmtTok(m.topCtx);
|
| 164 |
-
const ansTok = fmtTok(m.answerPiece).trim();
|
| 165 |
-
s.push(`With the corrective document in its prompt, the model's most likely continuation is β${topCtxTok}β at ${fmtPct(m.pTopCtx)}.`);
|
| 166 |
const odds = Math.round(1 / m.pt1);
|
| 167 |
-
s
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
s.push(`Counterintuitively, the document made the leading wrong answer stronger, raising it from ${fmtPct(m.pTopPlainSameTok)} to ${fmtPct(m.pTopCtx)}. Mentioning a fact, even to correct it, reinforces the association.`);
|
| 175 |
}
|
| 176 |
-
if (m.
|
| 177 |
-
s.push(
|
| 178 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
return s.join(" ");
|
| 180 |
}
|
| 181 |
|
|
@@ -286,7 +305,7 @@ $("run").addEventListener("click", async () => {
|
|
| 286 |
|
| 287 |
const m = {
|
| 288 |
pt0, pt1, dsync, ictx,
|
| 289 |
-
answerPiece: pieces[0],
|
| 290 |
topPlain: fmtTok(tokenizer.decode([topPlainId])),
|
| 291 |
topCtx: fmtTok(tokenizer.decode([topCtxId])),
|
| 292 |
pTopCtx: pCtx[topCtxId],
|
|
|
|
| 1 |
// =============================================================================
|
| 2 |
// File : main.js
|
| 3 |
// Project : The Knowledge Lifecycle of Large Language Models
|
| 4 |
+
// Purpose : Browser-side measurement engine. Loads GPT-2, runs the two
|
| 5 |
+
// prompt conditions, and renders the narrative, the paired
|
| 6 |
+
// distributions, the gauges, and the stage diagnosis.
|
| 7 |
+
// Tech Stack : JavaScript (ES modules), transformers.js 3.4.0,
|
| 8 |
+
// ONNX Runtime Web (WASM)
|
| 9 |
// Authors : Amey Thakur (https://github.com/Amey-Thakur)
|
| 10 |
// Sarvesh Talele (https://github.com/sarveshtalele)
|
| 11 |
// Repository : https://github.com/Amey-Thakur/LLM-KNOWLEDGE-LIFECYCLE
|
|
|
|
| 25 |
query: "Question: Is Vioxx safe to prescribe? Answer: Vioxx is considered",
|
| 26 |
context: "Context: In September 2004, Merck voluntarily withdrew Vioxx after trials revealed increased cardiovascular risks.",
|
| 27 |
answer: " withdrawn",
|
| 28 |
+
reading: "The headline case. The withdrawal notice is in the prompt and the model still answers βsafeβ. At full precision the paper measures 12.05 nats, far past the 9.2 failure threshold.",
|
| 29 |
},
|
| 30 |
monarch: {
|
| 31 |
query: "Question: Who is the current British monarch? Answer: The current British monarch is",
|
| 32 |
context: "Context: Queen Elizabeth II died in September 2022. Charles III acceded to the throne and is the reigning King of the United Kingdom.",
|
| 33 |
answer: " Charles",
|
| 34 |
+
reading: "A subtler failure. The document does raise the correct answer, but its strongest effect is boosting β Queenβ. Naming a fact, even to correct it, reinforces the old association.",
|
| 35 |
},
|
| 36 |
twitter: {
|
| 37 |
query: "Question: What is the social network Twitter called today? Answer: Twitter is now called",
|
| 38 |
context: "Context: In July 2023, Twitter was rebranded as X under Elon Musk's ownership.",
|
| 39 |
answer: " X",
|
| 40 |
+
reading: "Here the document moves the model hard and lifts the correct answer by orders of magnitude. It still answers βTwitterβ. Influence without resolution.",
|
| 41 |
},
|
| 42 |
};
|
| 43 |
|
|
|
|
| 117 |
return probs;
|
| 118 |
}
|
| 119 |
|
| 120 |
+
/**
|
| 121 |
+
* Indices of the k largest probabilities, highest first.
|
| 122 |
+
*
|
| 123 |
+
* One pass over the vocabulary, maintaining a short sorted list. The obvious
|
| 124 |
+
* alternative, k full scans with a "already taken" set, costs k passes over
|
| 125 |
+
* 50,257 entries at every call site; this costs one.
|
| 126 |
+
*/
|
| 127 |
function topK(probs, k) {
|
| 128 |
+
const best = []; // indices, kept sorted by descending probability
|
| 129 |
+
for (let i = 0; i < probs.length; i++) {
|
| 130 |
+
const p = probs[i];
|
| 131 |
+
if (best.length === k && p <= probs[best[k - 1]]) continue;
|
| 132 |
+
let at = best.length;
|
| 133 |
+
while (at > 0 && probs[best[at - 1]] < p) at--;
|
| 134 |
+
best.splice(at, 0, i);
|
| 135 |
+
if (best.length > k) best.pop();
|
|
|
|
| 136 |
}
|
| 137 |
+
return best;
|
| 138 |
}
|
| 139 |
|
| 140 |
// ---------- rendering ----------
|
|
|
|
| 166 |
+ rows.join("");
|
| 167 |
}
|
| 168 |
|
| 169 |
+
/**
|
| 170 |
+
* Plain-language account of one measurement, written from the numbers.
|
| 171 |
+
*
|
| 172 |
+
* The observations are additive rather than exclusive: a single run can be
|
| 173 |
+
* both "the document helped" and "the document also strengthened the wrong
|
| 174 |
+
* answer", and an if/else chain would report only the first and hide the
|
| 175 |
+
* second, which is the more interesting half.
|
| 176 |
+
*/
|
| 177 |
function narrative(m) {
|
| 178 |
+
const answerWon = m.topCtx === m.answerPiece;
|
|
|
|
|
|
|
|
|
|
| 179 |
const odds = Math.round(1 / m.pt1);
|
| 180 |
+
const s = [
|
| 181 |
+
`With the corrective document in its prompt, the model's most likely continuation is β${m.topCtx}β at ${fmtPct(m.pTopCtx)}.`,
|
| 182 |
+
`The correct answer β${m.answerPiece.trim()}β receives ${fmtPct(m.pt1)}: about one chance in ${odds.toLocaleString()}.`,
|
| 183 |
+
];
|
| 184 |
+
|
| 185 |
+
if (m.ictx < 0.05) {
|
| 186 |
+
s.push(`The document barely registered: it moved the model's whole distribution by ${m.ictx.toFixed(3)} nats.`);
|
|
|
|
| 187 |
}
|
| 188 |
+
if (m.pt1 > m.pt0 * 1.5) {
|
| 189 |
+
s.push(`It did help the correct answer, multiplying its probability by ${(m.pt1 / m.pt0).toFixed(1)}.`);
|
| 190 |
}
|
| 191 |
+
if (!answerWon && m.pTopCtx > m.pTopPlainSameTok) {
|
| 192 |
+
s.push(`It also strengthened the leading wrong answer, raising β${m.topCtx}β from ${fmtPct(m.pTopPlainSameTok)} to ${fmtPct(m.pTopCtx)}: mentioning a fact, even to correct it, reinforces the association.`);
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
s.push(answerWon
|
| 196 |
+
? "The document won here: the model's top answer is the correct one. This is what synchronization looks like."
|
| 197 |
+
: "The parametric memory still controls the answer.");
|
| 198 |
return s.join(" ");
|
| 199 |
}
|
| 200 |
|
|
|
|
| 305 |
|
| 306 |
const m = {
|
| 307 |
pt0, pt1, dsync, ictx,
|
| 308 |
+
answerPiece: fmtTok(pieces[0]),
|
| 309 |
topPlain: fmtTok(tokenizer.decode([topPlainId])),
|
| 310 |
topCtx: fmtTok(tokenizer.decode([topCtxId])),
|
| 311 |
pTopCtx: pCtx[topCtxId],
|
style.css
CHANGED
|
@@ -276,3 +276,69 @@ textarea:focus, input:focus, select:focus, #run:focus-visible, .preset:focus-vis
|
|
| 276 |
outline: 2px solid var(--accent); outline-offset: 1px;
|
| 277 |
}
|
| 278 |
.stagebar { border-radius: 6px; overflow: hidden; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
outline: 2px solid var(--accent); outline-offset: 1px;
|
| 277 |
}
|
| 278 |
.stagebar { border-radius: 6px; overflow: hidden; }
|
| 279 |
+
|
| 280 |
+
/* narrow screens: stack the paired bars rather than crushing four columns */
|
| 281 |
+
@media (max-width: 620px) {
|
| 282 |
+
body { padding: 2rem 1rem 3rem; }
|
| 283 |
+
.bar-tok { flex: 0 0 5rem; font-size: .78rem; }
|
| 284 |
+
.bar-pair { grid-template-columns: 1fr 4rem; row-gap: .2rem; }
|
| 285 |
+
.row { flex-wrap: wrap; }
|
| 286 |
+
#run { width: 100%; }
|
| 287 |
+
.authors { gap: 1.5rem; }
|
| 288 |
+
.stagebar { font-size: .62rem; }
|
| 289 |
+
.gauge-ticks span { font-size: .66rem; }
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
/* ---------------------------------------------------------------------------
|
| 293 |
+
Typographic measure and rhythm.
|
| 294 |
+
|
| 295 |
+
Prose is capped near 68 characters. Beyond that the eye loses the line
|
| 296 |
+
start on the return sweep, which is what made the earlier full-width
|
| 297 |
+
paragraphs tiring to read. Tables, bars, and gauges stay full width: they
|
| 298 |
+
are scanned, not read.
|
| 299 |
+
--------------------------------------------------------------------------- */
|
| 300 |
+
|
| 301 |
+
body { line-height: 1.65; }
|
| 302 |
+
|
| 303 |
+
.subtitle { max-width: 38rem; font-size: 1.02rem; }
|
| 304 |
+
.lede,
|
| 305 |
+
.reading,
|
| 306 |
+
#guide p,
|
| 307 |
+
.narrative,
|
| 308 |
+
.note { max-width: 46rem; }
|
| 309 |
+
.alert { max-width: 46rem; line-height: 1.6; }
|
| 310 |
+
.subtitle { max-width: 44rem; }
|
| 311 |
+
|
| 312 |
+
.lede { margin-bottom: 1.6rem; font-size: 1rem; }
|
| 313 |
+
.reading { margin: 1rem 0 1.8rem; }
|
| 314 |
+
|
| 315 |
+
header { margin-bottom: 2.2rem; }
|
| 316 |
+
header h1 { font-size: 1.9rem; margin-bottom: .6rem; }
|
| 317 |
+
.badges { margin-bottom: 1.2rem; }
|
| 318 |
+
|
| 319 |
+
.panel { padding-top: 2.2rem; }
|
| 320 |
+
.inputs { margin-top: 1.8rem; }
|
| 321 |
+
.inputs label { margin-bottom: 1.1rem; }
|
| 322 |
+
#results { margin-top: 2.8rem; }
|
| 323 |
+
.verdict-row { margin-bottom: 1.4rem; }
|
| 324 |
+
.narrative { padding: 1rem 1.2rem; margin-bottom: 2rem; line-height: 1.7; }
|
| 325 |
+
#bars { padding: 1rem 1.2rem; margin-bottom: 2.2rem; }
|
| 326 |
+
.gauge-block { margin-bottom: 2rem; }
|
| 327 |
+
.temp-block { padding: 1rem 1.2rem; margin-bottom: 1.8rem; }
|
| 328 |
+
|
| 329 |
+
#guide h2 { margin: 2.4rem 0 .8rem; }
|
| 330 |
+
#guide h2:first-child { margin-top: .4rem; }
|
| 331 |
+
#guide p { margin-bottom: 1.2rem; }
|
| 332 |
+
#guide table { margin-bottom: 1.8rem; }
|
| 333 |
+
th, td { padding: .55rem .7rem; }
|
| 334 |
+
|
| 335 |
+
h3 { margin: 1.8rem 0 .3rem; }
|
| 336 |
+
.cols h3 { margin-top: 0; }
|
| 337 |
+
|
| 338 |
+
footer { margin-top: 4rem; padding-top: 2rem; }
|
| 339 |
+
.authors { margin-bottom: 1.6rem; }
|
| 340 |
+
|
| 341 |
+
/* glossary: term column narrow and steady so definitions align */
|
| 342 |
+
.glossary td:first-child { width: 11rem; color: var(--ink); }
|
| 343 |
+
.glossary td:last-child { color: var(--muted); }
|
| 344 |
+
.glossary { max-width: 46rem; }
|