privateboard 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +879 -180
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/adjourn-overlay.css +2 -1
- package/public/agent-profile.css +525 -0
- package/public/agent-profile.js +278 -1
- package/public/app.js +269 -113
- package/public/index.html +304 -126
- package/public/magazine.html +1257 -838
- package/public/newspaper.html +1389 -1267
- package/public/ppt.html +2623 -0
- package/public/room-settings.css +40 -4
- package/public/room-settings.js +44 -2
- package/public/user-settings.css +23 -21
- package/public/user-settings.js +1 -4
- package/public/bento.html +0 -1396
package/public/newspaper.html
CHANGED
|
@@ -5,68 +5,60 @@
|
|
|
5
5
|
<meta name="viewport" content="width=900, initial-scale=1">
|
|
6
6
|
<title>Newspaper · PrivateBoard</title>
|
|
7
7
|
<link rel="icon" href="/avatars/chair.svg" type="image/svg+xml">
|
|
8
|
+
<!-- NYT blackletter nameplate · UnifrakturMaguntia is the free
|
|
9
|
+
open-source font that reads as "Old English / Fraktur" the way
|
|
10
|
+
the iconic NYT mast does. Loaded from Google Fonts; system
|
|
11
|
+
fallback to Old English Text MT when offline. -->
|
|
12
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
13
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
14
|
+
<link href="https://fonts.googleapis.com/css2?family=UnifrakturMaguntia&display=swap" rel="stylesheet">
|
|
8
15
|
<style>
|
|
9
16
|
/* ═══════════════════════════════════════════════════════════════════
|
|
10
|
-
Newspaper · broadsheet
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
·
|
|
18
|
-
|
|
19
|
-
·
|
|
20
|
-
|
|
21
|
-
·
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Reads BentoScaffold JSON from body_json. Same data shape as bento /
|
|
27
|
-
magazine modes · only the renderer differs.
|
|
28
|
-
─────────────────────────────────────────────────────────────────── */
|
|
17
|
+
Newspaper · two distinct broadsheet templates picked deterministi-
|
|
18
|
+
cally from the brief id (so a refresh always shows the same look
|
|
19
|
+
for the same brief). Both render at LEAST 2 pages so the report
|
|
20
|
+
reads as a paper rather than a single sheet.
|
|
21
|
+
|
|
22
|
+
· POST · The Boardroom Post · Washington-Post register · clean
|
|
23
|
+
modern broadsheet · cream paper · deep ink · RED section
|
|
24
|
+
accents · sans-y kickers · big hero photo zone.
|
|
25
|
+
· TIMES · The Boardroom · New York Times register · classical
|
|
26
|
+
broadsheet · BLACKLETTER nameplate · "All the news that's
|
|
27
|
+
fit to print" tagline · star-ornament dividers · NAVY accent
|
|
28
|
+
· italic decks · 6-col grid front page.
|
|
29
|
+
|
|
30
|
+
Both variants share base typography and helpers; the masthead +
|
|
31
|
+
section system + accent palette differ to give each its own feel.
|
|
32
|
+
═══════════════════════════════════════════════════════════════════ */
|
|
29
33
|
:root {
|
|
30
|
-
/*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
|
|
45
|
-
--
|
|
46
|
-
--
|
|
47
|
-
--ink-faint: #8E7A60;
|
|
48
|
-
--ink-muted: #B0A085;
|
|
49
|
-
|
|
50
|
-
/* Inverted register · dark callouts */
|
|
51
|
-
--inv-bg: #2A1D10;
|
|
52
|
-
--inv-bg-soft: #3A2A1A;
|
|
53
|
-
--inv-ink: #F5EBD2;
|
|
54
|
-
--inv-ink-soft: #C7B894;
|
|
55
|
-
|
|
56
|
-
/* Rules · brown hairlines */
|
|
57
|
-
--rule: #B5A483;
|
|
58
|
-
--rule-soft: #C9BB9D;
|
|
34
|
+
/* Page bg + paper + ink palette · shared across variants */
|
|
35
|
+
--bg: #1F1E1A;
|
|
36
|
+
--paper: #F4EFDF;
|
|
37
|
+
--paper-soft: #FAF6E8;
|
|
38
|
+
--paper-edge: #E8DFC4;
|
|
39
|
+
--ink: #14110B;
|
|
40
|
+
--ink-soft: #3A2F1E;
|
|
41
|
+
--ink-mid: #5E5238;
|
|
42
|
+
--ink-faint: #8E8160;
|
|
43
|
+
--ink-muted: #B0A380;
|
|
44
|
+
|
|
45
|
+
--inv-bg: #1A2734;
|
|
46
|
+
--inv-ink: #F4EFDF;
|
|
47
|
+
--inv-ink-soft: #B7C0CB;
|
|
48
|
+
|
|
49
|
+
--rule: #C3B891;
|
|
50
|
+
--rule-soft: #D2C7A0;
|
|
59
51
|
--rule-strong: #8C7A56;
|
|
60
52
|
|
|
61
|
-
/*
|
|
62
|
-
--accent:
|
|
53
|
+
/* Default accents · POST variant (Washington Post · red) */
|
|
54
|
+
--accent: #C42126;
|
|
55
|
+
--accent-deep: #951C1F;
|
|
56
|
+
--accent-soft: #E6BFC1;
|
|
57
|
+
--accent-blue: #2C5282;
|
|
63
58
|
|
|
64
|
-
/*
|
|
65
|
-
--
|
|
66
|
-
|
|
67
|
-
/* Shadow · only on the doc itself (lift it off the page) */
|
|
68
|
-
--shadow-doc: 0 2px 8px rgba(40, 25, 10, 0.08),
|
|
69
|
-
0 12px 36px rgba(40, 25, 10, 0.10);
|
|
59
|
+
/* Shadow for paper stacks */
|
|
60
|
+
--shadow-page: 0 2px 8px rgba(20, 17, 11, 0.18),
|
|
61
|
+
0 16px 48px rgba(20, 17, 11, 0.20);
|
|
70
62
|
|
|
71
63
|
--serif-display: "Tiempos Headline", "Playfair Display", "Bodoni 72",
|
|
72
64
|
"Didot", "Source Serif Pro", "Charter", Georgia,
|
|
@@ -79,17 +71,29 @@
|
|
|
79
71
|
"Source Han Sans CN", "Noto Sans CJK SC", sans-serif;
|
|
80
72
|
--mono: "SF Mono", "JetBrains Mono", "Menlo",
|
|
81
73
|
"PingFang SC", "Source Han Sans CN", monospace;
|
|
74
|
+
--blackletter: "UnifrakturMaguntia", "Old English Text MT",
|
|
75
|
+
"Cloister Black", "Goudy Old Style",
|
|
76
|
+
"Tiempos Headline", Georgia, serif;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* TIMES variant · accent shifts from red to navy, paper goes very
|
|
80
|
+
slightly cooler (NYT pages have a less yellowed tint than WaPo's). */
|
|
81
|
+
body[data-np-variant="times"] {
|
|
82
|
+
--paper: #F8F5E8;
|
|
83
|
+
--paper-soft: #FCF9EE;
|
|
84
|
+
--paper-edge: #ECE5CE;
|
|
85
|
+
--rule: #BCB29A;
|
|
86
|
+
--rule-soft: #C9C0AA;
|
|
87
|
+
--rule-strong: #6E6549;
|
|
88
|
+
--accent: #14366A;
|
|
89
|
+
--accent-deep: #0A2148;
|
|
90
|
+
--accent-soft: #B8C5D9;
|
|
91
|
+
--accent-blue: #14366A;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
85
95
|
html, body {
|
|
86
|
-
|
|
87
|
-
broadsheet · the radial gradient sits on top of the flat fill
|
|
88
|
-
to give the page a slight printed-press hint without committing
|
|
89
|
-
to a noise texture. */
|
|
90
|
-
background:
|
|
91
|
-
radial-gradient(120% 80% at 50% 0%, var(--bg-radial) 0%, transparent 60%),
|
|
92
|
-
var(--bg);
|
|
96
|
+
background: var(--bg);
|
|
93
97
|
color: var(--ink);
|
|
94
98
|
font-family: var(--serif);
|
|
95
99
|
font-size: 14px;
|
|
@@ -99,22 +103,22 @@
|
|
|
99
103
|
min-height: 100vh;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
/* ─── Top chrome
|
|
106
|
+
/* ─── Top chrome ───────────────────────────────────────────────── */
|
|
103
107
|
.np-top-bar {
|
|
104
108
|
display: flex;
|
|
105
109
|
align-items: center;
|
|
106
110
|
justify-content: space-between;
|
|
107
111
|
gap: 14px;
|
|
108
|
-
padding:
|
|
109
|
-
background:
|
|
112
|
+
padding: 14px 28px;
|
|
113
|
+
background: rgba(31, 30, 26, 0.92);
|
|
110
114
|
backdrop-filter: blur(10px);
|
|
111
115
|
-webkit-backdrop-filter: blur(10px);
|
|
112
|
-
border-bottom: 1px solid
|
|
116
|
+
border-bottom: 1px solid rgba(244, 239, 223, 0.12);
|
|
113
117
|
flex-wrap: wrap;
|
|
114
118
|
position: sticky;
|
|
115
119
|
top: 0;
|
|
116
120
|
z-index: 10;
|
|
117
|
-
|
|
121
|
+
color: var(--paper);
|
|
118
122
|
}
|
|
119
123
|
.np-crumb {
|
|
120
124
|
display: inline-flex;
|
|
@@ -123,862 +127,1077 @@
|
|
|
123
127
|
font-family: var(--serif-display);
|
|
124
128
|
font-size: 16px;
|
|
125
129
|
font-weight: 700;
|
|
126
|
-
color: var(--
|
|
130
|
+
color: var(--paper);
|
|
127
131
|
text-decoration: none;
|
|
128
132
|
}
|
|
129
133
|
.np-crumb::before {
|
|
130
134
|
content: "";
|
|
131
|
-
width:
|
|
132
|
-
height:
|
|
133
|
-
background: var(--
|
|
135
|
+
width: 9px;
|
|
136
|
+
height: 9px;
|
|
137
|
+
background: var(--accent);
|
|
134
138
|
flex: 0 0 auto;
|
|
139
|
+
border-radius: 50%;
|
|
135
140
|
}
|
|
136
141
|
.np-crumb-accent {
|
|
137
142
|
color: var(--ink-faint);
|
|
138
143
|
font-style: italic;
|
|
139
144
|
font-weight: 400;
|
|
140
145
|
}
|
|
141
|
-
.np-actions { display: flex; gap: 8px; }
|
|
146
|
+
.np-actions { display: flex; gap: 8px; align-items: center; }
|
|
147
|
+
.np-page-nav {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
gap: 4px;
|
|
150
|
+
margin-right: 12px;
|
|
151
|
+
font-family: var(--mono);
|
|
152
|
+
font-size: 10px;
|
|
153
|
+
letter-spacing: 0.12em;
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
}
|
|
156
|
+
.np-page-nav a {
|
|
157
|
+
color: var(--ink-muted);
|
|
158
|
+
padding: 4px 8px;
|
|
159
|
+
border: 1px solid rgba(244, 239, 223, 0.16);
|
|
160
|
+
text-decoration: none;
|
|
161
|
+
transition: all 0.12s;
|
|
162
|
+
}
|
|
163
|
+
.np-page-nav a:hover {
|
|
164
|
+
background: rgba(244, 239, 223, 0.10);
|
|
165
|
+
color: var(--paper);
|
|
166
|
+
}
|
|
167
|
+
.np-variant-badge {
|
|
168
|
+
font-family: var(--mono);
|
|
169
|
+
font-size: 10px;
|
|
170
|
+
letter-spacing: 0.16em;
|
|
171
|
+
text-transform: uppercase;
|
|
172
|
+
color: var(--accent-soft);
|
|
173
|
+
margin-right: 12px;
|
|
174
|
+
padding: 4px 10px;
|
|
175
|
+
background: rgba(244, 239, 223, 0.06);
|
|
176
|
+
border: 1px solid rgba(244, 239, 223, 0.16);
|
|
177
|
+
}
|
|
142
178
|
.np-btn {
|
|
143
179
|
font-family: var(--mono);
|
|
144
|
-
font-size:
|
|
180
|
+
font-size: 10.5px;
|
|
145
181
|
letter-spacing: 0.04em;
|
|
146
|
-
padding:
|
|
147
|
-
background:
|
|
148
|
-
border: 1px solid
|
|
149
|
-
color: var(--
|
|
182
|
+
padding: 7px 12px;
|
|
183
|
+
background: transparent;
|
|
184
|
+
border: 1px solid rgba(244, 239, 223, 0.30);
|
|
185
|
+
color: var(--paper);
|
|
150
186
|
cursor: pointer;
|
|
151
187
|
text-decoration: none;
|
|
152
|
-
|
|
188
|
+
text-transform: uppercase;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
transition: background 0.15s, color 0.15s;
|
|
153
191
|
}
|
|
154
192
|
.np-btn:hover {
|
|
155
|
-
background: var(--
|
|
156
|
-
color: var(--
|
|
157
|
-
border-color: var(--ink);
|
|
158
|
-
transform: translateY(-1px);
|
|
193
|
+
background: var(--paper);
|
|
194
|
+
color: var(--bg);
|
|
159
195
|
}
|
|
160
|
-
.np-btn:active { transform: translateY(0); }
|
|
161
196
|
.np-btn .glyph { margin-right: 4px; }
|
|
162
197
|
|
|
163
|
-
/* ─── Doc ·
|
|
198
|
+
/* ─── Doc · paper-stack frame ──────────────────────────────────── */
|
|
164
199
|
.np-doc {
|
|
165
|
-
max-width:
|
|
166
|
-
margin:
|
|
200
|
+
max-width: 920px;
|
|
201
|
+
margin: 0 auto;
|
|
202
|
+
padding: 28px 12px 56px;
|
|
203
|
+
}
|
|
204
|
+
.np-page {
|
|
167
205
|
background: var(--paper);
|
|
168
|
-
|
|
169
|
-
|
|
206
|
+
padding: 26px 32px 30px;
|
|
207
|
+
margin-bottom: 24px;
|
|
208
|
+
box-shadow: var(--shadow-page);
|
|
170
209
|
position: relative;
|
|
171
210
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
content: "";
|
|
176
|
-
position: absolute;
|
|
177
|
-
inset: 0;
|
|
178
|
-
pointer-events: none;
|
|
179
|
-
background:
|
|
180
|
-
linear-gradient(180deg, rgba(120, 90, 50, 0.04) 0%, transparent 30px),
|
|
181
|
-
linear-gradient(0deg, rgba(120, 90, 50, 0.04) 0%, transparent 30px);
|
|
211
|
+
.np-page:last-child { margin-bottom: 0; }
|
|
212
|
+
body[data-np-variant="times"] .np-page {
|
|
213
|
+
padding: 28px 36px 30px;
|
|
182
214
|
}
|
|
183
215
|
|
|
184
|
-
/* ─── Rules · single + double
|
|
185
|
-
.np-rule {
|
|
186
|
-
|
|
187
|
-
background: var(--ink);
|
|
188
|
-
margin: 14px 0;
|
|
189
|
-
}
|
|
216
|
+
/* ─── Rules · single + double · used as section dividers ─────── */
|
|
217
|
+
.np-rule { height: 1px; background: var(--ink); margin: 12px 0; }
|
|
218
|
+
.np-rule-thin { height: 1px; background: var(--rule); margin: 8px 0; }
|
|
190
219
|
.np-rule-double {
|
|
191
|
-
height:
|
|
220
|
+
height: 5px;
|
|
192
221
|
background:
|
|
193
222
|
linear-gradient(to bottom,
|
|
194
223
|
var(--ink) 0,
|
|
195
224
|
var(--ink) 1px,
|
|
196
225
|
transparent 1px,
|
|
197
|
-
transparent
|
|
198
|
-
var(--ink)
|
|
199
|
-
var(--ink)
|
|
200
|
-
margin:
|
|
201
|
-
}
|
|
202
|
-
.np-rule-thick {
|
|
203
|
-
height: 3px;
|
|
204
|
-
background: var(--ink);
|
|
205
|
-
margin: 18px 0;
|
|
226
|
+
transparent 4px,
|
|
227
|
+
var(--ink) 4px,
|
|
228
|
+
var(--ink) 5px);
|
|
229
|
+
margin: 12px 0;
|
|
206
230
|
}
|
|
231
|
+
.np-rule-thick { height: 2px; background: var(--ink); margin: 14px 0; }
|
|
207
232
|
|
|
208
|
-
/*
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
233
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
234
|
+
POST variant · The Boardroom Post · Washington-Post register
|
|
235
|
+
═══════════════════════════════════════════════════════════════ */
|
|
236
|
+
|
|
237
|
+
/* Section dots row · small accent dots above the masthead, each
|
|
238
|
+
paired with a section name. WaPo uses a colorful section nav
|
|
239
|
+
above the mast as a navigation aid. */
|
|
240
|
+
.post-sect-row {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: center;
|
|
243
|
+
gap: 22px;
|
|
244
|
+
padding: 2px 0 8px;
|
|
245
|
+
border-bottom: 1px solid var(--rule-soft);
|
|
246
|
+
margin-bottom: 10px;
|
|
247
|
+
flex-wrap: wrap;
|
|
248
|
+
}
|
|
249
|
+
.post-sect-item {
|
|
250
|
+
display: inline-flex;
|
|
213
251
|
align-items: center;
|
|
214
|
-
|
|
252
|
+
gap: 6px;
|
|
253
|
+
font-family: var(--sans);
|
|
254
|
+
font-size: 10px;
|
|
255
|
+
letter-spacing: 0.14em;
|
|
256
|
+
text-transform: uppercase;
|
|
257
|
+
color: var(--ink-mid);
|
|
258
|
+
font-weight: 700;
|
|
215
259
|
}
|
|
216
|
-
.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
text-justify: inter-word;
|
|
223
|
-
/* Vertical hairlines bracket the flanks · one inside edge each ·
|
|
224
|
-
evokes the nameplate "advertorial" boxes in classic broadsheets. */
|
|
225
|
-
border-left: 1px solid var(--ink);
|
|
226
|
-
border-right: 1px solid var(--ink);
|
|
227
|
-
padding: 4px 8px;
|
|
228
|
-
max-width: 160px;
|
|
229
|
-
word-spacing: -0.03em;
|
|
260
|
+
.post-sect-item::before {
|
|
261
|
+
content: "";
|
|
262
|
+
width: 6px;
|
|
263
|
+
height: 6px;
|
|
264
|
+
border-radius: 50%;
|
|
265
|
+
background: var(--accent);
|
|
230
266
|
}
|
|
231
|
-
.
|
|
232
|
-
.
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
font-
|
|
267
|
+
.post-sect-item.is-blue::before { background: var(--accent-blue); }
|
|
268
|
+
.post-sect-item.is-gold::before { background: #C8A36F; }
|
|
269
|
+
.post-sect-item.is-teal::before { background: #2C7B7A; }
|
|
270
|
+
|
|
271
|
+
/* Tagline + masthead (POST) · smaller / cleaner than NYT */
|
|
272
|
+
.post-tagline {
|
|
273
|
+
font-family: var(--serif);
|
|
274
|
+
font-style: italic;
|
|
275
|
+
font-size: 12px;
|
|
276
|
+
color: var(--ink-mid);
|
|
277
|
+
text-align: center;
|
|
278
|
+
margin-bottom: 6px;
|
|
239
279
|
}
|
|
240
|
-
.
|
|
280
|
+
.post-mast-name {
|
|
241
281
|
font-family: var(--serif-display);
|
|
242
|
-
font-size:
|
|
243
|
-
font-weight:
|
|
244
|
-
line-height:
|
|
245
|
-
letter-spacing: 0.
|
|
246
|
-
text-transform: uppercase;
|
|
282
|
+
font-size: 50px;
|
|
283
|
+
font-weight: 800;
|
|
284
|
+
line-height: 0.96;
|
|
285
|
+
letter-spacing: -0.018em;
|
|
247
286
|
color: var(--ink);
|
|
248
287
|
text-align: center;
|
|
249
|
-
|
|
250
|
-
padding: 0 4px;
|
|
251
|
-
white-space: nowrap;
|
|
288
|
+
margin: 2px 0 6px;
|
|
252
289
|
}
|
|
253
290
|
@media (max-width: 720px) {
|
|
254
|
-
.
|
|
255
|
-
.np-mast-flank { display: none; }
|
|
256
|
-
.np-masthead { grid-template-columns: 1fr; }
|
|
291
|
+
.post-mast-name { font-size: 36px; }
|
|
257
292
|
}
|
|
258
293
|
|
|
259
|
-
/*
|
|
260
|
-
.
|
|
294
|
+
/* WaPo meta strip · 3-cell with date/edition/copy */
|
|
295
|
+
.post-meta {
|
|
261
296
|
display: grid;
|
|
262
297
|
grid-template-columns: 1fr auto 1fr;
|
|
263
|
-
gap: 18px;
|
|
264
298
|
align-items: center;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
299
|
+
gap: 14px;
|
|
300
|
+
padding: 6px 0;
|
|
301
|
+
border-top: 2px solid var(--ink);
|
|
302
|
+
border-bottom: 1px solid var(--ink);
|
|
303
|
+
font-family: var(--sans);
|
|
304
|
+
font-size: 10.5px;
|
|
305
|
+
letter-spacing: 0.08em;
|
|
306
|
+
text-transform: uppercase;
|
|
307
|
+
color: var(--ink-mid);
|
|
308
|
+
font-weight: 600;
|
|
270
309
|
}
|
|
271
|
-
.
|
|
272
|
-
.
|
|
273
|
-
.
|
|
310
|
+
.post-meta-l { text-align: left; }
|
|
311
|
+
.post-meta-c { text-align: center; }
|
|
312
|
+
.post-meta-r { text-align: right; }
|
|
274
313
|
|
|
275
|
-
/*
|
|
276
|
-
.
|
|
277
|
-
|
|
278
|
-
padding: 18px 24px 14px;
|
|
314
|
+
/* Hero zone · big headline with deck + lead body + standfirst */
|
|
315
|
+
.post-hero {
|
|
316
|
+
padding: 14px 0 8px;
|
|
279
317
|
}
|
|
280
|
-
.
|
|
281
|
-
|
|
282
|
-
|
|
318
|
+
.post-hero-flag {
|
|
319
|
+
display: inline-block;
|
|
320
|
+
background: var(--accent);
|
|
321
|
+
color: var(--paper);
|
|
322
|
+
font-family: var(--sans);
|
|
323
|
+
font-size: 10px;
|
|
283
324
|
font-weight: 800;
|
|
284
|
-
|
|
285
|
-
letter-spacing: 0.005em;
|
|
325
|
+
letter-spacing: 0.18em;
|
|
286
326
|
text-transform: uppercase;
|
|
287
|
-
|
|
288
|
-
margin:
|
|
327
|
+
padding: 4px 10px;
|
|
328
|
+
margin-bottom: 8px;
|
|
289
329
|
}
|
|
290
|
-
.
|
|
330
|
+
.post-hero-title {
|
|
291
331
|
font-family: var(--serif-display);
|
|
292
|
-
font-size:
|
|
293
|
-
font-weight:
|
|
294
|
-
line-height: 1.
|
|
295
|
-
letter-spacing: 0.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
332
|
+
font-size: 42px;
|
|
333
|
+
font-weight: 800;
|
|
334
|
+
line-height: 1.04;
|
|
335
|
+
letter-spacing: -0.012em;
|
|
336
|
+
color: var(--ink);
|
|
337
|
+
margin-bottom: 8px;
|
|
338
|
+
max-width: 760px;
|
|
299
339
|
}
|
|
300
340
|
@media (max-width: 720px) {
|
|
301
|
-
.
|
|
302
|
-
.np-frontpage-deck { font-size: 15px; }
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/* ─── Editorial 3-column grid ───────────────────────────────── */
|
|
306
|
-
.np-grid {
|
|
307
|
-
display: grid;
|
|
308
|
-
grid-template-columns: 1fr 1fr 1fr;
|
|
309
|
-
gap: 22px;
|
|
310
|
-
margin: 18px 0;
|
|
341
|
+
.post-hero-title { font-size: 30px; }
|
|
311
342
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
343
|
+
.post-hero-deck {
|
|
344
|
+
font-family: var(--serif);
|
|
345
|
+
font-style: italic;
|
|
346
|
+
font-size: 16px;
|
|
347
|
+
line-height: 1.45;
|
|
348
|
+
color: var(--ink-mid);
|
|
349
|
+
max-width: 720px;
|
|
350
|
+
margin-bottom: 8px;
|
|
318
351
|
}
|
|
319
|
-
.
|
|
320
|
-
|
|
321
|
-
|
|
352
|
+
.post-hero-byline {
|
|
353
|
+
font-family: var(--sans);
|
|
354
|
+
font-size: 11px;
|
|
355
|
+
letter-spacing: 0.06em;
|
|
356
|
+
text-transform: uppercase;
|
|
357
|
+
color: var(--ink-mid);
|
|
358
|
+
font-weight: 600;
|
|
359
|
+
margin-bottom: 4px;
|
|
322
360
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
padding-right: 0;
|
|
330
|
-
border-right: 0;
|
|
331
|
-
border-bottom: 1px solid var(--rule);
|
|
332
|
-
padding-bottom: 18px;
|
|
333
|
-
}
|
|
334
|
-
.np-grid > .np-col:last-child { border-bottom: 0; }
|
|
361
|
+
.post-hero-byline em {
|
|
362
|
+
font-style: italic;
|
|
363
|
+
text-transform: none;
|
|
364
|
+
color: var(--ink-soft);
|
|
365
|
+
font-weight: 400;
|
|
366
|
+
letter-spacing: 0;
|
|
335
367
|
}
|
|
336
368
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
369
|
+
/* Standfirst panel · solid colored block carrying a pull-quote
|
|
370
|
+
(replaces the prior gradient portrait fake-photo). */
|
|
371
|
+
.post-stand {
|
|
372
|
+
background: var(--inv-bg);
|
|
373
|
+
color: var(--paper);
|
|
374
|
+
padding: 18px 22px;
|
|
375
|
+
margin: 10px 0;
|
|
376
|
+
display: grid;
|
|
377
|
+
grid-template-columns: 48px 1fr;
|
|
378
|
+
gap: 14px;
|
|
379
|
+
align-items: center;
|
|
342
380
|
}
|
|
343
|
-
.
|
|
381
|
+
.post-stand-mark {
|
|
344
382
|
font-family: var(--serif-display);
|
|
345
|
-
font-
|
|
346
|
-
font-
|
|
383
|
+
font-style: italic;
|
|
384
|
+
font-size: 54px;
|
|
385
|
+
line-height: 0.6;
|
|
386
|
+
color: var(--accent-soft);
|
|
387
|
+
}
|
|
388
|
+
.post-stand-text {
|
|
389
|
+
font-family: var(--serif-display);
|
|
390
|
+
font-style: italic;
|
|
391
|
+
font-size: 19px;
|
|
392
|
+
line-height: 1.32;
|
|
393
|
+
color: var(--paper);
|
|
394
|
+
letter-spacing: -0.005em;
|
|
395
|
+
}
|
|
396
|
+
.post-stand-cite {
|
|
397
|
+
font-family: var(--sans);
|
|
398
|
+
font-size: 10px;
|
|
399
|
+
letter-spacing: 0.12em;
|
|
347
400
|
text-transform: uppercase;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
margin: 0;
|
|
352
|
-
/* Tight underline rule below section heading */
|
|
353
|
-
padding-bottom: 6px;
|
|
354
|
-
border-bottom: 1px solid var(--ink);
|
|
401
|
+
color: var(--inv-ink-soft);
|
|
402
|
+
margin-top: 8px;
|
|
403
|
+
display: block;
|
|
355
404
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
405
|
+
@media (max-width: 720px) {
|
|
406
|
+
.post-stand { grid-template-columns: 1fr; }
|
|
407
|
+
.post-stand-mark { display: none; }
|
|
408
|
+
.post-stand-text { font-size: 18px; }
|
|
359
409
|
}
|
|
360
410
|
|
|
361
|
-
/*
|
|
362
|
-
|
|
363
|
-
|
|
411
|
+
/* 2-col body grid (POST) · single-column main story (no internal
|
|
412
|
+
multi-column · the brief data isn't dense enough to fill a
|
|
413
|
+
2-col body without leaving gaps) · sidebar with stacked tiles. */
|
|
414
|
+
.post-grid {
|
|
415
|
+
display: grid;
|
|
416
|
+
grid-template-columns: minmax(0, 1.7fr) minmax(180px, 1fr);
|
|
417
|
+
gap: 22px;
|
|
418
|
+
margin-top: 4px;
|
|
419
|
+
align-items: start;
|
|
420
|
+
}
|
|
421
|
+
@media (max-width: 720px) {
|
|
422
|
+
.post-grid { grid-template-columns: 1fr; gap: 18px; }
|
|
423
|
+
}
|
|
424
|
+
.post-body {
|
|
364
425
|
font-family: var(--serif);
|
|
365
|
-
font-size:
|
|
366
|
-
line-height: 1.
|
|
426
|
+
font-size: 13.5px;
|
|
427
|
+
line-height: 1.62;
|
|
367
428
|
color: var(--ink-soft);
|
|
368
429
|
text-align: justify;
|
|
369
|
-
text-justify: inter-word;
|
|
370
430
|
hyphens: auto;
|
|
371
431
|
-webkit-hyphens: auto;
|
|
372
432
|
}
|
|
373
|
-
.
|
|
374
|
-
.
|
|
433
|
+
.post-body p + p { margin-top: 8px; }
|
|
434
|
+
.post-body.has-drop::first-letter {
|
|
375
435
|
font-family: var(--serif-display);
|
|
376
|
-
font-size:
|
|
377
|
-
line-height: 0.
|
|
378
|
-
font-weight:
|
|
379
|
-
color: var(--
|
|
436
|
+
font-size: 56px;
|
|
437
|
+
line-height: 0.85;
|
|
438
|
+
font-weight: 800;
|
|
439
|
+
color: var(--accent);
|
|
380
440
|
float: left;
|
|
381
|
-
margin:
|
|
382
|
-
padding: 0;
|
|
383
|
-
}
|
|
384
|
-
.np-prose strong, .np-prose b {
|
|
385
|
-
color: var(--ink);
|
|
386
|
-
font-weight: 700;
|
|
387
|
-
}
|
|
388
|
-
.np-pagehint {
|
|
389
|
-
font-family: var(--serif);
|
|
390
|
-
font-size: 11px;
|
|
391
|
-
font-style: italic;
|
|
392
|
-
color: var(--ink-mid);
|
|
393
|
-
margin-top: 4px;
|
|
441
|
+
margin: 6px 8px 0 0;
|
|
394
442
|
}
|
|
395
|
-
.np-pagehint::before { content: "→ "; font-style: normal; color: var(--ink); }
|
|
396
443
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
padding:
|
|
444
|
+
.post-side {
|
|
445
|
+
display: flex;
|
|
446
|
+
flex-direction: column;
|
|
447
|
+
gap: 12px;
|
|
448
|
+
padding-left: 22px;
|
|
449
|
+
border-left: 1px solid var(--rule);
|
|
402
450
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
451
|
+
@media (max-width: 720px) {
|
|
452
|
+
.post-side { border-left: 0; padding-left: 0; padding-top: 16px; border-top: 1px solid var(--rule); }
|
|
453
|
+
}
|
|
454
|
+
.post-side-flag {
|
|
455
|
+
display: inline-block;
|
|
456
|
+
font-family: var(--sans);
|
|
457
|
+
font-size: 10px;
|
|
458
|
+
font-weight: 800;
|
|
408
459
|
letter-spacing: 0.18em;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
460
|
+
text-transform: uppercase;
|
|
461
|
+
color: var(--accent);
|
|
462
|
+
padding-bottom: 6px;
|
|
463
|
+
border-bottom: 2px solid var(--accent);
|
|
464
|
+
margin-bottom: 6px;
|
|
465
|
+
align-self: flex-start;
|
|
412
466
|
}
|
|
413
|
-
.
|
|
467
|
+
.post-side-head {
|
|
414
468
|
font-family: var(--serif-display);
|
|
415
469
|
font-size: 18px;
|
|
416
470
|
font-weight: 700;
|
|
417
|
-
line-height: 1.
|
|
418
|
-
letter-spacing: -0.
|
|
419
|
-
color: var(--
|
|
420
|
-
text-transform: uppercase;
|
|
471
|
+
line-height: 1.2;
|
|
472
|
+
letter-spacing: -0.008em;
|
|
473
|
+
color: var(--ink);
|
|
421
474
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
475
|
+
.post-side-body {
|
|
476
|
+
font-family: var(--serif);
|
|
477
|
+
font-size: 12.5px;
|
|
478
|
+
line-height: 1.55;
|
|
479
|
+
color: var(--ink-soft);
|
|
426
480
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
481
|
+
|
|
482
|
+
/* Stat tile (POST register) · cream box with hairline border +
|
|
483
|
+
red bar accent. Numeric size sits at 36px to fit the narrow
|
|
484
|
+
sidebar (~200px) without wrapping awkwardly. word-break
|
|
485
|
+
ensures any 12-char callout still fits cleanly. */
|
|
486
|
+
.post-stat {
|
|
487
|
+
background: var(--paper-soft);
|
|
488
|
+
padding: 16px 16px 16px;
|
|
489
|
+
border-top: 4px solid var(--accent);
|
|
490
|
+
margin-top: 6px;
|
|
436
491
|
}
|
|
437
|
-
.
|
|
438
|
-
font-family: var(--
|
|
439
|
-
font-size:
|
|
440
|
-
font-weight:
|
|
492
|
+
.post-stat-eye {
|
|
493
|
+
font-family: var(--sans);
|
|
494
|
+
font-size: 10px;
|
|
495
|
+
font-weight: 800;
|
|
441
496
|
letter-spacing: 0.18em;
|
|
442
497
|
text-transform: uppercase;
|
|
443
|
-
color: var(--
|
|
444
|
-
|
|
445
|
-
margin-bottom: 12px;
|
|
446
|
-
}
|
|
447
|
-
.np-callout-date .np-date-num {
|
|
448
|
-
font-family: var(--serif-display);
|
|
449
|
-
font-size: 56px;
|
|
450
|
-
font-weight: 900;
|
|
451
|
-
line-height: 1;
|
|
452
|
-
color: var(--inv-ink);
|
|
453
|
-
letter-spacing: -0.02em;
|
|
454
|
-
margin-bottom: 8px;
|
|
498
|
+
color: var(--accent);
|
|
499
|
+
margin-bottom: 6px;
|
|
455
500
|
}
|
|
456
|
-
.
|
|
501
|
+
.post-stat-num {
|
|
457
502
|
font-family: var(--serif-display);
|
|
458
|
-
font-size:
|
|
459
|
-
font-weight:
|
|
460
|
-
line-height: 1;
|
|
461
|
-
letter-spacing: 0.
|
|
462
|
-
color: var(--
|
|
463
|
-
|
|
503
|
+
font-size: 36px;
|
|
504
|
+
font-weight: 800;
|
|
505
|
+
line-height: 1.0;
|
|
506
|
+
letter-spacing: -0.022em;
|
|
507
|
+
color: var(--ink);
|
|
508
|
+
word-break: break-word;
|
|
509
|
+
overflow-wrap: anywhere;
|
|
464
510
|
}
|
|
465
|
-
.
|
|
511
|
+
.post-stat-cap {
|
|
466
512
|
font-family: var(--serif);
|
|
467
|
-
font-size: 11px;
|
|
468
513
|
font-style: italic;
|
|
469
|
-
|
|
470
|
-
|
|
514
|
+
font-size: 12.5px;
|
|
515
|
+
line-height: 1.4;
|
|
516
|
+
color: var(--ink-mid);
|
|
517
|
+
margin-top: 6px;
|
|
471
518
|
}
|
|
472
519
|
|
|
473
|
-
/*
|
|
474
|
-
.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
520
|
+
/* Index strip at bottom of front page · "Inside this issue" */
|
|
521
|
+
.post-index {
|
|
522
|
+
display: grid;
|
|
523
|
+
grid-template-columns: auto 1fr;
|
|
524
|
+
gap: 18px;
|
|
525
|
+
align-items: start;
|
|
526
|
+
margin-top: 16px;
|
|
527
|
+
padding-top: 12px;
|
|
528
|
+
border-top: 2px solid var(--ink);
|
|
529
|
+
}
|
|
530
|
+
.post-index-flag {
|
|
531
|
+
font-family: var(--sans);
|
|
532
|
+
font-size: 10px;
|
|
533
|
+
font-weight: 800;
|
|
534
|
+
letter-spacing: 0.18em;
|
|
535
|
+
text-transform: uppercase;
|
|
536
|
+
color: var(--ink);
|
|
537
|
+
padding: 4px 10px;
|
|
538
|
+
border: 1px solid var(--ink);
|
|
539
|
+
background: var(--paper);
|
|
540
|
+
align-self: flex-start;
|
|
541
|
+
}
|
|
542
|
+
.post-index-list {
|
|
543
|
+
list-style: none;
|
|
544
|
+
margin: 0;
|
|
545
|
+
padding: 0;
|
|
546
|
+
display: grid;
|
|
547
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
548
|
+
gap: 8px 22px;
|
|
484
549
|
}
|
|
485
|
-
.
|
|
550
|
+
.post-index-list li {
|
|
486
551
|
display: grid;
|
|
487
552
|
grid-template-columns: 1fr auto;
|
|
488
553
|
gap: 6px;
|
|
489
554
|
align-items: baseline;
|
|
490
555
|
font-family: var(--serif);
|
|
491
|
-
font-size:
|
|
556
|
+
font-size: 12.5px;
|
|
557
|
+
line-height: 1.4;
|
|
558
|
+
color: var(--ink-soft);
|
|
559
|
+
border-bottom: 1px dotted var(--rule-strong);
|
|
560
|
+
padding-bottom: 4px;
|
|
561
|
+
}
|
|
562
|
+
.post-index-list li b {
|
|
563
|
+
font-family: var(--serif-display);
|
|
564
|
+
font-weight: 700;
|
|
492
565
|
color: var(--ink);
|
|
493
566
|
}
|
|
494
|
-
.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
var(--paper-edge) 4px,
|
|
502
|
-
var(--paper-soft) 4px,
|
|
503
|
-
var(--paper-soft) 8px
|
|
504
|
-
);
|
|
505
|
-
border: 1px solid var(--ink);
|
|
506
|
-
position: relative;
|
|
507
|
-
margin-top: 2px;
|
|
567
|
+
.post-index-list li .pg {
|
|
568
|
+
font-family: var(--sans);
|
|
569
|
+
font-size: 10px;
|
|
570
|
+
letter-spacing: 0.1em;
|
|
571
|
+
text-transform: uppercase;
|
|
572
|
+
color: var(--accent);
|
|
573
|
+
font-weight: 700;
|
|
508
574
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
575
|
+
|
|
576
|
+
/* POST inside running header · running mast + page indicator */
|
|
577
|
+
.post-running {
|
|
578
|
+
display: grid;
|
|
579
|
+
grid-template-columns: auto 1fr auto;
|
|
580
|
+
gap: 18px;
|
|
581
|
+
align-items: center;
|
|
582
|
+
padding-bottom: 8px;
|
|
583
|
+
border-bottom: 2px solid var(--ink);
|
|
513
584
|
}
|
|
514
|
-
.
|
|
515
|
-
font-family: var(--
|
|
585
|
+
.post-running-mast {
|
|
586
|
+
font-family: var(--serif-display);
|
|
587
|
+
font-size: 18px;
|
|
588
|
+
font-weight: 800;
|
|
589
|
+
color: var(--ink);
|
|
590
|
+
letter-spacing: -0.005em;
|
|
591
|
+
}
|
|
592
|
+
.post-running-mast::before {
|
|
593
|
+
content: "";
|
|
594
|
+
display: inline-block;
|
|
595
|
+
width: 8px;
|
|
596
|
+
height: 8px;
|
|
597
|
+
background: var(--accent);
|
|
598
|
+
margin-right: 8px;
|
|
599
|
+
vertical-align: middle;
|
|
600
|
+
border-radius: 50%;
|
|
601
|
+
}
|
|
602
|
+
.post-running-meta {
|
|
603
|
+
text-align: center;
|
|
604
|
+
font-family: var(--sans);
|
|
516
605
|
font-size: 10.5px;
|
|
517
|
-
|
|
518
|
-
|
|
606
|
+
letter-spacing: 0.06em;
|
|
607
|
+
text-transform: uppercase;
|
|
608
|
+
color: var(--ink-mid);
|
|
519
609
|
}
|
|
520
|
-
.
|
|
521
|
-
font-family: var(--
|
|
610
|
+
.post-running-page {
|
|
611
|
+
font-family: var(--sans);
|
|
522
612
|
font-size: 11px;
|
|
613
|
+
letter-spacing: 0.14em;
|
|
614
|
+
text-transform: uppercase;
|
|
615
|
+
color: var(--accent);
|
|
616
|
+
font-weight: 800;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* Inside section banner */
|
|
620
|
+
.post-sect-banner {
|
|
621
|
+
display: flex;
|
|
622
|
+
align-items: center;
|
|
623
|
+
gap: 14px;
|
|
624
|
+
margin: 14px 0 12px;
|
|
625
|
+
}
|
|
626
|
+
.post-sect-banner-tag {
|
|
627
|
+
font-family: var(--sans);
|
|
628
|
+
font-size: 11px;
|
|
629
|
+
font-weight: 800;
|
|
630
|
+
letter-spacing: 0.18em;
|
|
631
|
+
text-transform: uppercase;
|
|
632
|
+
color: var(--paper);
|
|
633
|
+
background: var(--accent);
|
|
634
|
+
padding: 5px 12px;
|
|
635
|
+
}
|
|
636
|
+
.post-sect-banner-deck {
|
|
637
|
+
font-family: var(--serif);
|
|
523
638
|
font-style: italic;
|
|
639
|
+
font-size: 13px;
|
|
524
640
|
color: var(--ink-mid);
|
|
525
|
-
text-align: right;
|
|
526
|
-
margin-top: 4px;
|
|
527
|
-
letter-spacing: 0.02em;
|
|
528
|
-
}
|
|
529
|
-
.np-figcaption::before {
|
|
530
|
-
content: "Fig. ";
|
|
531
|
-
font-style: normal;
|
|
532
|
-
color: var(--ink);
|
|
533
|
-
font-weight: 700;
|
|
534
641
|
}
|
|
535
642
|
|
|
536
|
-
/*
|
|
537
|
-
.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
643
|
+
/* Inside article (POST) · headline + body */
|
|
644
|
+
.post-article {
|
|
645
|
+
display: grid;
|
|
646
|
+
grid-template-columns: minmax(0, 1.6fr) minmax(180px, 1fr);
|
|
647
|
+
gap: 28px;
|
|
648
|
+
margin-top: 6px;
|
|
649
|
+
}
|
|
650
|
+
@media (max-width: 720px) {
|
|
651
|
+
.post-article { grid-template-columns: 1fr; gap: 20px; }
|
|
544
652
|
}
|
|
545
|
-
.
|
|
653
|
+
.post-article-head {
|
|
654
|
+
font-family: var(--serif-display);
|
|
655
|
+
font-size: 32px;
|
|
656
|
+
font-weight: 800;
|
|
657
|
+
line-height: 1.08;
|
|
658
|
+
letter-spacing: -0.012em;
|
|
659
|
+
color: var(--ink);
|
|
660
|
+
margin-bottom: 8px;
|
|
661
|
+
}
|
|
662
|
+
.post-article-deck {
|
|
546
663
|
font-family: var(--serif);
|
|
547
|
-
font-
|
|
664
|
+
font-style: italic;
|
|
665
|
+
font-size: 14px;
|
|
548
666
|
line-height: 1.5;
|
|
549
|
-
color: var(--ink-
|
|
667
|
+
color: var(--ink-mid);
|
|
668
|
+
margin-bottom: 8px;
|
|
669
|
+
}
|
|
670
|
+
.post-article-byline {
|
|
671
|
+
font-family: var(--sans);
|
|
672
|
+
font-size: 10.5px;
|
|
673
|
+
letter-spacing: 0.08em;
|
|
674
|
+
text-transform: uppercase;
|
|
675
|
+
color: var(--ink-mid);
|
|
676
|
+
font-weight: 700;
|
|
550
677
|
padding-bottom: 10px;
|
|
551
678
|
border-bottom: 1px solid var(--rule);
|
|
679
|
+
margin-bottom: 12px;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/* Pull-quote inline (POST) · vertical red bar + italic */
|
|
683
|
+
.post-pull {
|
|
684
|
+
border-left: 4px solid var(--accent);
|
|
685
|
+
padding: 8px 18px;
|
|
686
|
+
margin: 14px 0;
|
|
552
687
|
}
|
|
553
|
-
.
|
|
554
|
-
.np-list li b {
|
|
688
|
+
.post-pull-text {
|
|
555
689
|
font-family: var(--serif-display);
|
|
556
|
-
font-
|
|
557
|
-
font-
|
|
690
|
+
font-style: italic;
|
|
691
|
+
font-size: 19px;
|
|
692
|
+
line-height: 1.35;
|
|
558
693
|
color: var(--ink);
|
|
559
|
-
|
|
560
|
-
letter-spacing: 0.02em;
|
|
561
|
-
margin-right: 4px;
|
|
694
|
+
letter-spacing: -0.005em;
|
|
562
695
|
}
|
|
563
|
-
.
|
|
564
|
-
|
|
565
|
-
|
|
696
|
+
.post-pull-cite {
|
|
697
|
+
font-family: var(--sans);
|
|
698
|
+
font-size: 10px;
|
|
699
|
+
letter-spacing: 0.14em;
|
|
700
|
+
text-transform: uppercase;
|
|
701
|
+
color: var(--accent);
|
|
702
|
+
font-weight: 700;
|
|
703
|
+
margin-top: 6px;
|
|
566
704
|
}
|
|
567
705
|
|
|
568
|
-
/*
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
706
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
707
|
+
TIMES variant · The Boardroom · New York Times register
|
|
708
|
+
═══════════════════════════════════════════════════════════════ */
|
|
709
|
+
|
|
710
|
+
.times-tagline {
|
|
711
|
+
font-family: var(--serif);
|
|
573
712
|
font-style: italic;
|
|
574
|
-
|
|
713
|
+
font-size: 11.5px;
|
|
714
|
+
color: var(--ink-mid);
|
|
715
|
+
text-align: center;
|
|
716
|
+
padding: 2px 0 6px;
|
|
717
|
+
}
|
|
718
|
+
.times-mast-name {
|
|
719
|
+
font-family: var(--blackletter);
|
|
720
|
+
font-weight: 400;
|
|
721
|
+
font-size: 72px;
|
|
722
|
+
line-height: 1;
|
|
723
|
+
letter-spacing: 0.005em;
|
|
575
724
|
color: var(--ink);
|
|
576
725
|
text-align: center;
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
726
|
+
margin: 4px 0 8px;
|
|
727
|
+
}
|
|
728
|
+
@media (max-width: 720px) {
|
|
729
|
+
.times-mast-name { font-size: 46px; }
|
|
581
730
|
}
|
|
582
731
|
|
|
583
|
-
/*
|
|
584
|
-
.
|
|
732
|
+
/* TIMES volume strip · "VOL. CXC...No 12,345 ★ Late Edition ★
|
|
733
|
+
{Date} ★ $4.00" */
|
|
734
|
+
.times-vol-strip {
|
|
585
735
|
display: flex;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
gap:
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
border-
|
|
736
|
+
align-items: center;
|
|
737
|
+
justify-content: center;
|
|
738
|
+
gap: 18px;
|
|
739
|
+
padding: 6px 0;
|
|
740
|
+
border-top: 2px solid var(--ink);
|
|
741
|
+
border-bottom: 1px solid var(--ink);
|
|
592
742
|
font-family: var(--serif);
|
|
593
|
-
font-size:
|
|
594
|
-
|
|
595
|
-
color: var(--ink-mid);
|
|
743
|
+
font-size: 11.5px;
|
|
744
|
+
color: var(--ink-soft);
|
|
596
745
|
flex-wrap: wrap;
|
|
597
746
|
}
|
|
598
|
-
.
|
|
599
|
-
|
|
600
|
-
font-size:
|
|
601
|
-
|
|
747
|
+
.times-vol-strip .star {
|
|
748
|
+
color: var(--ink);
|
|
749
|
+
font-size: 12px;
|
|
750
|
+
line-height: 1;
|
|
751
|
+
}
|
|
752
|
+
.times-vol-strip em {
|
|
753
|
+
font-style: italic;
|
|
602
754
|
color: var(--ink);
|
|
603
|
-
letter-spacing: 0.04em;
|
|
604
755
|
}
|
|
605
756
|
|
|
606
|
-
/*
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
757
|
+
/* TIMES front · 4-col grid (1+2+1 spans) · packs better than the
|
|
758
|
+
classical 6-col when content is brief-sized; the lead is the
|
|
759
|
+
visual center, sidebars stay narrow but full of content. */
|
|
760
|
+
.times-front {
|
|
761
|
+
display: grid;
|
|
762
|
+
grid-template-columns: repeat(4, 1fr);
|
|
763
|
+
gap: 22px;
|
|
764
|
+
margin-top: 14px;
|
|
765
|
+
align-items: start;
|
|
615
766
|
}
|
|
616
|
-
|
|
617
|
-
|
|
767
|
+
@media (max-width: 720px) {
|
|
768
|
+
.times-front { grid-template-columns: 1fr; }
|
|
769
|
+
}
|
|
770
|
+
.times-col {
|
|
771
|
+
display: flex;
|
|
772
|
+
flex-direction: column;
|
|
773
|
+
gap: 8px;
|
|
774
|
+
min-width: 0;
|
|
775
|
+
}
|
|
776
|
+
.times-col + .times-col {
|
|
777
|
+
padding-left: 22px;
|
|
778
|
+
border-left: 1px solid var(--rule);
|
|
779
|
+
}
|
|
780
|
+
@media (max-width: 720px) {
|
|
781
|
+
.times-col + .times-col {
|
|
782
|
+
padding-left: 0;
|
|
783
|
+
border-left: 0;
|
|
784
|
+
padding-top: 16px;
|
|
785
|
+
border-top: 1px solid var(--rule);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
.times-col-1 { grid-column: span 1; }
|
|
789
|
+
.times-col-2 { grid-column: span 1; }
|
|
790
|
+
.times-col-3 { grid-column: span 2; }
|
|
791
|
+
|
|
792
|
+
.times-section-eye {
|
|
793
|
+
font-family: var(--serif);
|
|
618
794
|
font-size: 10px;
|
|
619
|
-
letter-spacing: 0.
|
|
795
|
+
letter-spacing: 0.16em;
|
|
620
796
|
text-transform: uppercase;
|
|
621
|
-
color: var(--ink);
|
|
622
|
-
border: 1px solid var(--ink);
|
|
623
|
-
padding: 5px 14px;
|
|
624
|
-
display: inline-block;
|
|
625
|
-
margin-bottom: 16px;
|
|
797
|
+
color: var(--ink-mid);
|
|
626
798
|
font-weight: 700;
|
|
799
|
+
text-align: center;
|
|
800
|
+
padding-bottom: 6px;
|
|
801
|
+
border-bottom: 1px solid var(--ink);
|
|
802
|
+
margin-bottom: 8px;
|
|
627
803
|
}
|
|
628
|
-
.
|
|
804
|
+
.times-section-eye::before { content: "★ "; color: var(--ink); margin-right: 4px; }
|
|
805
|
+
.times-section-eye::after { content: " ★"; color: var(--ink); margin-left: 4px; }
|
|
806
|
+
|
|
807
|
+
.times-head-large {
|
|
808
|
+
font-family: var(--serif-display);
|
|
809
|
+
font-size: 34px;
|
|
810
|
+
font-weight: 700;
|
|
811
|
+
line-height: 1.05;
|
|
812
|
+
letter-spacing: -0.014em;
|
|
813
|
+
color: var(--ink);
|
|
814
|
+
text-align: center;
|
|
815
|
+
}
|
|
816
|
+
@media (max-width: 720px) {
|
|
817
|
+
.times-head-large { font-size: 26px; }
|
|
818
|
+
}
|
|
819
|
+
.times-head-medium {
|
|
629
820
|
font-family: var(--serif-display);
|
|
630
821
|
font-size: 24px;
|
|
631
822
|
font-weight: 700;
|
|
823
|
+
line-height: 1.12;
|
|
824
|
+
letter-spacing: -0.008em;
|
|
632
825
|
color: var(--ink);
|
|
633
|
-
|
|
634
|
-
|
|
826
|
+
}
|
|
827
|
+
.times-head-small {
|
|
828
|
+
font-family: var(--serif-display);
|
|
829
|
+
font-size: 17px;
|
|
830
|
+
font-weight: 700;
|
|
831
|
+
line-height: 1.18;
|
|
832
|
+
letter-spacing: -0.005em;
|
|
833
|
+
color: var(--ink);
|
|
834
|
+
}
|
|
835
|
+
.times-deck {
|
|
836
|
+
font-family: var(--serif);
|
|
837
|
+
font-style: italic;
|
|
838
|
+
font-size: 14.5px;
|
|
839
|
+
line-height: 1.4;
|
|
840
|
+
color: var(--ink-mid);
|
|
841
|
+
text-align: center;
|
|
842
|
+
margin: 6px 0 10px;
|
|
843
|
+
}
|
|
844
|
+
.times-byline {
|
|
845
|
+
font-family: var(--serif);
|
|
846
|
+
font-size: 11px;
|
|
847
|
+
letter-spacing: 0.12em;
|
|
635
848
|
text-transform: uppercase;
|
|
636
|
-
|
|
849
|
+
color: var(--ink-mid);
|
|
850
|
+
text-align: center;
|
|
851
|
+
margin-bottom: 8px;
|
|
637
852
|
}
|
|
638
|
-
.
|
|
853
|
+
.times-byline em {
|
|
854
|
+
font-style: italic;
|
|
855
|
+
color: var(--ink);
|
|
856
|
+
}
|
|
857
|
+
.times-body {
|
|
639
858
|
font-family: var(--serif);
|
|
640
|
-
font-size:
|
|
859
|
+
font-size: 12.5px;
|
|
860
|
+
line-height: 1.55;
|
|
641
861
|
color: var(--ink-soft);
|
|
642
|
-
|
|
862
|
+
text-align: justify;
|
|
863
|
+
hyphens: auto;
|
|
864
|
+
-webkit-hyphens: auto;
|
|
643
865
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
above date-stack so the column never reads as short). The
|
|
654
|
-
`:first-child` column gets the vertical column rule so the rule
|
|
655
|
-
paints between the two regardless of which child sits first. */
|
|
656
|
-
.np-hero-band {
|
|
657
|
-
display: grid;
|
|
658
|
-
grid-template-columns: 2fr 1fr;
|
|
659
|
-
gap: 24px;
|
|
660
|
-
margin: 8px 0 12px;
|
|
661
|
-
align-items: stretch;
|
|
866
|
+
.times-body p + p { margin-top: 6px; text-indent: 1.2em; }
|
|
867
|
+
.times-body.has-drop::first-letter {
|
|
868
|
+
font-family: var(--serif-display);
|
|
869
|
+
font-size: 48px;
|
|
870
|
+
line-height: 0.85;
|
|
871
|
+
font-weight: 700;
|
|
872
|
+
color: var(--ink);
|
|
873
|
+
float: left;
|
|
874
|
+
margin: 4px 6px 0 0;
|
|
662
875
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
876
|
+
|
|
877
|
+
/* TIMES standfirst · solid block with serif italic quote · used
|
|
878
|
+
in the centered hero column */
|
|
879
|
+
.times-stand {
|
|
880
|
+
background: var(--ink);
|
|
881
|
+
color: var(--paper);
|
|
882
|
+
padding: 16px 20px;
|
|
883
|
+
margin: 10px 0;
|
|
884
|
+
text-align: center;
|
|
666
885
|
}
|
|
667
|
-
.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
886
|
+
.times-stand-mark {
|
|
887
|
+
font-family: var(--blackletter);
|
|
888
|
+
font-size: 32px;
|
|
889
|
+
color: var(--paper);
|
|
890
|
+
line-height: 1;
|
|
891
|
+
margin-bottom: 8px;
|
|
671
892
|
}
|
|
672
|
-
.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
893
|
+
.times-stand-text {
|
|
894
|
+
font-family: var(--serif-display);
|
|
895
|
+
font-style: italic;
|
|
896
|
+
font-size: 18px;
|
|
897
|
+
line-height: 1.35;
|
|
898
|
+
color: var(--paper);
|
|
899
|
+
letter-spacing: -0.005em;
|
|
676
900
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
901
|
+
.times-stand-cite {
|
|
902
|
+
font-family: var(--serif);
|
|
903
|
+
font-size: 10.5px;
|
|
904
|
+
letter-spacing: 0.14em;
|
|
905
|
+
text-transform: uppercase;
|
|
906
|
+
color: var(--inv-ink-soft);
|
|
907
|
+
margin-top: 10px;
|
|
685
908
|
}
|
|
686
909
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
910
|
+
/* TIMES stat tile · serif numeric on cream · narrow sidebar
|
|
911
|
+
constraint forces 30px max for the numeric so 5-char strings
|
|
912
|
+
("$120M" / "2,000") fit cleanly without breaking. */
|
|
913
|
+
.times-stat {
|
|
914
|
+
background: var(--paper-soft);
|
|
915
|
+
padding: 14px 14px;
|
|
916
|
+
border: 1px solid var(--ink);
|
|
917
|
+
text-align: center;
|
|
918
|
+
margin: 8px 0;
|
|
692
919
|
}
|
|
693
|
-
.
|
|
694
|
-
|
|
695
|
-
|
|
920
|
+
.times-stat-eye {
|
|
921
|
+
font-family: var(--serif);
|
|
922
|
+
font-size: 10px;
|
|
923
|
+
letter-spacing: 0.16em;
|
|
924
|
+
text-transform: uppercase;
|
|
925
|
+
color: var(--ink-mid);
|
|
926
|
+
font-weight: 700;
|
|
927
|
+
padding-bottom: 6px;
|
|
928
|
+
border-bottom: 1px solid var(--rule);
|
|
929
|
+
margin-bottom: 8px;
|
|
696
930
|
}
|
|
697
|
-
.
|
|
698
|
-
|
|
699
|
-
|
|
931
|
+
.times-stat-eye::before { content: "★ "; color: var(--ink); }
|
|
932
|
+
.times-stat-eye::after { content: " ★"; color: var(--ink); }
|
|
933
|
+
.times-stat-num {
|
|
934
|
+
font-family: var(--serif-display);
|
|
935
|
+
font-size: 30px;
|
|
936
|
+
font-weight: 700;
|
|
937
|
+
line-height: 1.0;
|
|
938
|
+
letter-spacing: -0.018em;
|
|
939
|
+
color: var(--ink);
|
|
940
|
+
word-break: break-word;
|
|
941
|
+
overflow-wrap: anywhere;
|
|
700
942
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
.np-grid-4 > .np-col:last-child { border-bottom: 0; padding-bottom: 0; }
|
|
943
|
+
.times-stat-cap {
|
|
944
|
+
font-family: var(--serif);
|
|
945
|
+
font-style: italic;
|
|
946
|
+
font-size: 12px;
|
|
947
|
+
line-height: 1.4;
|
|
948
|
+
color: var(--ink-mid);
|
|
949
|
+
margin-top: 6px;
|
|
710
950
|
}
|
|
711
951
|
|
|
712
|
-
/*
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
952
|
+
/* TIMES inside running header · plain minimal */
|
|
953
|
+
.times-running {
|
|
954
|
+
display: grid;
|
|
955
|
+
grid-template-columns: auto 1fr auto;
|
|
956
|
+
align-items: center;
|
|
957
|
+
gap: 16px;
|
|
958
|
+
padding-bottom: 8px;
|
|
959
|
+
border-bottom: 1px solid var(--ink);
|
|
960
|
+
font-family: var(--serif);
|
|
961
|
+
font-size: 11px;
|
|
962
|
+
color: var(--ink);
|
|
963
|
+
}
|
|
964
|
+
.times-running-l {
|
|
716
965
|
font-family: var(--serif-display);
|
|
717
|
-
font-size:
|
|
966
|
+
font-size: 14px;
|
|
967
|
+
font-weight: 700;
|
|
968
|
+
letter-spacing: 0.005em;
|
|
969
|
+
}
|
|
970
|
+
.times-running-c {
|
|
971
|
+
text-align: center;
|
|
718
972
|
font-style: italic;
|
|
719
|
-
|
|
973
|
+
color: var(--ink-mid);
|
|
974
|
+
}
|
|
975
|
+
.times-running-r {
|
|
976
|
+
font-family: var(--serif);
|
|
977
|
+
font-size: 11px;
|
|
978
|
+
letter-spacing: 0.16em;
|
|
979
|
+
text-transform: uppercase;
|
|
720
980
|
color: var(--ink);
|
|
721
|
-
padding: 16px 36px;
|
|
722
|
-
letter-spacing: -0.005em;
|
|
723
|
-
margin: 10px auto;
|
|
724
|
-
max-width: 720px;
|
|
725
981
|
}
|
|
726
982
|
|
|
727
|
-
/*
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
grid-template-columns: 1fr 1fr;
|
|
732
|
-
gap: 22px;
|
|
733
|
-
margin: 12px 0;
|
|
983
|
+
/* TIMES inside section banner · centered with rules + caps */
|
|
984
|
+
.times-section-banner {
|
|
985
|
+
text-align: center;
|
|
986
|
+
margin: 18px 0 14px;
|
|
734
987
|
}
|
|
735
|
-
.
|
|
736
|
-
|
|
737
|
-
|
|
988
|
+
.times-section-banner-rule {
|
|
989
|
+
height: 1px;
|
|
990
|
+
background: var(--ink);
|
|
991
|
+
margin: 6px 0;
|
|
738
992
|
}
|
|
739
|
-
.
|
|
740
|
-
|
|
741
|
-
|
|
993
|
+
.times-section-banner-name {
|
|
994
|
+
font-family: var(--serif-display);
|
|
995
|
+
font-size: 22px;
|
|
996
|
+
font-weight: 700;
|
|
997
|
+
letter-spacing: 0.16em;
|
|
998
|
+
text-transform: uppercase;
|
|
999
|
+
color: var(--ink);
|
|
1000
|
+
padding: 0 4px;
|
|
742
1001
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
padding-bottom: 18px;
|
|
750
|
-
}
|
|
751
|
-
.np-grid-2 > .np-col:last-child { border-bottom: 0; padding-bottom: 0; }
|
|
1002
|
+
.times-section-banner-deck {
|
|
1003
|
+
font-family: var(--serif);
|
|
1004
|
+
font-style: italic;
|
|
1005
|
+
font-size: 12.5px;
|
|
1006
|
+
color: var(--ink-mid);
|
|
1007
|
+
margin-top: 4px;
|
|
752
1008
|
}
|
|
753
1009
|
|
|
754
|
-
/*
|
|
755
|
-
|
|
756
|
-
while the main column carries the lead + secondary stories. */
|
|
757
|
-
.np-hero-asym {
|
|
1010
|
+
/* TIMES inside · 4-col article spread */
|
|
1011
|
+
.times-inside {
|
|
758
1012
|
display: grid;
|
|
759
|
-
grid-template-columns:
|
|
1013
|
+
grid-template-columns: minmax(0, 2.6fr) minmax(180px, 1fr);
|
|
760
1014
|
gap: 28px;
|
|
761
|
-
margin:
|
|
762
|
-
align-items: start;
|
|
1015
|
+
margin-top: 4px;
|
|
763
1016
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
gap:
|
|
1017
|
+
@media (max-width: 720px) {
|
|
1018
|
+
.times-inside { grid-template-columns: 1fr; }
|
|
1019
|
+
}
|
|
1020
|
+
.times-inside-main {
|
|
1021
|
+
column-count: 2;
|
|
1022
|
+
column-gap: 22px;
|
|
1023
|
+
column-rule: 1px solid var(--rule);
|
|
1024
|
+
font-family: var(--serif);
|
|
1025
|
+
font-size: 12.5px;
|
|
1026
|
+
line-height: 1.6;
|
|
1027
|
+
color: var(--ink-soft);
|
|
1028
|
+
text-align: justify;
|
|
1029
|
+
hyphens: auto;
|
|
1030
|
+
-webkit-hyphens: auto;
|
|
1031
|
+
}
|
|
1032
|
+
@media (max-width: 540px) {
|
|
1033
|
+
.times-inside-main { column-count: 1; }
|
|
1034
|
+
}
|
|
1035
|
+
.times-inside-main p + p { margin-top: 6px; text-indent: 1.2em; }
|
|
1036
|
+
.times-inside-main.has-drop::first-letter {
|
|
1037
|
+
font-family: var(--serif-display);
|
|
1038
|
+
font-size: 52px;
|
|
1039
|
+
line-height: 0.85;
|
|
1040
|
+
font-weight: 700;
|
|
1041
|
+
color: var(--ink);
|
|
1042
|
+
float: left;
|
|
1043
|
+
margin: 4px 6px 0 0;
|
|
770
1044
|
}
|
|
771
|
-
.
|
|
1045
|
+
.times-inside-side {
|
|
772
1046
|
display: flex;
|
|
773
1047
|
flex-direction: column;
|
|
774
1048
|
gap: 14px;
|
|
1049
|
+
padding-left: 22px;
|
|
1050
|
+
border-left: 1px solid var(--rule);
|
|
775
1051
|
}
|
|
776
1052
|
@media (max-width: 720px) {
|
|
777
|
-
.
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1053
|
+
.times-inside-side { border-left: 0; padding-left: 0; padding-top: 18px; border-top: 1px solid var(--rule); }
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/* TIMES "Continued from page A1" tag */
|
|
1057
|
+
.times-continued {
|
|
1058
|
+
font-family: var(--serif);
|
|
1059
|
+
font-style: italic;
|
|
1060
|
+
font-size: 11px;
|
|
1061
|
+
color: var(--accent);
|
|
1062
|
+
margin-bottom: 8px;
|
|
1063
|
+
}
|
|
1064
|
+
.times-continued::before { content: "→ "; font-style: normal; color: var(--ink); }
|
|
1065
|
+
|
|
1066
|
+
/* TIMES pull-quote inline · star ornaments */
|
|
1067
|
+
.times-pull {
|
|
1068
|
+
text-align: center;
|
|
1069
|
+
margin: 14px 0;
|
|
1070
|
+
padding: 10px 0;
|
|
1071
|
+
border-top: 1px solid var(--ink);
|
|
1072
|
+
border-bottom: 1px solid var(--ink);
|
|
784
1073
|
}
|
|
785
|
-
|
|
786
|
-
/* Framed pull-quote · used by layout 3 · double-rule top + bottom
|
|
787
|
-
for an antique broadsheet pull-quote feel. */
|
|
788
|
-
.np-pull-framed {
|
|
789
|
-
text-align: center;
|
|
1074
|
+
.times-pull-text {
|
|
790
1075
|
font-family: var(--serif-display);
|
|
791
|
-
font-size: 22px;
|
|
792
1076
|
font-style: italic;
|
|
1077
|
+
font-size: 19px;
|
|
793
1078
|
line-height: 1.4;
|
|
794
1079
|
color: var(--ink);
|
|
795
|
-
padding: 24px 36px;
|
|
796
|
-
border-top: 2px double var(--ink);
|
|
797
|
-
border-bottom: 2px double var(--ink);
|
|
798
|
-
margin: 12px auto;
|
|
799
|
-
max-width: 760px;
|
|
800
1080
|
letter-spacing: -0.005em;
|
|
801
1081
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
margin:
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
812
|
-
VARIANT 2 · Modern monochrome (Le Monde / Berliner register)
|
|
813
|
-
Cooler off-white paper, near-black ink, deep red spot accent.
|
|
814
|
-
Masthead nameplate flips to a CONDENSED SANS for the visual
|
|
815
|
-
identity shift; the body stays serif for editorial readability.
|
|
816
|
-
The dark callout fills become solid black with a red kicker.
|
|
817
|
-
═════════════════════════════════════════════════════════════════ */
|
|
818
|
-
body[data-np-variant="2"] {
|
|
819
|
-
--bg: #ECE7DA;
|
|
820
|
-
--bg-radial: rgba(20, 20, 15, 0.04);
|
|
821
|
-
--paper: #F8F4EA;
|
|
822
|
-
--paper-soft: #F2EDE0;
|
|
823
|
-
--paper-edge: #E8E2D2;
|
|
824
|
-
--ink: #14140F;
|
|
825
|
-
--ink-soft: #2C2A24;
|
|
826
|
-
--ink-mid: #5A574F;
|
|
827
|
-
--ink-faint: #8A867D;
|
|
828
|
-
--ink-muted: #B0ABA0;
|
|
829
|
-
--inv-bg: #14140F;
|
|
830
|
-
--inv-bg-soft: #1F1E18;
|
|
831
|
-
--inv-ink: #FFFFFF;
|
|
832
|
-
--inv-ink-soft:#C4C0B6;
|
|
833
|
-
--rule: #B0ABA0;
|
|
834
|
-
--rule-soft: #C8C4B8;
|
|
835
|
-
--rule-strong: #75716A;
|
|
836
|
-
--accent: #B12B2B;
|
|
837
|
-
--top-bar-bg: rgba(236, 231, 218, 0.92);
|
|
838
|
-
}
|
|
839
|
-
body[data-np-variant="2"] .np-mast-name {
|
|
840
|
-
font-family: "Inter", "Helvetica Neue", -apple-system,
|
|
841
|
-
"PingFang SC", "Hiragino Sans GB", sans-serif;
|
|
842
|
-
font-weight: 900;
|
|
843
|
-
font-stretch: 80%;
|
|
844
|
-
letter-spacing: -0.04em;
|
|
845
|
-
font-size: 70px;
|
|
846
|
-
}
|
|
847
|
-
@media (max-width: 720px) {
|
|
848
|
-
body[data-np-variant="2"] .np-mast-name { font-size: 46px; }
|
|
849
|
-
}
|
|
850
|
-
body[data-np-variant="2"] .np-frontpage-title { letter-spacing: -0.005em; }
|
|
851
|
-
body[data-np-variant="2"] .np-rule-double {
|
|
852
|
-
height: 2px;
|
|
853
|
-
background: var(--ink);
|
|
854
|
-
}
|
|
855
|
-
body[data-np-variant="2"] .np-callout-label {
|
|
856
|
-
color: var(--accent);
|
|
857
|
-
letter-spacing: 0.22em;
|
|
858
|
-
}
|
|
859
|
-
body[data-np-variant="2"] .np-callout-date .np-date-day {
|
|
860
|
-
color: var(--accent);
|
|
1082
|
+
.times-pull-cite {
|
|
1083
|
+
font-family: var(--serif);
|
|
1084
|
+
font-size: 10.5px;
|
|
1085
|
+
letter-spacing: 0.16em;
|
|
1086
|
+
text-transform: uppercase;
|
|
1087
|
+
color: var(--ink-mid);
|
|
1088
|
+
margin-top: 6px;
|
|
861
1089
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1090
|
+
.times-pull-cite::before { content: "★ "; color: var(--accent); }
|
|
1091
|
+
.times-pull-cite::after { content: " ★"; color: var(--accent); }
|
|
1092
|
+
|
|
1093
|
+
/* TIMES sidebar · "What's inside" + verification list */
|
|
1094
|
+
.times-side-flag {
|
|
1095
|
+
font-family: var(--serif-display);
|
|
1096
|
+
font-size: 14px;
|
|
1097
|
+
font-weight: 700;
|
|
1098
|
+
letter-spacing: 0.04em;
|
|
1099
|
+
color: var(--ink);
|
|
1100
|
+
text-align: center;
|
|
1101
|
+
padding: 4px 0;
|
|
1102
|
+
border-top: 2px solid var(--ink);
|
|
1103
|
+
border-bottom: 1px solid var(--ink);
|
|
866
1104
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
#F8F4EA 4px,
|
|
874
|
-
#F8F4EA 8px
|
|
875
|
-
);
|
|
1105
|
+
.times-side-list {
|
|
1106
|
+
list-style: none;
|
|
1107
|
+
margin: 0;
|
|
1108
|
+
padding: 0;
|
|
1109
|
+
display: flex;
|
|
1110
|
+
flex-direction: column;
|
|
876
1111
|
}
|
|
877
|
-
|
|
878
|
-
|
|
1112
|
+
.times-side-list li {
|
|
1113
|
+
padding: 10px 0;
|
|
1114
|
+
border-bottom: 1px solid var(--rule);
|
|
1115
|
+
font-family: var(--serif);
|
|
1116
|
+
font-size: 12px;
|
|
1117
|
+
line-height: 1.55;
|
|
1118
|
+
color: var(--ink-soft);
|
|
879
1119
|
}
|
|
880
|
-
|
|
881
|
-
|
|
1120
|
+
.times-side-list li:last-child { border-bottom: 0; }
|
|
1121
|
+
.times-side-list li b {
|
|
1122
|
+
display: block;
|
|
1123
|
+
font-family: var(--serif-display);
|
|
1124
|
+
font-size: 13.5px;
|
|
1125
|
+
font-weight: 700;
|
|
1126
|
+
color: var(--ink);
|
|
1127
|
+
letter-spacing: -0.004em;
|
|
1128
|
+
margin-bottom: 4px;
|
|
882
1129
|
}
|
|
883
1130
|
|
|
884
|
-
/*
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
--ink-faint
|
|
901
|
-
--ink-muted: #B89D7A;
|
|
902
|
-
--inv-bg: #3D2818;
|
|
903
|
-
--inv-bg-soft: #4F3622;
|
|
904
|
-
--inv-ink: #F2DBB4;
|
|
905
|
-
--inv-ink-soft:#C7AC7E;
|
|
906
|
-
--rule: #A88D5F;
|
|
907
|
-
--rule-soft: #BFA677;
|
|
908
|
-
--rule-strong: #6B4A1F;
|
|
909
|
-
--accent: #B5722E;
|
|
910
|
-
--top-bar-bg: rgba(201, 178, 134, 0.92);
|
|
911
|
-
}
|
|
912
|
-
body[data-np-variant="3"] .np-mast-name {
|
|
913
|
-
font-family: "Didot", "Bodoni 72", "Bodoni Moda",
|
|
914
|
-
"Tiempos Headline", "Playfair Display", Georgia, serif;
|
|
915
|
-
font-weight: 900;
|
|
916
|
-
font-style: italic;
|
|
917
|
-
letter-spacing: 0.02em;
|
|
918
|
-
font-size: 60px;
|
|
919
|
-
}
|
|
920
|
-
@media (max-width: 720px) {
|
|
921
|
-
body[data-np-variant="3"] .np-mast-name { font-size: 42px; }
|
|
1131
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
1132
|
+
Shared · page footer + states + print
|
|
1133
|
+
═══════════════════════════════════════════════════════════════ */
|
|
1134
|
+
|
|
1135
|
+
.np-page-foot {
|
|
1136
|
+
display: grid;
|
|
1137
|
+
grid-template-columns: 1fr auto 1fr;
|
|
1138
|
+
align-items: baseline;
|
|
1139
|
+
gap: 14px;
|
|
1140
|
+
padding-top: 14px;
|
|
1141
|
+
margin-top: 18px;
|
|
1142
|
+
border-top: 1px solid var(--rule);
|
|
1143
|
+
font-family: var(--mono);
|
|
1144
|
+
font-size: 10px;
|
|
1145
|
+
letter-spacing: 0.08em;
|
|
1146
|
+
text-transform: uppercase;
|
|
1147
|
+
color: var(--ink-faint);
|
|
922
1148
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1149
|
+
.np-page-foot-left { text-align: left; }
|
|
1150
|
+
.np-page-foot-mid { text-align: center; color: var(--ink-mid); font-weight: 700; }
|
|
1151
|
+
.np-page-foot-right { text-align: right; font-style: italic; text-transform: none; }
|
|
1152
|
+
|
|
1153
|
+
.np-state {
|
|
1154
|
+
max-width: 560px;
|
|
1155
|
+
margin: 80px auto;
|
|
1156
|
+
padding: 48px 36px;
|
|
929
1157
|
text-align: center;
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
font-size: 14px;
|
|
933
|
-
letter-spacing: 0.6em;
|
|
934
|
-
line-height: 1;
|
|
935
|
-
}
|
|
936
|
-
body[data-np-variant="3"] .np-rule-double::before {
|
|
937
|
-
content: "◆ ◆ ◆";
|
|
938
|
-
}
|
|
939
|
-
body[data-np-variant="3"] .np-callout {
|
|
940
|
-
border: 2px double var(--accent);
|
|
941
|
-
}
|
|
942
|
-
body[data-np-variant="3"] .np-callout-label,
|
|
943
|
-
body[data-np-variant="3"] .np-callout-date .np-date-day {
|
|
944
|
-
color: var(--accent);
|
|
945
|
-
}
|
|
946
|
-
body[data-np-variant="3"] .np-conclusion-mark::before {
|
|
947
|
-
color: var(--accent);
|
|
1158
|
+
background: var(--paper);
|
|
1159
|
+
box-shadow: var(--shadow-page);
|
|
948
1160
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1161
|
+
.np-state-mark {
|
|
1162
|
+
font-family: var(--mono);
|
|
1163
|
+
font-size: 10px;
|
|
1164
|
+
letter-spacing: 0.18em;
|
|
1165
|
+
text-transform: uppercase;
|
|
1166
|
+
color: var(--ink);
|
|
1167
|
+
border: 1px solid var(--ink);
|
|
1168
|
+
padding: 5px 14px;
|
|
1169
|
+
display: inline-block;
|
|
1170
|
+
margin-bottom: 16px;
|
|
1171
|
+
font-weight: 700;
|
|
955
1172
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
font-
|
|
960
|
-
|
|
1173
|
+
.np-state-title {
|
|
1174
|
+
font-family: var(--serif-display);
|
|
1175
|
+
font-size: 24px;
|
|
1176
|
+
font-weight: 700;
|
|
1177
|
+
color: var(--ink);
|
|
1178
|
+
margin-bottom: 10px;
|
|
1179
|
+
line-height: 1.2;
|
|
1180
|
+
text-transform: uppercase;
|
|
1181
|
+
letter-spacing: 0.02em;
|
|
961
1182
|
}
|
|
962
|
-
|
|
963
|
-
font-
|
|
1183
|
+
.np-state-body {
|
|
1184
|
+
font-family: var(--serif);
|
|
1185
|
+
font-size: 13.5px;
|
|
1186
|
+
color: var(--ink-soft);
|
|
1187
|
+
line-height: 1.6;
|
|
964
1188
|
}
|
|
965
1189
|
|
|
966
|
-
/* ─── Print · drop chrome ───────────────────────────────────── */
|
|
967
1190
|
@media print {
|
|
968
1191
|
.np-top-bar { display: none; }
|
|
969
1192
|
body, html { background: white; }
|
|
970
|
-
.np-doc {
|
|
971
|
-
|
|
972
|
-
margin: 0;
|
|
1193
|
+
.np-doc { max-width: none; padding: 0; margin: 0; }
|
|
1194
|
+
.np-page {
|
|
973
1195
|
box-shadow: none;
|
|
1196
|
+
margin: 0;
|
|
1197
|
+
padding: 16mm 18mm 18mm;
|
|
1198
|
+
page-break-after: always;
|
|
974
1199
|
}
|
|
975
|
-
.np-
|
|
976
|
-
.np-hero-band > .np-hero-figure,
|
|
977
|
-
.np-hero-asym > .np-hero-main,
|
|
978
|
-
.np-pull-band, .np-pull-framed {
|
|
979
|
-
break-inside: avoid;
|
|
980
|
-
page-break-inside: avoid;
|
|
981
|
-
}
|
|
1200
|
+
.np-page:last-child { page-break-after: auto; }
|
|
982
1201
|
}
|
|
983
1202
|
</style>
|
|
984
1203
|
</head>
|
|
@@ -987,6 +1206,11 @@
|
|
|
987
1206
|
<header class="np-top-bar" data-np-chrome>
|
|
988
1207
|
<a href="/" class="np-crumb">PrivateBoard <span class="np-crumb-accent">· newspaper</span></a>
|
|
989
1208
|
<div class="np-actions">
|
|
1209
|
+
<span class="np-variant-badge" data-np-variant-badge>Loading…</span>
|
|
1210
|
+
<nav class="np-page-nav" aria-label="Pages">
|
|
1211
|
+
<a href="#np-page-1">P. 1</a>
|
|
1212
|
+
<a href="#np-page-2">P. 2</a>
|
|
1213
|
+
</nav>
|
|
990
1214
|
<button type="button" class="np-btn" data-np-png>
|
|
991
1215
|
<span class="glyph">↓</span>PNG
|
|
992
1216
|
</button>
|
|
@@ -1006,61 +1230,24 @@
|
|
|
1006
1230
|
|
|
1007
1231
|
<script>
|
|
1008
1232
|
/* ──────────────────────────────────────────────────────────────────
|
|
1009
|
-
Newspaper renderer ·
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
continuation prose
|
|
1021
|
-
· RIGHT: figure (rankedBars chart) + heading + body
|
|
1022
|
-
rankedBars → figure card in the right column's "image slot"
|
|
1023
|
-
verification → "MORE HEADINGS" stacked sidebar in bottom-left
|
|
1024
|
-
talkingPoints → bottom-middle / bottom-right editorial paragraphs
|
|
1025
|
-
(with one card promoted to a centered pull-quote)
|
|
1026
|
-
conclusion → BOTTOM-LINE inverted callout in the LEFT column
|
|
1027
|
-
flow → bottom-right "FROM HERE" closing prose
|
|
1028
|
-
|
|
1029
|
-
The renderer is defensive — it skips any slot that's missing/empty
|
|
1030
|
-
instead of leaving placeholder boxes, so partial data still
|
|
1031
|
-
produces a clean page.
|
|
1233
|
+
Newspaper renderer · two distinct templates picked deterministi-
|
|
1234
|
+
cally from the brief id:
|
|
1235
|
+
· POST · Washington-Post register (red accents · sans kickers ·
|
|
1236
|
+
display-serif nameplate · 3-col body grid)
|
|
1237
|
+
· TIMES · New York Times register (blackletter nameplate ·
|
|
1238
|
+
"All the news that's fit to print" tagline · navy accent ·
|
|
1239
|
+
star ornaments · 6-col grid front + 2-col inside)
|
|
1240
|
+
|
|
1241
|
+
Each variant renders 2 pages (front + inside) so the report
|
|
1242
|
+
reads as a paper, not a single sheet. The variant + page count
|
|
1243
|
+
are stable across refreshes (hash of brief id).
|
|
1032
1244
|
────────────────────────────────────────────────────────────── */
|
|
1033
1245
|
(function () {
|
|
1034
1246
|
const params = new URLSearchParams(location.search);
|
|
1035
1247
|
const briefId = (params.get("b") || "").trim();
|
|
1036
1248
|
const roomId = (params.get("r") || "").trim();
|
|
1037
1249
|
const root = document.querySelector("[data-np-root]");
|
|
1038
|
-
|
|
1039
|
-
/** Pick one of three newspaper variants deterministically from
|
|
1040
|
-
* the brief id, so a refresh always shows the same look for the
|
|
1041
|
-
* same brief. The `?v=1|2|3` URL parameter overrides the hash
|
|
1042
|
-
* for previewing / debugging.
|
|
1043
|
-
*
|
|
1044
|
-
* · 1 · Broadsheet · the default WSJ / FT register · cream paper
|
|
1045
|
-
* + deep brown ink + heavy serif nameplate
|
|
1046
|
-
* · 2 · Modern monochrome · Le Monde register · cooler off-white
|
|
1047
|
-
* + near-black + deep red accent · CONDENSED SANS nameplate
|
|
1048
|
-
* · 3 · Vintage sepia · penny-press register · aged sepia paper
|
|
1049
|
-
* + amber accent · italic Didot/Bodoni nameplate · ◆◆◆
|
|
1050
|
-
* ornamental dingbats replace the double-rule dividers
|
|
1051
|
-
*/
|
|
1052
|
-
function pickVariant(id) {
|
|
1053
|
-
const force = (params.get("v") || "").trim();
|
|
1054
|
-
if (force === "1" || force === "2" || force === "3") return parseInt(force, 10);
|
|
1055
|
-
const s = String(id || "");
|
|
1056
|
-
if (!s) return 1;
|
|
1057
|
-
let h = 0;
|
|
1058
|
-
for (let i = 0; i < s.length; i++) {
|
|
1059
|
-
h = ((h << 5) - h) + s.charCodeAt(i);
|
|
1060
|
-
h |= 0;
|
|
1061
|
-
}
|
|
1062
|
-
return (Math.abs(h) % 3) + 1;
|
|
1063
|
-
}
|
|
1250
|
+
const variantBadge = document.querySelector("[data-np-variant-badge]");
|
|
1064
1251
|
|
|
1065
1252
|
function escape(s) {
|
|
1066
1253
|
return String(s == null ? "" : s)
|
|
@@ -1082,29 +1269,54 @@
|
|
|
1082
1269
|
|
|
1083
1270
|
async function loadBrief() {
|
|
1084
1271
|
let url;
|
|
1085
|
-
if (briefId) {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
url = `/api/rooms/${encodeURIComponent(roomId)}/brief`;
|
|
1089
|
-
} else {
|
|
1090
|
-
showState("Missing query", "No brief specified",
|
|
1091
|
-
"Add ?b=<briefId> or ?r=<roomId> to the URL.");
|
|
1092
|
-
return null;
|
|
1093
|
-
}
|
|
1272
|
+
if (briefId) url = `/api/briefs/${encodeURIComponent(briefId)}`;
|
|
1273
|
+
else if (roomId) url = `/api/rooms/${encodeURIComponent(roomId)}/brief`;
|
|
1274
|
+
else { showState("Missing query", "No brief specified", "Add ?b=<briefId> or ?r=<roomId> to the URL."); return null; }
|
|
1094
1275
|
const res = await fetch(url);
|
|
1095
1276
|
if (!res.ok) {
|
|
1096
1277
|
const e = await res.json().catch(() => ({}));
|
|
1097
|
-
showState("Not found", "Brief not found",
|
|
1098
|
-
e.error || "The requested brief doesn't exist or is no longer available.");
|
|
1278
|
+
showState("Not found", "Brief not found", e.error || "The requested brief doesn't exist or is no longer available.");
|
|
1099
1279
|
return null;
|
|
1100
1280
|
}
|
|
1101
1281
|
return await res.json();
|
|
1102
1282
|
}
|
|
1103
1283
|
|
|
1104
|
-
/**
|
|
1105
|
-
*
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1284
|
+
/** Pick "post" or "times" deterministically from the brief id ·
|
|
1285
|
+
* same brief id always renders the same template so refreshes /
|
|
1286
|
+
* share-links / PNG exports stay stable. The `?v=post|times`
|
|
1287
|
+
* URL parameter forces a specific template (debug / preview). */
|
|
1288
|
+
function pickVariant(id) {
|
|
1289
|
+
const force = (params.get("v") || "").trim().toLowerCase();
|
|
1290
|
+
if (force === "post" || force === "times") return force;
|
|
1291
|
+
const s = String(id || "");
|
|
1292
|
+
if (!s) return "post";
|
|
1293
|
+
let h = 0;
|
|
1294
|
+
for (let i = 0; i < s.length; i++) {
|
|
1295
|
+
h = ((h << 5) - h) + s.charCodeAt(i);
|
|
1296
|
+
h |= 0;
|
|
1297
|
+
}
|
|
1298
|
+
return (Math.abs(h) % 2) === 0 ? "post" : "times";
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/** Format date for masthead · returns formatted strings used by
|
|
1302
|
+
* both variants but in slightly different shapes. */
|
|
1303
|
+
function formatDate(footerTag) {
|
|
1304
|
+
const months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
|
|
1305
|
+
const monthsShort = ["Jan.","Feb.","March","April","May","June","July","Aug.","Sept.","Oct.","Nov.","Dec."];
|
|
1306
|
+
const days = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
|
1307
|
+
const m = String(footerTag || "").match(/(\d{4})-(\d{2})-(\d{2})/);
|
|
1308
|
+
const target = m ? new Date(`${m[1]}-${m[2]}-${m[3]}T00:00:00`) : new Date();
|
|
1309
|
+
const d = isNaN(target.getTime()) ? new Date() : target;
|
|
1310
|
+
return {
|
|
1311
|
+
weekday: days[d.getDay()],
|
|
1312
|
+
weekdayShort: days[d.getDay()].toUpperCase().slice(0, 3),
|
|
1313
|
+
long: `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`,
|
|
1314
|
+
short: `${monthsShort[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`,
|
|
1315
|
+
year: String(d.getFullYear()),
|
|
1316
|
+
nytDate: `${days[d.getDay()]}, ${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`,
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1108
1320
|
function splitHeading(raw) {
|
|
1109
1321
|
const s = String(raw || "").trim();
|
|
1110
1322
|
if (!s) return { heading: "", body: "" };
|
|
@@ -1112,512 +1324,451 @@
|
|
|
1112
1324
|
for (const sep of seps) {
|
|
1113
1325
|
const idx = s.indexOf(sep);
|
|
1114
1326
|
if (idx > 0 && idx < 60) {
|
|
1115
|
-
return {
|
|
1116
|
-
heading: s.slice(0, idx).trim(),
|
|
1117
|
-
body: s.slice(idx + sep.length).trim(),
|
|
1118
|
-
};
|
|
1327
|
+
return { heading: s.slice(0, idx).trim(), body: s.slice(idx + sep.length).trim() };
|
|
1119
1328
|
}
|
|
1120
1329
|
}
|
|
1121
1330
|
return { heading: s, body: "" };
|
|
1122
1331
|
}
|
|
1123
1332
|
|
|
1124
|
-
/**
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1333
|
+
/** Pick a stat tile's numeric + caption from the brief data.
|
|
1334
|
+
* Strict: only returns a tile when a milestone has a SHORT
|
|
1335
|
+
* numeric-y callout (≤ 14 chars, the "$120M" / "10×" / "Q4"
|
|
1336
|
+
* shape). The previous version fell back to verification
|
|
1337
|
+
* headings as the "num" slot, which rendered long phrases
|
|
1338
|
+
* ("Compliance review remains the gating constraint") at
|
|
1339
|
+
* 38-48px display-serif in a 200px-wide column · severely
|
|
1340
|
+
* cramped. Skipping is honest. */
|
|
1341
|
+
function pickStat(m) {
|
|
1342
|
+
const ms = m.milestones || [];
|
|
1343
|
+
const milestoneWithCallout = ms.find((x) => x && x.callout && String(x.callout).trim().length > 0 && String(x.callout).trim().length <= 14);
|
|
1344
|
+
if (milestoneWithCallout) {
|
|
1345
|
+
return {
|
|
1346
|
+
num: String(milestoneWithCallout.callout).trim(),
|
|
1347
|
+
cap: milestoneWithCallout.title || milestoneWithCallout.period || "",
|
|
1348
|
+
};
|
|
1131
1349
|
}
|
|
1132
|
-
|
|
1133
|
-
const ratio = Math.max(0, Math.min(1, Number(e.ratio) || 0));
|
|
1134
|
-
const pct = (ratio * 100).toFixed(1);
|
|
1135
|
-
return `
|
|
1136
|
-
<div class="np-bar">
|
|
1137
|
-
<span>${escape(e.label || "")}</span>
|
|
1138
|
-
<span class="np-bar-value">${escape(e.value || "")}</span>
|
|
1139
|
-
<div class="np-bar-row"><span class="np-bar-fill" style="width: ${pct}%"></span></div>
|
|
1140
|
-
</div>`;
|
|
1141
|
-
}).join("");
|
|
1142
|
-
const caption = rankedBars.title || fallbackCaption || "Reading the room";
|
|
1143
|
-
return `
|
|
1144
|
-
<figure class="np-figure">
|
|
1145
|
-
<div class="np-figure-bars">${rows}</div>
|
|
1146
|
-
<figcaption class="np-figcaption">${escape(caption)}</figcaption>
|
|
1147
|
-
</figure>`;
|
|
1350
|
+
return null;
|
|
1148
1351
|
}
|
|
1149
1352
|
|
|
1150
|
-
function
|
|
1151
|
-
if (!conclusion) return "";
|
|
1353
|
+
function pageFoot(pageNum, totalPages, briefIdShort) {
|
|
1152
1354
|
return `
|
|
1153
|
-
<
|
|
1154
|
-
<
|
|
1155
|
-
<
|
|
1156
|
-
|
|
1355
|
+
<div class="np-page-foot">
|
|
1356
|
+
<span class="np-page-foot-left">${escape(briefIdShort)}</span>
|
|
1357
|
+
<span class="np-page-foot-mid">Page ${pageNum} of ${totalPages}</span>
|
|
1358
|
+
<span class="np-page-foot-right">privateboard.ai</span>
|
|
1359
|
+
</div>`;
|
|
1157
1360
|
}
|
|
1158
1361
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1362
|
+
/* ═════════════════════════════════════════════════════════════
|
|
1363
|
+
POST · The Boardroom Post · Washington Post register
|
|
1364
|
+
═════════════════════════════════════════════════════════════ */
|
|
1365
|
+
|
|
1366
|
+
function renderPostPage1(brief, parts) {
|
|
1367
|
+
const { m, ms0, ms1, ms2, dateInfo, briefIdShort, totalPages } = parts;
|
|
1368
|
+
const tpAll = (m.talkingPoints && Array.isArray(m.talkingPoints.bullets)) ? m.talkingPoints.bullets : [];
|
|
1369
|
+
const tpFirst = tpAll[0] || "";
|
|
1370
|
+
const tpSecond = tpAll[1] || "";
|
|
1371
|
+
const stat = pickStat(m);
|
|
1372
|
+
const indexItems = [
|
|
1373
|
+
{ name: ms1.title || ms1.period || "Inside the room", page: "A2" },
|
|
1374
|
+
{ name: ms2.title || ms2.period || "More from the desk", page: "A2" },
|
|
1375
|
+
];
|
|
1376
|
+
if (m.verification && m.verification.title) {
|
|
1377
|
+
indexItems.push({ name: m.verification.title, page: "A2" });
|
|
1172
1378
|
}
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1379
|
+
const indexHtml = indexItems.map((it) => `
|
|
1380
|
+
<li><b>${escape(it.name)}</b><span class="pg">Page ${escape(it.page)}</span></li>
|
|
1381
|
+
`).join("");
|
|
1382
|
+
|
|
1383
|
+
// Pack the body cell with extra content so it doesn't end
|
|
1384
|
+
// short relative to the sidebar (which has flag + head +
|
|
1385
|
+
// body + stat + pull = 4-5 stacked items). Mid-article
|
|
1386
|
+
// sub-headline + tail talking-point keeps body height in
|
|
1387
|
+
// reach of the sidebar.
|
|
1388
|
+
const bodySubHead = ms2.title || ms2.period || "";
|
|
1389
|
+
const bodySubBody = ms2.body || "";
|
|
1390
|
+
|
|
1177
1391
|
return `
|
|
1178
|
-
<
|
|
1179
|
-
|
|
1180
|
-
<div class="
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1392
|
+
<section class="np-page" id="np-page-1">
|
|
1393
|
+
<!-- Section dots row · WaPo nav strip above the mast -->
|
|
1394
|
+
<div class="post-sect-row">
|
|
1395
|
+
<span class="post-sect-item">Top Story</span>
|
|
1396
|
+
<span class="post-sect-item is-blue">Markets</span>
|
|
1397
|
+
<span class="post-sect-item is-gold">Opinion</span>
|
|
1398
|
+
<span class="post-sect-item is-teal">Inside</span>
|
|
1399
|
+
</div>
|
|
1186
1400
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
return `
|
|
1196
|
-
<h4 class="np-col-heading">${escape(p)}</h4>
|
|
1197
|
-
<h3 class="np-col-heading np-col-heading-large">${escape(t)}</h3>`;
|
|
1198
|
-
}
|
|
1199
|
-
return `<h4 class="np-col-heading np-col-heading-large">${escape(t || p)}</h4>`;
|
|
1200
|
-
}
|
|
1401
|
+
<div class="post-tagline">From the boardroom — insights, every issue.</div>
|
|
1402
|
+
<h1 class="post-mast-name">The Boardroom Post</h1>
|
|
1403
|
+
|
|
1404
|
+
<div class="post-meta">
|
|
1405
|
+
<span class="post-meta-l">${escape(dateInfo.weekday)}, ${escape(dateInfo.long)}</span>
|
|
1406
|
+
<span class="post-meta-c">privateboard.ai</span>
|
|
1407
|
+
<span class="post-meta-r">${escape(briefIdShort)}</span>
|
|
1408
|
+
</div>
|
|
1201
1409
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1410
|
+
<div class="post-hero">
|
|
1411
|
+
<span class="post-hero-flag">Top Story</span>
|
|
1412
|
+
<h2 class="post-hero-title">${escape(m.title || "")}</h2>
|
|
1413
|
+
${m.kicker ? `<div class="post-hero-deck">${escape(m.kicker)}</div>` : ""}
|
|
1414
|
+
<div class="post-hero-byline">By the chair · <em>From the boardroom</em></div>
|
|
1415
|
+
</div>
|
|
1416
|
+
|
|
1417
|
+
${m.conclusion ? `
|
|
1418
|
+
<aside class="post-stand">
|
|
1419
|
+
<div class="post-stand-mark">"</div>
|
|
1420
|
+
<div>
|
|
1421
|
+
<div class="post-stand-text">${escape(m.conclusion)}</div>
|
|
1422
|
+
<span class="post-stand-cite">— Bottom line · ${escape(m.title || "")}</span>
|
|
1423
|
+
</div>
|
|
1424
|
+
</aside>` : ""}
|
|
1425
|
+
|
|
1426
|
+
<div class="post-grid">
|
|
1427
|
+
<div>
|
|
1428
|
+
${ms0.body ? `
|
|
1429
|
+
<div class="post-body has-drop">
|
|
1430
|
+
<p>${escape(ms0.body)}</p>
|
|
1431
|
+
${ms1.body ? `<p>${escape(ms1.body)}</p>` : ""}
|
|
1432
|
+
</div>` : ""}
|
|
1433
|
+
${tpSecond ? `
|
|
1434
|
+
<div class="post-pull" style="margin: 14px 0 8px;">
|
|
1435
|
+
<div class="post-pull-text">"${escape(tpSecond)}"</div>
|
|
1436
|
+
<div class="post-pull-cite">Editorial · second take</div>
|
|
1437
|
+
</div>` : ""}
|
|
1438
|
+
${bodySubHead || bodySubBody ? `
|
|
1439
|
+
<div style="margin-top: 12px;">
|
|
1440
|
+
${bodySubHead ? `<h4 style="font-family: var(--serif-display); font-size: 18px; font-weight: 700; line-height: 1.2; color: var(--ink); margin-bottom: 6px; padding-top: 10px; border-top: 1px solid var(--rule);">${escape(bodySubHead)}</h4>` : ""}
|
|
1441
|
+
${bodySubBody ? `<p class="post-body" style="margin: 0;">${escape(bodySubBody)}</p>` : ""}
|
|
1442
|
+
</div>` : ""}
|
|
1443
|
+
</div>
|
|
1444
|
+
<aside class="post-side">
|
|
1445
|
+
<span class="post-side-flag">Breaking</span>
|
|
1446
|
+
<h3 class="post-side-head">${escape(ms1.title || ms1.period || "Late dispatches")}</h3>
|
|
1447
|
+
${ms1.body ? `<p class="post-side-body">${escape(ms1.body)}</p>` : ""}
|
|
1448
|
+
${stat ? `
|
|
1449
|
+
<div class="post-stat">
|
|
1450
|
+
<div class="post-stat-eye">By the numbers</div>
|
|
1451
|
+
<div class="post-stat-num">${escape(stat.num)}</div>
|
|
1452
|
+
<div class="post-stat-cap">${escape(stat.cap)}</div>
|
|
1453
|
+
</div>` : ""}
|
|
1454
|
+
${tpFirst ? `
|
|
1455
|
+
<div class="post-pull">
|
|
1456
|
+
<div class="post-pull-text">"${escape(tpFirst)}"</div>
|
|
1457
|
+
<div class="post-pull-cite">From the editorial</div>
|
|
1458
|
+
</div>` : ""}
|
|
1459
|
+
</aside>
|
|
1460
|
+
</div>
|
|
1461
|
+
|
|
1462
|
+
${indexHtml ? `
|
|
1463
|
+
<div class="post-index">
|
|
1464
|
+
<span class="post-index-flag">Inside this issue</span>
|
|
1465
|
+
<ul class="post-index-list">${indexHtml}</ul>
|
|
1466
|
+
</div>` : ""}
|
|
1467
|
+
|
|
1468
|
+
${pageFoot(1, totalPages, briefIdShort)}
|
|
1469
|
+
</section>`;
|
|
1208
1470
|
}
|
|
1209
1471
|
|
|
1210
|
-
function
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1472
|
+
function renderPostPage2(brief, parts) {
|
|
1473
|
+
const { m, ms1, ms2, dateInfo, briefIdShort, totalPages } = parts;
|
|
1474
|
+
const tpAll = (m.talkingPoints && Array.isArray(m.talkingPoints.bullets)) ? m.talkingPoints.bullets : [];
|
|
1475
|
+
const pullQuote = tpAll[1] || tpAll[0] || "";
|
|
1476
|
+
const verifs = (m.verification && Array.isArray(m.verification.bullets)) ? m.verification.bullets : [];
|
|
1477
|
+
const verifsHtml = verifs.slice(0, 4).map((b) => {
|
|
1215
1478
|
const parts = splitHeading(b);
|
|
1216
1479
|
const inner = parts.body
|
|
1217
1480
|
? `<b>${escape(parts.heading)}</b>${escape(parts.body)}`
|
|
1218
1481
|
: escape(parts.heading);
|
|
1219
1482
|
return `<li>${inner}</li>`;
|
|
1220
1483
|
}).join("");
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
const
|
|
1242
|
-
if (!tail) return "";
|
|
1243
|
-
return `<div class="np-prose">${tail}</div>`;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
function render(brief) {
|
|
1247
|
-
if (brief.mode !== "newspaper") {
|
|
1248
|
-
showState("Wrong mode", "This brief isn't a newspaper",
|
|
1249
|
-
"Open it in the matching viewer instead. Newspaper, magazine, bento, and research-note are separate output modes.");
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
const m = brief.bodyJson;
|
|
1253
|
-
if (!m || typeof m !== "object") {
|
|
1254
|
-
if (brief.isGenerating) {
|
|
1255
|
-
showState("Generating",
|
|
1256
|
-
"Newspaper is still being prepared",
|
|
1257
|
-
"The chair is currently composing the front page. Refresh in a few seconds.");
|
|
1258
|
-
} else {
|
|
1259
|
-
showState("Empty",
|
|
1260
|
-
"This brief has no newspaper data",
|
|
1261
|
-
"It may have failed to generate. Try regenerating from the room view.");
|
|
1262
|
-
}
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
if (m.title) document.title = `${m.title} · Newspaper`;
|
|
1267
|
-
|
|
1268
|
-
// Apply the variant attribute · drives the broadsheet / modern
|
|
1269
|
-
// / vintage skin via CSS. Setting on body so the page bg and
|
|
1270
|
-
// top-bar bg also retint per variant. Cleared / overwritten on
|
|
1271
|
-
// every render so re-renders within the same page (e.g. retry
|
|
1272
|
-
// path) pick up the right look.
|
|
1273
|
-
const variant = pickVariant(brief.id);
|
|
1274
|
-
document.body.setAttribute("data-np-variant", String(variant));
|
|
1275
|
-
|
|
1276
|
-
// Masthead flanks · use the source byline as the left flank's
|
|
1277
|
-
// "advertorial" decoration and a stable static line on the right
|
|
1278
|
-
// so the masthead reads as balanced regardless of brief length.
|
|
1279
|
-
const sourceTxt = m.source || "From the editor's desk";
|
|
1280
|
-
const leftFlankBody = m.kicker || sourceTxt;
|
|
1281
|
-
const flankLeft = `<b>${escape((sourceTxt || "Boardroom").toUpperCase())}</b>${escape(leftFlankBody.slice(0, 180))}`;
|
|
1282
|
-
const flankRight = `<b>EDITORIAL NOTE</b>An issue from the room · pour a coffee, take it column by column, and trust the numbers more than the slogans.`;
|
|
1283
|
-
|
|
1284
|
-
// Meta strip · prefer footerTag for the date format; fall back
|
|
1285
|
-
// to today.
|
|
1286
|
-
const today = new Date();
|
|
1287
|
-
const monthsShort = ["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"];
|
|
1288
|
-
const daysShort = ["SUN","MON","TUE","WED","THU","FRI","SAT"];
|
|
1289
|
-
const dateMatch = String(m.footerTag || "").match(/(\d{4})-(\d{2})-(\d{2})/);
|
|
1290
|
-
const dispDate = (() => {
|
|
1291
|
-
const d = dateMatch ? new Date(`${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}T00:00:00`) : today;
|
|
1292
|
-
const dd = String(d.getDate()).padStart(2, "0");
|
|
1293
|
-
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
1294
|
-
const yyyy = d.getFullYear();
|
|
1295
|
-
const wk = daysShort[d.getDay()];
|
|
1296
|
-
return `${dd}.${mm}.${yyyy} / ${wk}`;
|
|
1297
|
-
})();
|
|
1298
|
-
const briefIdShort = brief.id ? `#${String(brief.id).slice(0, 10).toUpperCase()}` : "";
|
|
1299
|
-
|
|
1300
|
-
const milestones = (m.milestones || []).slice(0, 3);
|
|
1301
|
-
const ms0 = milestones[0] || {};
|
|
1302
|
-
const ms1 = milestones[1] || {};
|
|
1303
|
-
const ms2 = milestones[2] || {};
|
|
1304
|
-
|
|
1305
|
-
const figure = renderFigure(m.rankedBars, ms2.title);
|
|
1306
|
-
const bottomLine = renderBottomLineCallout(m.conclusion);
|
|
1307
|
-
const dateStack = renderDateCallout(m.footerTag);
|
|
1308
|
-
const moreHeadings = renderBullets(m.verification);
|
|
1309
|
-
|
|
1310
|
-
// Bottom band · 3 cols · MORE HEADINGS / pull quote + tail / closing
|
|
1311
|
-
const flowText = (m.flow && Array.isArray(m.flow.nodes) && m.flow.nodes.length >= 2)
|
|
1312
|
-
? `<div class="np-prose">${escape(m.flow.nodes.join(" → "))}${m.flow.caption ? `<p style="font-style:italic;margin-top:6px;">${escape(m.flow.caption)}</p>` : ""}</div>`
|
|
1313
|
-
: "";
|
|
1314
|
-
|
|
1315
|
-
const parts = {
|
|
1316
|
-
m, ms0, ms1, ms2,
|
|
1317
|
-
flankLeft, flankRight, dispDate, briefIdShort,
|
|
1318
|
-
bottomLine, dateStack, figure, moreHeadings, flowText,
|
|
1319
|
-
};
|
|
1320
|
-
|
|
1321
|
-
// Dispatch to the layout matching the picked variant. Each
|
|
1322
|
-
// layout owns its full <article> tree · they share helpers
|
|
1323
|
-
// (renderProse, renderFigure, renderColumnHeading, etc.) but
|
|
1324
|
-
// arrange the blocks differently.
|
|
1325
|
-
let html;
|
|
1326
|
-
if (variant === 2) html = layoutVariant2(parts);
|
|
1327
|
-
else if (variant === 3) html = layoutVariant3(parts);
|
|
1328
|
-
else html = layoutVariant1(parts);
|
|
1329
|
-
root.innerHTML = html;
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/* ─────────────────────── Layout dispatch ───────────────────────
|
|
1333
|
-
* Three structurally distinct broadsheet layouts. Each takes the
|
|
1334
|
-
* pre-rendered field parts and composes them into its own <article>
|
|
1335
|
-
* tree. Variant 1 keeps the symmetric 3+3 broadsheet feel · variant
|
|
1336
|
-
* 2 leans modular with a 2:1 hero band, 4-narrow-column body, and
|
|
1337
|
-
* a centered pull-quote band · variant 3 goes asymmetric (2/3 main
|
|
1338
|
-
* + 1/3 sidebar), framed pull-quote, 2-col tail, and a centered
|
|
1339
|
-
* closing figure.
|
|
1340
|
-
* ─────────────────────────────────────────────────────────────── */
|
|
1341
|
-
|
|
1342
|
-
/** Layout 1 · classic 3+3 broadsheet (WSJ / FT register).
|
|
1343
|
-
* TOP grid · heading+callout+body / heading+lead-drop+date / figure+heading+body
|
|
1344
|
-
* BOTTOM grid · MORE HEADINGS / pull-quote + tail / closing prose */
|
|
1345
|
-
function layoutVariant1(p) {
|
|
1346
|
-
const { m, ms0, ms1, ms2, flankLeft, flankRight, dispDate, briefIdShort,
|
|
1347
|
-
bottomLine, dateStack, figure, moreHeadings, flowText } = p;
|
|
1348
|
-
const middlePull = renderTalkingMiddle(m.talkingPoints);
|
|
1349
|
-
const tailProse = renderTalkingTail(m.talkingPoints);
|
|
1350
|
-
const tailFallback = tailProse || flowText
|
|
1351
|
-
|| (m.conclusion ? `<h4 class="np-col-heading">From here</h4><div class="np-prose">${escape(m.conclusion)}</div>` : "");
|
|
1484
|
+
const sideHtml = verifsHtml ? `
|
|
1485
|
+
<span class="post-side-flag">More headings</span>
|
|
1486
|
+
<ul style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column;">
|
|
1487
|
+
${verifs.slice(0, 5).map((b) => {
|
|
1488
|
+
const p = splitHeading(b);
|
|
1489
|
+
return `
|
|
1490
|
+
<li style="font-family:var(--serif);font-size:12.5px;line-height:1.55;color:var(--ink-soft);padding:10px 0;border-bottom:1px solid var(--rule);">
|
|
1491
|
+
${p.body
|
|
1492
|
+
? `<b style="display:block;font-family:var(--serif-display);font-size:13.5px;font-weight:700;color:var(--ink);margin-bottom:4px;">${escape(p.heading)}</b>${escape(p.body)}`
|
|
1493
|
+
: escape(p.heading)}
|
|
1494
|
+
</li>`;
|
|
1495
|
+
}).join("")}
|
|
1496
|
+
</ul>` : "";
|
|
1497
|
+
|
|
1498
|
+
const bodyParts = [];
|
|
1499
|
+
if (ms1.body) bodyParts.push(`<p>${escape(ms1.body)}</p>`);
|
|
1500
|
+
if (ms2.body) bodyParts.push(`<p>${escape(ms2.body)}</p>`);
|
|
1501
|
+
const bodyHtml = bodyParts.join("");
|
|
1502
|
+
|
|
1503
|
+
const headline = ms1.title || ms2.title || "Inside the room";
|
|
1504
|
+
const deck = m.kicker || "";
|
|
1352
1505
|
|
|
1353
1506
|
return `
|
|
1354
|
-
<
|
|
1355
|
-
<div class="
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
<
|
|
1359
|
-
<h1 class="np-mast-name">PrivateBoard</h1>
|
|
1360
|
-
<div class="np-mast-flank np-mast-flank-right">${flankRight}</div>
|
|
1361
|
-
</header>
|
|
1362
|
-
|
|
1363
|
-
<div class="np-rule"></div>
|
|
1364
|
-
|
|
1365
|
-
<div class="np-meta-strip">
|
|
1366
|
-
<span class="np-meta-left">${escape(dispDate)}</span>
|
|
1367
|
-
<span class="np-meta-center">www.privateboard.app</span>
|
|
1368
|
-
<span class="np-meta-right">${escape(briefIdShort)}</span>
|
|
1507
|
+
<section class="np-page" id="np-page-2">
|
|
1508
|
+
<div class="post-running">
|
|
1509
|
+
<span class="post-running-mast">The Boardroom Post</span>
|
|
1510
|
+
<span class="post-running-meta">${escape(dateInfo.weekday)}, ${escape(dateInfo.long)} · privateboard.ai</span>
|
|
1511
|
+
<span class="post-running-page">A2</span>
|
|
1369
1512
|
</div>
|
|
1370
1513
|
|
|
1371
|
-
<div class="
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
${m.kicker ? `<div class="np-frontpage-deck">${escape(m.kicker)}</div>` : ""}
|
|
1376
|
-
</section>
|
|
1377
|
-
|
|
1378
|
-
<div class="np-rule"></div>
|
|
1514
|
+
<div class="post-sect-banner">
|
|
1515
|
+
<span class="post-sect-banner-tag">${escape(ms1.period || ms2.period || "Continued")}</span>
|
|
1516
|
+
<span class="post-sect-banner-deck">From the room · continued from the front page</span>
|
|
1517
|
+
</div>
|
|
1379
1518
|
|
|
1380
|
-
<
|
|
1381
|
-
<div
|
|
1382
|
-
|
|
1383
|
-
${
|
|
1384
|
-
|
|
1385
|
-
${
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1519
|
+
<div class="post-article">
|
|
1520
|
+
<div>
|
|
1521
|
+
<h2 class="post-article-head">${escape(headline)}</h2>
|
|
1522
|
+
${deck ? `<div class="post-article-deck">${escape(deck)}</div>` : ""}
|
|
1523
|
+
<div class="post-article-byline">By the chair · Continued from A1</div>
|
|
1524
|
+
${bodyHtml ? `<div class="post-body has-drop">${bodyHtml}</div>` : ""}
|
|
1525
|
+
${pullQuote ? `
|
|
1526
|
+
<div class="post-pull">
|
|
1527
|
+
<div class="post-pull-text">"${escape(pullQuote)}"</div>
|
|
1528
|
+
<div class="post-pull-cite">Editorial</div>
|
|
1529
|
+
</div>` : ""}
|
|
1391
1530
|
</div>
|
|
1392
|
-
<
|
|
1393
|
-
${
|
|
1394
|
-
${
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1531
|
+
<aside class="post-side">
|
|
1532
|
+
${sideHtml}
|
|
1533
|
+
${m.flow && Array.isArray(m.flow.nodes) && m.flow.nodes.length >= 2 ? `
|
|
1534
|
+
<div class="post-stat">
|
|
1535
|
+
<div class="post-stat-eye">Transformation</div>
|
|
1536
|
+
<div style="font-family: var(--serif-display); font-size: 17px; font-weight: 700; color: var(--ink); line-height: 1.25;">
|
|
1537
|
+
${m.flow.nodes.map(escape).join('<span style="color: var(--accent); margin: 0 6px;">→</span>')}
|
|
1538
|
+
</div>
|
|
1539
|
+
${m.flow.caption ? `<div class="post-stat-cap">${escape(m.flow.caption)}</div>` : ""}
|
|
1540
|
+
</div>` : ""}
|
|
1541
|
+
</aside>
|
|
1542
|
+
</div>
|
|
1400
1543
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
<h4 class="np-col-heading">${escape((m.verification && m.verification.title) || "More headings")}</h4>
|
|
1404
|
-
${moreHeadings}
|
|
1405
|
-
</div>
|
|
1406
|
-
<div class="np-col">${middlePull}</div>
|
|
1407
|
-
<div class="np-col">${tailFallback}</div>
|
|
1408
|
-
</section>
|
|
1409
|
-
|
|
1410
|
-
<footer class="np-footer">
|
|
1411
|
-
<span>${escape(m.footerTag || "")}</span>
|
|
1412
|
-
<span class="np-footer-stamp">PrivateBoard · ${escape(briefIdShort)}</span>
|
|
1413
|
-
</footer>
|
|
1414
|
-
</article>`;
|
|
1544
|
+
${pageFoot(2, totalPages, briefIdShort)}
|
|
1545
|
+
</section>`;
|
|
1415
1546
|
}
|
|
1416
1547
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|| `<aside class="np-callout"><div class="np-callout-label">From the desk</div><div class="np-callout-text">${escape((m.source || "Boardroom").toUpperCase())}</div></aside>`;
|
|
1548
|
+
/* ═════════════════════════════════════════════════════════════
|
|
1549
|
+
TIMES · The Boardroom · New York Times register
|
|
1550
|
+
═════════════════════════════════════════════════════════════ */
|
|
1551
|
+
|
|
1552
|
+
function renderTimesPage1(brief, parts) {
|
|
1553
|
+
const { m, ms0, ms1, ms2, dateInfo, briefIdShort, totalPages } = parts;
|
|
1554
|
+
const tpAll = (m.talkingPoints && Array.isArray(m.talkingPoints.bullets)) ? m.talkingPoints.bullets : [];
|
|
1555
|
+
const tpFirst = tpAll[0] || "";
|
|
1556
|
+
const tpSecond = tpAll[1] || "";
|
|
1557
|
+
const stat = pickStat(m);
|
|
1558
|
+
|
|
1559
|
+
// Verification preview · 2 short bullets at the bottom of the
|
|
1560
|
+
// lead column so the lead extends to roughly match the side
|
|
1561
|
+
// cols' height (the lead is twice as wide so its body wraps
|
|
1562
|
+
// half as tall · packing more content here keeps the columns
|
|
1563
|
+
// visually balanced).
|
|
1564
|
+
const vArr = (m.verification && Array.isArray(m.verification.bullets)) ? m.verification.bullets : [];
|
|
1565
|
+
const vPreview = vArr.slice(0, 2).map((b) => {
|
|
1566
|
+
const p = splitHeading(b);
|
|
1567
|
+
return `<li>${p.body
|
|
1568
|
+
? `<b>${escape(p.heading)}</b> ${escape(p.body)}`
|
|
1569
|
+
: escape(p.heading)}</li>`;
|
|
1570
|
+
}).join("");
|
|
1441
1571
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1572
|
+
// Volume info · classic NYT pattern · "VOL. CXC..No. {n} ★ Late
|
|
1573
|
+
// City Edition ★ {date} ★ {price/id}". We use the brief id
|
|
1574
|
+
// as a serial number and "Late City Edition" as the standard
|
|
1575
|
+
// copy.
|
|
1576
|
+
const volStrip = `
|
|
1577
|
+
<div class="times-vol-strip">
|
|
1578
|
+
<span><em>VOL. CXC . . No.</em> ${escape(briefIdShort.replace(/^#/, ""))}</span>
|
|
1579
|
+
<span class="star">★</span>
|
|
1580
|
+
<span>Late City Edition</span>
|
|
1581
|
+
<span class="star">★</span>
|
|
1582
|
+
<span>${escape(dateInfo.nytDate)}</span>
|
|
1583
|
+
<span class="star">★</span>
|
|
1584
|
+
<span><em>${escape(briefIdShort)}</em></span>
|
|
1585
|
+
</div>`;
|
|
1445
1586
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1587
|
+
return `
|
|
1588
|
+
<section class="np-page" id="np-page-1">
|
|
1589
|
+
<div class="times-tagline">"All the analysis that's fit to print"</div>
|
|
1590
|
+
<h1 class="times-mast-name">The Boardroom</h1>
|
|
1591
|
+
${volStrip}
|
|
1592
|
+
|
|
1593
|
+
<div class="times-front">
|
|
1594
|
+
<!-- Col 1 · sidebar story (ms2) -->
|
|
1595
|
+
<div class="times-col times-col-1">
|
|
1596
|
+
<div class="times-section-eye">Inside</div>
|
|
1597
|
+
<h3 class="times-head-small">${escape(ms2.title || ms2.period || "Late Dispatches")}</h3>
|
|
1598
|
+
${ms2.body ? `<div class="times-body">${escape(ms2.body)}</div>` : ""}
|
|
1599
|
+
${stat ? `
|
|
1600
|
+
<div class="times-stat">
|
|
1601
|
+
<div class="times-stat-eye">By the numbers</div>
|
|
1602
|
+
<div class="times-stat-num">${escape(stat.num)}</div>
|
|
1603
|
+
<div class="times-stat-cap">${escape(stat.cap)}</div>
|
|
1604
|
+
</div>` : ""}
|
|
1605
|
+
</div>
|
|
1451
1606
|
|
|
1452
|
-
|
|
1607
|
+
<!-- Col 2-3 · the lead story · centered headline + body
|
|
1608
|
+
+ standfirst + verification preview · packed so the
|
|
1609
|
+
lead matches the side cols' height -->
|
|
1610
|
+
<div class="times-col times-col-3">
|
|
1611
|
+
<div class="times-section-eye">The Boardroom</div>
|
|
1612
|
+
<h2 class="times-head-large">${escape(m.title || "")}</h2>
|
|
1613
|
+
${m.kicker ? `<div class="times-deck">${escape(m.kicker)}</div>` : ""}
|
|
1614
|
+
<div class="times-byline">By <em>The Chair</em></div>
|
|
1615
|
+
${ms0.body ? `<div class="times-body has-drop">${escape(ms0.body)}</div>` : ""}
|
|
1616
|
+
${m.conclusion ? `
|
|
1617
|
+
<aside class="times-stand">
|
|
1618
|
+
<div class="times-stand-mark">¶</div>
|
|
1619
|
+
<div class="times-stand-text">${escape(m.conclusion)}</div>
|
|
1620
|
+
<div class="times-stand-cite">Bottom line</div>
|
|
1621
|
+
</aside>` : ""}
|
|
1622
|
+
${tpSecond ? `
|
|
1623
|
+
<div class="times-pull">
|
|
1624
|
+
<div class="times-pull-text">"${escape(tpSecond)}"</div>
|
|
1625
|
+
<div class="times-pull-cite">Editorial · second take</div>
|
|
1626
|
+
</div>` : ""}
|
|
1627
|
+
${vPreview ? `
|
|
1628
|
+
<div style="margin-top:6px;">
|
|
1629
|
+
<div class="times-section-eye" style="text-align:left;border:0;padding-bottom:0;margin-bottom:6px;">More headings</div>
|
|
1630
|
+
<ul class="times-side-list" style="font-size:12px;">${vPreview}</ul>
|
|
1631
|
+
</div>` : ""}
|
|
1632
|
+
</div>
|
|
1453
1633
|
|
|
1454
|
-
|
|
1455
|
-
<
|
|
1456
|
-
|
|
1457
|
-
|
|
1634
|
+
<!-- Col 5-6 · secondary stories stacked -->
|
|
1635
|
+
<div class="times-col times-col-2">
|
|
1636
|
+
<div class="times-section-eye">Markets</div>
|
|
1637
|
+
<h3 class="times-head-medium">${escape(ms1.title || ms1.period || "On the markets")}</h3>
|
|
1638
|
+
${ms1.body ? `<div class="times-body">${escape(ms1.body)}</div>` : ""}
|
|
1639
|
+
${tpFirst ? `
|
|
1640
|
+
<div class="times-pull">
|
|
1641
|
+
<div class="times-pull-text">"${escape(tpFirst)}"</div>
|
|
1642
|
+
<div class="times-pull-cite">From the editorial</div>
|
|
1643
|
+
</div>` : ""}
|
|
1644
|
+
<div class="times-continued">Continued on Page A2</div>
|
|
1645
|
+
</div>
|
|
1458
1646
|
</div>
|
|
1459
1647
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
<h2 class="np-frontpage-title">${escape(m.title || "")}</h2>
|
|
1464
|
-
${m.kicker ? `<div class="np-frontpage-deck">${escape(m.kicker)}</div>` : ""}
|
|
1465
|
-
</section>
|
|
1466
|
-
|
|
1467
|
-
<div class="np-rule"></div>
|
|
1648
|
+
${pageFoot(1, totalPages, briefIdShort)}
|
|
1649
|
+
</section>`;
|
|
1650
|
+
}
|
|
1468
1651
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
</section>
|
|
1652
|
+
function renderTimesPage2(brief, parts) {
|
|
1653
|
+
const { m, ms1, ms2, dateInfo, briefIdShort, totalPages } = parts;
|
|
1654
|
+
const tpAll = (m.talkingPoints && Array.isArray(m.talkingPoints.bullets)) ? m.talkingPoints.bullets : [];
|
|
1655
|
+
const pullQuote = tpAll[1] || tpAll[0] || "";
|
|
1656
|
+
const verifs = (m.verification && Array.isArray(m.verification.bullets)) ? m.verification.bullets : [];
|
|
1657
|
+
const verifsHtml = verifs.slice(0, 5).map((b) => {
|
|
1658
|
+
const p = splitHeading(b);
|
|
1659
|
+
return `<li>${p.body
|
|
1660
|
+
? `<b>${escape(p.heading)}</b>${escape(p.body)}`
|
|
1661
|
+
: escape(p.heading)}</li>`;
|
|
1662
|
+
}).join("");
|
|
1481
1663
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1664
|
+
const bodyParts = [];
|
|
1665
|
+
if (ms1.body) bodyParts.push(`<p>${escape(ms1.body)}</p>`);
|
|
1666
|
+
if (ms2.body) bodyParts.push(`<p>${escape(ms2.body)}</p>`);
|
|
1667
|
+
const bodyHtml = bodyParts.join("");
|
|
1486
1668
|
|
|
1487
|
-
|
|
1669
|
+
const headline = ms1.title || ms2.title || "Continued from the front page";
|
|
1670
|
+
const deck = m.kicker || "";
|
|
1671
|
+
const sectionName = (ms1.period || ms2.period || "International").toUpperCase();
|
|
1488
1672
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
<div
|
|
1493
|
-
${
|
|
1494
|
-
${renderProse(ms1.body)}
|
|
1495
|
-
</div>
|
|
1496
|
-
<div class="np-col">
|
|
1497
|
-
${renderColumnHeading(ms2.period, ms2.title)}
|
|
1498
|
-
${renderProse(ms2.body)}
|
|
1673
|
+
const flowHtml = (m.flow && Array.isArray(m.flow.nodes) && m.flow.nodes.length >= 2)
|
|
1674
|
+
? `<div class="times-stat">
|
|
1675
|
+
<div class="times-stat-eye">Transformation</div>
|
|
1676
|
+
<div style="font-family: var(--serif-display); font-size: 17px; font-weight: 700; color: var(--ink); line-height: 1.3; padding: 6px 0;">
|
|
1677
|
+
${m.flow.nodes.map(escape).join('<span style="color: var(--accent); margin: 0 8px;">→</span>')}
|
|
1499
1678
|
</div>
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
${moreHeadings}
|
|
1503
|
-
${tail ? `<div class="np-prose" style="margin-top:14px;">${tail}</div>` : ""}
|
|
1504
|
-
${flowText}
|
|
1505
|
-
</div>
|
|
1506
|
-
</section>
|
|
1507
|
-
|
|
1508
|
-
<footer class="np-footer">
|
|
1509
|
-
<span>${escape(m.footerTag || "")}</span>
|
|
1510
|
-
<span class="np-footer-stamp">PrivateBoard · ${escape(briefIdShort)}</span>
|
|
1511
|
-
</footer>
|
|
1512
|
-
</article>`;
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
/** Layout 3 · 2x2 framed-quote spread (penny-press feel).
|
|
1516
|
-
* Each quadrant carries a milestone body + a callout/figure so
|
|
1517
|
-
* the four cells are roughly the same height — the prior 2/3 +
|
|
1518
|
-
* 1/3 asymmetric layout left the sidebar short relative to the
|
|
1519
|
-
* main column's two stacked bodies.
|
|
1520
|
-
*
|
|
1521
|
-
* · TOP 2-col · LEFT: ms0 lead-drop + bottom-line · RIGHT: ms1
|
|
1522
|
-
* + figure (chart inline)
|
|
1523
|
-
* · CENTER · framed full-width pull-quote drawn from talking[0]
|
|
1524
|
-
* · BOTTOM 2-col · LEFT: ms2 + date-stack · RIGHT: more-headings
|
|
1525
|
-
* + talking tail + flow
|
|
1526
|
-
*
|
|
1527
|
-
* Result: 4 quadrants bookending a centered framed quote ·
|
|
1528
|
-
* reads as a magazine spread on broadsheet paper, not a
|
|
1529
|
-
* half-empty asymmetric grid.
|
|
1530
|
-
*/
|
|
1531
|
-
function layoutVariant3(p) {
|
|
1532
|
-
const { m, ms0, ms1, ms2, flankLeft, flankRight, dispDate, briefIdShort,
|
|
1533
|
-
bottomLine, dateStack, figure, moreHeadings, flowText } = p;
|
|
1534
|
-
|
|
1535
|
-
const tp = (m.talkingPoints && Array.isArray(m.talkingPoints.bullets)) ? m.talkingPoints.bullets : [];
|
|
1536
|
-
const pullText = tp[0] || "";
|
|
1537
|
-
const tail = tp.slice(1, 5).map((b) => `<p>${escape(b)}</p>`).join("");
|
|
1538
|
-
|
|
1539
|
-
// Quadrant fillers · right-top carries the figure when
|
|
1540
|
-
// present, otherwise the date-stack steps in so that cell
|
|
1541
|
-
// never reads as prose-only. Left-bottom carries the
|
|
1542
|
-
// date-stack ONLY when right-top got the figure (avoids
|
|
1543
|
-
// double-rendering); when figure is missing the date-stack
|
|
1544
|
-
// already sat in right-top, so left-bottom stays bare prose.
|
|
1545
|
-
const rightTopAside = figure || dateStack;
|
|
1546
|
-
const leftBottomAside = figure ? dateStack : "";
|
|
1679
|
+
${m.flow.caption ? `<div class="times-stat-cap">${escape(m.flow.caption)}</div>` : ""}
|
|
1680
|
+
</div>` : "";
|
|
1547
1681
|
|
|
1548
1682
|
return `
|
|
1549
|
-
<
|
|
1550
|
-
<div class="
|
|
1683
|
+
<section class="np-page" id="np-page-2">
|
|
1684
|
+
<div class="times-running">
|
|
1685
|
+
<span class="times-running-l">The Boardroom</span>
|
|
1686
|
+
<span class="times-running-c">${escape(dateInfo.long)}</span>
|
|
1687
|
+
<span class="times-running-r">A2</span>
|
|
1688
|
+
</div>
|
|
1551
1689
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1690
|
+
<!-- Section banner · drops the rule ABOVE the section
|
|
1691
|
+
name because the running header already provides a
|
|
1692
|
+
horizontal line right above this block (its
|
|
1693
|
+
border-bottom). Two close-together rules read as a
|
|
1694
|
+
doubled border. -->
|
|
1695
|
+
<div class="times-section-banner">
|
|
1696
|
+
<div class="times-section-banner-name">${escape(sectionName)}</div>
|
|
1697
|
+
<div class="times-section-banner-rule"></div>
|
|
1698
|
+
<div class="times-section-banner-deck">Continued from the front page</div>
|
|
1699
|
+
</div>
|
|
1557
1700
|
|
|
1558
|
-
<div class="
|
|
1701
|
+
<div class="times-inside">
|
|
1702
|
+
<div>
|
|
1703
|
+
<h2 class="times-head-medium" style="text-align:left;">${escape(headline)}</h2>
|
|
1704
|
+
${deck ? `<div class="times-deck" style="text-align:left;">${escape(deck)}</div>` : ""}
|
|
1705
|
+
<div class="times-byline" style="text-align:left;">By <em>The Chair</em> · Continued from A1</div>
|
|
1706
|
+
<div class="times-continued">Continued from Page A1</div>
|
|
1707
|
+
${bodyHtml ? `<div class="times-inside-main has-drop">${bodyHtml}</div>` : ""}
|
|
1708
|
+
${pullQuote ? `
|
|
1709
|
+
<div class="times-pull">
|
|
1710
|
+
<div class="times-pull-text">"${escape(pullQuote)}"</div>
|
|
1711
|
+
<div class="times-pull-cite">From the editorial</div>
|
|
1712
|
+
</div>` : ""}
|
|
1713
|
+
</div>
|
|
1559
1714
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1715
|
+
<aside class="times-inside-side">
|
|
1716
|
+
<div class="times-side-flag">★ More Headings ★</div>
|
|
1717
|
+
${verifsHtml ? `<ul class="times-side-list">${verifsHtml}</ul>` : ""}
|
|
1718
|
+
${flowHtml}
|
|
1719
|
+
</aside>
|
|
1564
1720
|
</div>
|
|
1565
1721
|
|
|
1566
|
-
|
|
1722
|
+
${pageFoot(2, totalPages, briefIdShort)}
|
|
1723
|
+
</section>`;
|
|
1724
|
+
}
|
|
1567
1725
|
|
|
1568
|
-
|
|
1569
|
-
<h2 class="np-frontpage-title">${escape(m.title || "")}</h2>
|
|
1570
|
-
${m.kicker ? `<div class="np-frontpage-deck">${escape(m.kicker)}</div>` : ""}
|
|
1571
|
-
</section>
|
|
1726
|
+
/* ═════════════════════════════════════════════════════════════ */
|
|
1572
1727
|
|
|
1573
|
-
|
|
1728
|
+
function render(brief) {
|
|
1729
|
+
if (brief.mode !== "newspaper") {
|
|
1730
|
+
showState("Wrong mode", "This brief isn't a newspaper",
|
|
1731
|
+
"Open it in the matching viewer instead. Newspaper, magazine, and research-note are separate output modes.");
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const m = brief.bodyJson;
|
|
1735
|
+
if (!m || typeof m !== "object") {
|
|
1736
|
+
if (brief.isGenerating) {
|
|
1737
|
+
showState("Generating", "Newspaper is still being prepared",
|
|
1738
|
+
"The chair is currently composing the front page. Refresh in a few seconds.");
|
|
1739
|
+
} else {
|
|
1740
|
+
showState("Empty", "This brief has no newspaper data",
|
|
1741
|
+
"It may have failed to generate. Try regenerating from the room view.");
|
|
1742
|
+
}
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1574
1745
|
|
|
1575
|
-
|
|
1576
|
-
<section class="np-grid-2">
|
|
1577
|
-
<div class="np-col">
|
|
1578
|
-
${renderColumnHeading(ms0.period, ms0.title)}
|
|
1579
|
-
${renderProse(ms0.body, { lead: true })}
|
|
1580
|
-
${bottomLine}
|
|
1581
|
-
</div>
|
|
1582
|
-
<div class="np-col">
|
|
1583
|
-
${renderColumnHeading(ms1.period, ms1.title)}
|
|
1584
|
-
${renderProse(ms1.body)}
|
|
1585
|
-
${rightTopAside}
|
|
1586
|
-
</div>
|
|
1587
|
-
</section>
|
|
1746
|
+
if (m.title) document.title = `${m.title} · Newspaper`;
|
|
1588
1747
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1748
|
+
const variant = pickVariant(brief.id);
|
|
1749
|
+
document.body.setAttribute("data-np-variant", variant);
|
|
1750
|
+
if (variantBadge) {
|
|
1751
|
+
variantBadge.textContent = variant === "times" ? "Times Edition" : "Post Edition";
|
|
1752
|
+
}
|
|
1593
1753
|
|
|
1594
|
-
|
|
1754
|
+
const milestones = (m.milestones || []).slice(0, 3);
|
|
1755
|
+
const ms0 = milestones[0] || {};
|
|
1756
|
+
const ms1 = milestones[1] || {};
|
|
1757
|
+
const ms2 = milestones[2] || {};
|
|
1758
|
+
const dateInfo = formatDate(m.footerTag);
|
|
1759
|
+
const briefIdShort = brief.id ? `#${String(brief.id).slice(0, 10).toUpperCase()}` : "";
|
|
1760
|
+
const totalPages = 2;
|
|
1595
1761
|
|
|
1596
|
-
|
|
1597
|
-
<section class="np-grid-2">
|
|
1598
|
-
<div class="np-col">
|
|
1599
|
-
${renderColumnHeading(ms2.period, ms2.title)}
|
|
1600
|
-
${renderProse(ms2.body)}
|
|
1601
|
-
${leftBottomAside}
|
|
1602
|
-
</div>
|
|
1603
|
-
<div class="np-col">
|
|
1604
|
-
<h4 class="np-col-heading">${escape((m.verification && m.verification.title) || "More headings")}</h4>
|
|
1605
|
-
${moreHeadings}
|
|
1606
|
-
${tail ? `<div class="np-prose" style="margin-top:14px;">${tail}</div>` : ""}
|
|
1607
|
-
${flowText}
|
|
1608
|
-
</div>
|
|
1609
|
-
</section>
|
|
1762
|
+
const parts = { m, ms0, ms1, ms2, dateInfo, briefIdShort, totalPages };
|
|
1610
1763
|
|
|
1611
|
-
|
|
1764
|
+
const pages = variant === "times"
|
|
1765
|
+
? [renderTimesPage1(brief, parts), renderTimesPage2(brief, parts)]
|
|
1766
|
+
: [renderPostPage1(brief, parts), renderPostPage2(brief, parts)];
|
|
1612
1767
|
|
|
1613
|
-
|
|
1614
|
-
<span>${escape(m.footerTag || "")}</span>
|
|
1615
|
-
<span class="np-footer-stamp">PrivateBoard · ${escape(briefIdShort)}</span>
|
|
1616
|
-
</footer>
|
|
1617
|
-
</article>`;
|
|
1768
|
+
root.innerHTML = `<article class="np-doc" data-np-paper>${pages.join("")}</article>`;
|
|
1618
1769
|
}
|
|
1619
1770
|
|
|
1620
|
-
/* ─── Export wiring · same PNG-as-PDF strategy
|
|
1771
|
+
/* ─── Export wiring · same PNG-as-PDF strategy ─────────── */
|
|
1621
1772
|
let _h2iLoaded = null;
|
|
1622
1773
|
async function ensureHtmlToImage() {
|
|
1623
1774
|
if (window.htmlToImage) return;
|
|
@@ -1644,17 +1795,13 @@
|
|
|
1644
1795
|
const height = Math.max(el.scrollHeight, el.offsetHeight, el.clientHeight);
|
|
1645
1796
|
return window.htmlToImage.toPng(el, {
|
|
1646
1797
|
pixelRatio: 2,
|
|
1647
|
-
backgroundColor: "#
|
|
1798
|
+
backgroundColor: "#1F1E1A",
|
|
1648
1799
|
cacheBust: true,
|
|
1649
1800
|
width,
|
|
1650
1801
|
height,
|
|
1651
1802
|
canvasWidth: width,
|
|
1652
1803
|
canvasHeight: height,
|
|
1653
|
-
style: {
|
|
1654
|
-
margin: "0",
|
|
1655
|
-
width: `${width}px`,
|
|
1656
|
-
height: `${height}px`,
|
|
1657
|
-
},
|
|
1804
|
+
style: { margin: "0", width: `${width}px`, height: `${height}px` },
|
|
1658
1805
|
});
|
|
1659
1806
|
}
|
|
1660
1807
|
|
|
@@ -1692,33 +1839,17 @@
|
|
|
1692
1839
|
@page { size: auto; margin: 10mm; }
|
|
1693
1840
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1694
1841
|
html, body { background: #FFFFFF; }
|
|
1695
|
-
body {
|
|
1696
|
-
|
|
1697
|
-
align-items: flex-start;
|
|
1698
|
-
justify-content: center;
|
|
1699
|
-
padding: 20px;
|
|
1700
|
-
min-height: 100vh;
|
|
1701
|
-
}
|
|
1702
|
-
img {
|
|
1703
|
-
display: block;
|
|
1704
|
-
width: 100%;
|
|
1705
|
-
max-width: 880px;
|
|
1706
|
-
height: auto;
|
|
1707
|
-
box-shadow: 0 0 24px rgba(0, 0, 0, 0.08);
|
|
1708
|
-
}
|
|
1842
|
+
body { display: flex; align-items: flex-start; justify-content: center; padding: 20px; min-height: 100vh; }
|
|
1843
|
+
img { display: block; width: 100%; max-width: 880px; height: auto; box-shadow: 0 0 24px rgba(0, 0, 0, 0.08); }
|
|
1709
1844
|
@media print {
|
|
1710
1845
|
body { padding: 0; }
|
|
1711
1846
|
img { box-shadow: none; max-width: none; width: 100%; }
|
|
1712
1847
|
}
|
|
1713
1848
|
.hint {
|
|
1714
|
-
position: fixed;
|
|
1715
|
-
top: 12px;
|
|
1716
|
-
left: 12px;
|
|
1849
|
+
position: fixed; top: 12px; left: 12px;
|
|
1717
1850
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
1718
|
-
font-size: 11px;
|
|
1719
|
-
|
|
1720
|
-
background: rgba(255,255,255,0.9);
|
|
1721
|
-
padding: 6px 10px;
|
|
1851
|
+
font-size: 11px; color: #8E8B83;
|
|
1852
|
+
background: rgba(255,255,255,0.9); padding: 6px 10px;
|
|
1722
1853
|
border: 1px solid #E5E2DA;
|
|
1723
1854
|
}
|
|
1724
1855
|
@media print { .hint { display: none; } }
|
|
@@ -1743,16 +1874,8 @@
|
|
|
1743
1874
|
}
|
|
1744
1875
|
|
|
1745
1876
|
document.addEventListener("click", (e) => {
|
|
1746
|
-
if (e.target.closest("[data-np-png]")) {
|
|
1747
|
-
|
|
1748
|
-
exportPng();
|
|
1749
|
-
return;
|
|
1750
|
-
}
|
|
1751
|
-
if (e.target.closest("[data-np-print]")) {
|
|
1752
|
-
e.preventDefault();
|
|
1753
|
-
exportPdf();
|
|
1754
|
-
return;
|
|
1755
|
-
}
|
|
1877
|
+
if (e.target.closest("[data-np-png]")) { e.preventDefault(); exportPng(); return; }
|
|
1878
|
+
if (e.target.closest("[data-np-print]")) { e.preventDefault(); exportPdf(); return; }
|
|
1756
1879
|
});
|
|
1757
1880
|
|
|
1758
1881
|
/* ─── Boot ──────────────────────────────────────────────────── */
|
|
@@ -1760,8 +1883,7 @@
|
|
|
1760
1883
|
if (brief) render(brief);
|
|
1761
1884
|
}).catch((e) => {
|
|
1762
1885
|
console.error("[newspaper] load failed:", e);
|
|
1763
|
-
showState("Error", "Couldn't load this brief",
|
|
1764
|
-
e instanceof Error ? e.message : String(e));
|
|
1886
|
+
showState("Error", "Couldn't load this brief", e instanceof Error ? e.message : String(e));
|
|
1765
1887
|
});
|
|
1766
1888
|
})();
|
|
1767
1889
|
</script>
|