Skip to content

Commit c6596d7

Browse files
committed
Add a scrolled timeline of this year's AI incidents
Every entry's date, headline, prose, source and numbers live in the markdown, so the post reads top to bottom without the script. ts/src/aiincidents.ts watches which entry the reader is level with and fills a sticky panel with that entry's headline figure and the running totals over everything above it. An entry carries only its own contribution, as data-tally-<key>; the sums are computed in the browser because they depend on where the reader is.
1 parent 20ba302 commit c6596d7

3 files changed

Lines changed: 726 additions & 0 deletions

File tree

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/* The 2026 AI incident timeline: one column of dated entries under a sticky
2+
panel of running totals. The entries are plain markup and read top to bottom
3+
on their own; the panel and the dimming come from ts/src/aiincidents.ts, and
4+
nothing here is required for the post to be legible. Colours are the ones
5+
the interactive tools use, from custom.scss. */
6+
7+
.ait {
8+
--ait-border: color-mix(in srgb, var(--gray) 32%, transparent);
9+
--ait-muted: var(--gray);
10+
--ait-accent: var(--secondary);
11+
margin: 1.5rem 0 2.5rem;
12+
}
13+
14+
/* The panel */
15+
16+
.ait-dash {
17+
position: sticky;
18+
top: 0;
19+
z-index: 2;
20+
display: grid;
21+
grid-template-columns: 1fr auto;
22+
grid-template-areas:
23+
"when figure"
24+
"kind figure"
25+
"tallies tallies"
26+
"bar position";
27+
align-items: center;
28+
gap: 0.3rem 0.9rem;
29+
padding: 0.7rem 0.9rem;
30+
border: 1px solid var(--ait-border);
31+
border-radius: 6px;
32+
background: var(--light);
33+
box-shadow: 0 2px 10px color-mix(in srgb, var(--dark) 8%, transparent);
34+
}
35+
36+
.ait-dash > p,
37+
.ait-dash > dl {
38+
margin: 0;
39+
}
40+
41+
.ait-dash-when {
42+
grid-area: when;
43+
align-self: end;
44+
}
45+
46+
.ait-dash-date {
47+
font-weight: 600;
48+
}
49+
50+
.ait-dash-year {
51+
margin-left: 0.35rem;
52+
color: var(--ait-muted);
53+
font-size: 0.8rem;
54+
}
55+
56+
.ait-dash-kind {
57+
grid-area: kind;
58+
align-self: start;
59+
justify-self: start;
60+
}
61+
62+
.ait-dash-figure {
63+
display: grid;
64+
grid-area: figure;
65+
justify-items: end;
66+
text-align: right;
67+
}
68+
69+
.ait-dash-figure[hidden] {
70+
display: none;
71+
}
72+
73+
.ait-figure-value {
74+
color: var(--ait-accent);
75+
font-size: 1.9rem;
76+
font-variant-numeric: tabular-nums;
77+
font-weight: 600;
78+
line-height: 1.1;
79+
}
80+
81+
.ait-figure-label {
82+
max-width: 9rem;
83+
color: var(--ait-muted);
84+
font-size: 0.7rem;
85+
line-height: 1.25;
86+
}
87+
88+
.ait-tallies {
89+
grid-area: tallies;
90+
display: grid;
91+
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
92+
gap: 0.25rem 0.9rem;
93+
margin-top: 0.4rem;
94+
padding-top: 0.5rem;
95+
border-top: 1px solid var(--ait-border);
96+
}
97+
98+
.ait-tally {
99+
display: flex;
100+
align-items: baseline;
101+
justify-content: space-between;
102+
gap: 0.4rem;
103+
}
104+
105+
.ait-tally dt {
106+
color: var(--ait-muted);
107+
font-size: 0.7rem;
108+
line-height: 1.25;
109+
}
110+
111+
.ait-tally dd {
112+
margin: 0;
113+
font-variant-numeric: tabular-nums;
114+
font-weight: 600;
115+
}
116+
117+
.ait-progress {
118+
grid-area: bar;
119+
height: 3px;
120+
margin-top: 0.4rem;
121+
border-radius: 2px;
122+
background: var(--ait-border);
123+
overflow: hidden;
124+
}
125+
126+
.ait-progress-fill {
127+
display: block;
128+
width: 0;
129+
height: 100%;
130+
background: var(--ait-accent);
131+
transition: width 0.42s cubic-bezier(0.22, 1, 0.36, 1);
132+
}
133+
134+
.ait-position {
135+
grid-area: position;
136+
margin-top: 0.4rem;
137+
color: var(--ait-muted);
138+
font-size: 0.7rem;
139+
font-variant-numeric: tabular-nums;
140+
text-align: right;
141+
}
142+
143+
/* The entries */
144+
145+
.ait-track {
146+
--ait-inset: 1.6rem;
147+
--ait-rail: 0.35rem;
148+
--ait-dot: 0.55rem;
149+
position: relative;
150+
margin: 1.6rem 0 0;
151+
padding: 0 0 0 var(--ait-inset);
152+
list-style: none;
153+
}
154+
155+
.ait-track::before {
156+
content: "";
157+
position: absolute;
158+
top: 0.6rem;
159+
bottom: 0.6rem;
160+
left: var(--ait-rail);
161+
width: 1px;
162+
background: var(--ait-border);
163+
}
164+
165+
.ait-entry {
166+
position: relative;
167+
padding-bottom: 2.25rem;
168+
}
169+
170+
.ait-entry::before {
171+
content: "";
172+
position: absolute;
173+
top: 0.4rem;
174+
left: calc(var(--ait-rail) - var(--ait-inset) - var(--ait-dot) / 2);
175+
width: var(--ait-dot);
176+
height: var(--ait-dot);
177+
border: 1px solid var(--ait-border);
178+
border-radius: 50%;
179+
background: var(--light);
180+
transition:
181+
background 0.25s ease,
182+
border-color 0.25s ease,
183+
transform 0.25s ease;
184+
}
185+
186+
.ait-entry h3 {
187+
margin: 0.1rem 0 0.35rem;
188+
font-size: 1.02rem;
189+
line-height: 1.3;
190+
}
191+
192+
.ait-entry p {
193+
margin: 0 0 0.3rem;
194+
}
195+
196+
.ait-meta {
197+
display: flex;
198+
align-items: baseline;
199+
gap: 0.5rem;
200+
font-size: 0.75rem;
201+
}
202+
203+
.ait-meta time {
204+
color: var(--ait-muted);
205+
font-variant-numeric: tabular-nums;
206+
}
207+
208+
.ait-kind,
209+
.ait-dash-kind {
210+
padding: 0.05rem 0.45rem;
211+
border: 1px solid var(--ait-border);
212+
border-radius: 999px;
213+
color: var(--ait-muted);
214+
font-size: 0.65rem;
215+
letter-spacing: 0.06em;
216+
text-transform: uppercase;
217+
}
218+
219+
.ait-source {
220+
font-size: 0.75rem;
221+
}
222+
223+
.ait-source a {
224+
color: var(--ait-muted);
225+
}
226+
227+
.ait-source a:hover {
228+
color: var(--ait-accent);
229+
}
230+
231+
/* What the script drives. Before it runs, nothing is dimmed. */
232+
233+
.ait.is-live .ait-entry {
234+
opacity: 0.45;
235+
transition: opacity 0.3s ease;
236+
}
237+
238+
.ait.is-live .ait-entry.is-active,
239+
.ait.is-live .ait-entry.is-past {
240+
opacity: 1;
241+
}
242+
243+
.ait.is-live .ait-entry.is-active::before {
244+
border-color: var(--ait-accent);
245+
background: var(--ait-accent);
246+
transform: scale(1.3);
247+
}
248+
249+
.ait.is-live .ait-entry.is-past::before {
250+
border-color: color-mix(in srgb, var(--ait-accent) 50%, var(--ait-border));
251+
background: color-mix(in srgb, var(--ait-accent) 45%, var(--light));
252+
}
253+
254+
@media (prefers-reduced-motion: reduce) {
255+
.ait-progress-fill,
256+
.ait.is-live .ait-entry,
257+
.ait-entry::before {
258+
transition: none;
259+
}
260+
}

0 commit comments

Comments
 (0)