edgepress 2026.9.6 → 2026.9.8

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.
Files changed (42) hide show
  1. package/README.md +2 -2
  2. package/config.yml +110 -63
  3. package/content/pages/home/en.md +9 -1
  4. package/content/pages/home/zh-cn.md +9 -1
  5. package/content/pages/plugin-development/en.md +5 -4
  6. package/content/pages/plugin-development/zh-cn.md +5 -4
  7. package/content/pages/privacy-policy/en.md +7 -4
  8. package/content/pages/privacy-policy/zh-cn.md +7 -4
  9. package/content/pages/theme-development/en.md +1 -0
  10. package/content/pages/theme-development/zh-cn.md +1 -0
  11. package/content/posts/2026-09-23-edgepress-on-the-edge/en.md +9 -1
  12. package/content/posts/2026-09-23-edgepress-on-the-edge/zh-cn.md +9 -1
  13. package/content/posts/2026-09-25-edgepress-release-2026-9-6/en.md +2 -2
  14. package/content/posts/2026-09-25-edgepress-release-2026-9-6/zh-cn.md +2 -2
  15. package/content/posts/2026-09-26-edgepress-release-2026-9-7/en.md +26 -0
  16. package/content/posts/2026-09-26-edgepress-release-2026-9-7/zh-cn.md +26 -0
  17. package/content/posts/2026-09-26-edgepress-release-2026-9-8/en.md +26 -0
  18. package/content/posts/2026-09-26-edgepress-release-2026-9-8/zh-cn.md +26 -0
  19. package/languages/base/en.json +7 -2
  20. package/languages/packs/zh-CN.json +7 -2
  21. package/package.json +2 -3
  22. package/plugins/consent/index.js +4 -2
  23. package/project-compatibility.json +19 -3
  24. package/src/build.js +5 -4
  25. package/src/cli.js +1 -11
  26. package/src/config.js +72 -14
  27. package/src/content.js +22 -1
  28. package/src/generators.js +25 -20
  29. package/src/i18n.js +6 -0
  30. package/src/markdown.js +88 -9
  31. package/src/page-blocks.js +61 -16
  32. package/src/page-check.js +73 -24
  33. package/src/report-visuals.js +111 -16
  34. package/static/edgepress/brand/mascot-209.webp +0 -0
  35. package/static/edgepress/brand/mascot-418.png +0 -0
  36. package/static/edgepress/brand/mascot-418.webp +0 -0
  37. package/static/edgepress/plugins/consent/manager.js +204 -71
  38. package/static/edgepress/plugins/statistics/cloudflare-web-analytics.js +1 -0
  39. package/themes/atelier/assets/style.css +50 -19
  40. package/themes/default/assets/style.css +50 -19
  41. package/themes/signal/assets/style.css +50 -19
  42. package/CHANGELOG.txt +0 -12
@@ -21,104 +21,171 @@ function initialize(config) {
21
21
  const privacy = config.privacy || {};
22
22
  const integrations = Array.isArray(privacy.integrations) ? privacy.integrations : [];
23
23
  const ui = config.ui || {};
24
+ const locale = config.siteLanguage || document.documentElement.lang || 'en';
25
+ const defaultLocale = config.defaultLocale || 'en';
24
26
  const root = document.createElement('div');
25
27
  root.className = 'privacy-manager';
28
+ root.setAttribute('data-consent-ui', 'true');
26
29
 
27
- const settingsButton = document.createElement('button');
28
- settingsButton.type = 'button';
29
- settingsButton.className = 'privacy-settings-button';
30
- settingsButton.textContent = ui.privacySettings || 'Privacy settings';
30
+ const settingsButton = makeButton(ui.privacySettings || 'Privacy settings', 'privacy-settings-button', 'settings');
31
+ settingsButton.setAttribute('aria-expanded', 'false');
32
+ settingsButton.setAttribute('aria-controls', 'privacy-panel');
31
33
 
32
34
  const panel = document.createElement('section');
35
+ panel.id = 'privacy-panel';
33
36
  panel.className = 'privacy-panel';
34
37
  panel.setAttribute('role', 'dialog');
35
38
  panel.setAttribute('aria-modal', 'false');
36
39
  panel.setAttribute('aria-labelledby', 'privacy-panel-title');
37
- panel.lang = config.siteLanguage || document.documentElement.lang;
40
+ panel.lang = locale;
38
41
  panel.hidden = true;
39
42
 
43
+ const close = makeButton(ui.closePrivacy || 'Close settings', 'privacy-close', 'close');
44
+ close.setAttribute('aria-label', ui.closePrivacy || 'Close settings');
40
45
  const heading = document.createElement('h2');
41
46
  heading.id = 'privacy-panel-title';
42
47
  heading.tabIndex = -1;
43
- heading.textContent = ui.privacyNotice || 'Optional services';
48
+ heading.textContent = ui.privacyNotice || 'Privacy choices';
44
49
  const intro = document.createElement('p');
45
- intro.textContent = ui.privacyIntro || 'Choose which optional services may load.';
46
- const details = document.createElement('div');
47
- details.className = 'privacy-details';
48
- details.setAttribute('aria-label', ui.optionalServices || 'Optional services');
49
- const actions = document.createElement('div');
50
- actions.className = 'privacy-actions';
51
- const close = makeButton(ui.closePrivacy || 'Close settings', 'privacy-close');
52
-
53
- const accept = makeButton(ui.acceptOptional || 'Accept all', 'privacy-accept');
54
- const reject = makeButton(ui.rejectOptional || 'Reject optional', 'privacy-reject');
55
- const save = makeButton(ui.savePreferences || 'Save choices', 'privacy-save');
56
- actions.append(accept, reject, save);
57
- panel.append(close, heading, intro, details);
58
- if (privacy.controller?.name || privacy.controller?.contact) {
59
- const controller = document.createElement('p');
60
- controller.className = 'privacy-controller';
61
- controller.textContent = (ui.privacyController || 'Site operator') + ': ' + [privacy.controller.name, privacy.controller.contact].filter(Boolean).join(' · ');
62
- panel.append(controller);
63
- }
64
- if (privacy.policyUrl) {
65
- const policy = document.createElement('a');
66
- policy.href = privacy.policyUrl;
67
- policy.textContent = ui.privacyPolicy || 'Privacy policy';
68
- policy.className = 'privacy-policy-link';
69
- panel.append(policy);
70
- }
71
- panel.append(actions);
72
- root.append(settingsButton, panel);
73
- document.body.append(root);
50
+ intro.className = 'privacy-intro';
51
+ intro.textContent = ui.privacyIntro || 'Optional services stay off until you choose.';
74
52
 
75
- const saved = readChoice(privacy);
53
+ const preview = document.createElement('div');
54
+ preview.className = 'privacy-service-preview';
55
+ preview.setAttribute('aria-label', ui.optionalServices || 'Optional services');
56
+ const details = document.createElement('details');
57
+ details.className = 'privacy-details';
58
+ const summary = document.createElement('summary');
59
+ summary.textContent = ui.reviewDetails || 'Review data and service settings';
60
+ const serviceSettings = document.createElement('div');
61
+ serviceSettings.className = 'privacy-service-settings';
76
62
  const checkboxes = new Map();
63
+
77
64
  for (const integration of integrations) {
78
- const label = document.createElement('label');
79
- label.className = 'privacy-service';
65
+ const category = translate(ui, 'plugin' + capitalize(integration.category || 'statistics'));
66
+ const name = localized(integration.name, locale, defaultLocale) || integration.provider;
67
+ const purpose = localized(integration.purpose, locale, defaultLocale);
68
+ const dataCategories = localized(integration.dataCategories, locale, defaultLocale);
69
+ const recipient = localized(integration.recipient, locale, defaultLocale);
70
+ const retention = localized(integration.retention, locale, defaultLocale);
71
+
72
+ const previewItem = document.createElement('article');
73
+ previewItem.className = 'privacy-preview-item';
74
+ const previewHeading = document.createElement('h3');
75
+ previewHeading.textContent = name;
76
+ const categoryText = document.createElement('span');
77
+ categoryText.className = 'privacy-category';
78
+ categoryText.textContent = category;
79
+ const previewPurpose = document.createElement('p');
80
+ previewPurpose.textContent = purpose;
81
+ previewItem.append(previewHeading, categoryText, previewPurpose);
82
+ preview.append(previewItem);
83
+
84
+ const item = document.createElement('article');
85
+ item.className = 'privacy-service-setting';
86
+ const toggle = document.createElement('label');
87
+ toggle.className = 'privacy-service-toggle';
80
88
  const checkbox = document.createElement('input');
81
89
  checkbox.type = 'checkbox';
82
90
  checkbox.value = integration.id;
83
- checkbox.checked = saved?.allowed.includes(integration.id) || false;
84
- const info = document.createElement('span');
91
+ checkbox.setAttribute('role', 'switch');
92
+ checkbox.checked = false;
93
+ const toggleText = document.createElement('span');
94
+ toggleText.className = 'privacy-service-toggle-text';
85
95
  const title = document.createElement('strong');
86
- title.textContent = integration.provider;
87
- const purpose = document.createElement('span');
88
- purpose.textContent = (ui.servicePurpose || 'Purpose') + ': ' + integration.purpose;
89
- info.append(title, document.createElement('br'), purpose);
90
- if (integration.retention) {
91
- const retention = document.createElement('span');
92
- retention.textContent = ' · ' + (ui.serviceRetention || 'Retention') + ': ' + integration.retention;
93
- info.append(retention);
94
- }
95
- label.append(checkbox, info);
96
- details.append(label);
96
+ title.textContent = name;
97
+ const categoryLabel = document.createElement('span');
98
+ categoryLabel.className = 'privacy-category';
99
+ categoryLabel.textContent = category;
100
+ toggleText.append(title, categoryLabel);
101
+ toggle.append(checkbox, toggleText);
102
+ item.append(toggle, disclosureList(ui, [
103
+ ['servicePurpose', purpose],
104
+ ['serviceDataCategories', dataCategories],
105
+ ['serviceRecipient', recipient],
106
+ ['serviceRetention', retention]
107
+ ]));
108
+ serviceSettings.append(item);
97
109
  checkboxes.set(integration.id, checkbox);
98
110
  }
111
+
99
112
  if (!integrations.length) {
100
113
  const empty = document.createElement('p');
114
+ empty.className = 'privacy-empty';
101
115
  empty.textContent = ui.noIntegrations || 'No optional services are configured.';
102
- details.append(empty);
103
- accept.disabled = true;
116
+ preview.append(empty);
117
+ serviceSettings.append(empty.cloneNode(true));
118
+ }
119
+
120
+ details.append(summary, serviceSettings);
121
+ const essential = document.createElement('p');
122
+ essential.className = 'privacy-essential';
123
+ essential.textContent = (ui.essentialStorage || 'Your choice is saved in this browser for up to {days} days.')
124
+ .replace('{days}', String(privacy.consent?.expiresDays || 180));
125
+
126
+ const operator = privacy.controller || {};
127
+ const controller = document.createElement('p');
128
+ controller.className = 'privacy-controller';
129
+ const operatorDetails = [operator.name, operator.contact].filter(Boolean).join(' · ');
130
+ controller.textContent = operatorDetails ? (ui.privacyController || 'Site operator') + ': ' + operatorDetails : '';
131
+ const policy = document.createElement('a');
132
+ policy.href = privacy.policyUrl || '#';
133
+ policy.textContent = ui.privacyPolicy || 'Privacy policy';
134
+ policy.className = 'privacy-policy-link';
135
+ if (!privacy.policyUrl) policy.hidden = true;
136
+
137
+ const actions = document.createElement('div');
138
+ actions.className = 'privacy-actions';
139
+ const reject = makeButton(ui.rejectOptional || 'Reject optional', 'privacy-reject', 'reject');
140
+ const manage = makeButton(ui.managePrivacy || 'Manage', 'privacy-manage', 'manage');
141
+ const accept = makeButton(ui.acceptOptional || 'Accept all', 'privacy-accept', 'accept');
142
+ actions.append(reject, manage, accept);
143
+ const save = makeButton(ui.savePreferences || 'Save choices', 'privacy-save', 'save');
144
+ save.disabled = !integrations.length;
145
+ serviceSettings.append(save);
146
+
147
+ panel.append(close, heading, intro, preview, details, essential);
148
+ if (controller.textContent) panel.append(controller);
149
+ panel.append(policy, actions);
150
+ root.append(settingsButton, panel);
151
+ document.body.append(root);
152
+
153
+ const saved = readChoice(privacy);
154
+ for (const [id, checkbox] of checkboxes) checkbox.checked = Boolean(saved?.allowed.includes(id));
155
+ if (!integrations.length) {
104
156
  reject.disabled = true;
105
- save.disabled = true;
157
+ accept.disabled = true;
158
+ manage.disabled = true;
106
159
  }
107
160
 
108
161
  settingsButton.addEventListener('click', () => {
109
162
  panel.hidden = !panel.hidden;
110
- if (!panel.hidden) heading.focus?.();
163
+ settingsButton.setAttribute('aria-expanded', String(!panel.hidden));
164
+ if (!panel.hidden) heading.focus();
165
+ });
166
+ close.addEventListener('click', () => closePanel());
167
+ manage.addEventListener('click', () => {
168
+ details.open = true;
169
+ details.querySelector('summary')?.focus();
111
170
  });
112
- close.addEventListener('click', () => { panel.hidden = true; settingsButton.focus(); });
113
- accept.addEventListener('click', () => saveChoice(integrations.map((item) => item.id)));
114
171
  reject.addEventListener('click', () => saveChoice([]));
172
+ accept.addEventListener('click', () => saveChoice(integrations.map((item) => item.id)));
115
173
  save.addEventListener('click', () => saveChoice([...checkboxes].filter(([, checkbox]) => checkbox.checked).map(([id]) => id)));
174
+ panel.addEventListener('keydown', (event) => {
175
+ if (event.key === 'Escape' && !panel.hidden) closePanel();
176
+ });
177
+
178
+ function closePanel() {
179
+ panel.hidden = true;
180
+ settingsButton.setAttribute('aria-expanded', 'false');
181
+ settingsButton.focus();
182
+ }
116
183
 
117
184
  function saveChoice(allowed) {
118
185
  const previous = readChoice(privacy);
119
186
  const next = makeChoice(privacy, allowed);
120
187
  persistChoice(next);
121
- panel.hidden = true;
188
+ closePanel();
122
189
  if (previous && previous.allowed.some((id) => !next.allowed.includes(id))) {
123
190
  intro.textContent = ui.consentReload || 'Your choice was saved. Reloading to apply the updated settings.';
124
191
  window.location.reload();
@@ -128,28 +195,94 @@ function initialize(config) {
128
195
  }
129
196
 
130
197
  if (saved) activate(saved.allowed);
131
- else if (integrations.length) panel.hidden = false;
132
-
133
- function makeButton(label, className) {
134
- const button = document.createElement('button');
135
- button.type = 'button';
136
- button.className = className;
137
- button.textContent = label;
138
- return button;
198
+ else if (integrations.length) {
199
+ panel.hidden = false;
200
+ settingsButton.setAttribute('aria-expanded', 'true');
201
+ }
202
+ }
203
+
204
+ function disclosureList(ui, entries) {
205
+ const list = document.createElement('dl');
206
+ list.className = 'privacy-service-disclosures';
207
+ for (const [key, value] of entries) {
208
+ if (!value) continue;
209
+ const row = document.createElement('div');
210
+ const term = document.createElement('dt');
211
+ term.textContent = translate(ui, key);
212
+ const description = document.createElement('dd');
213
+ description.textContent = value;
214
+ row.append(term, description);
215
+ list.append(row);
139
216
  }
217
+ return list;
218
+ }
219
+
220
+ function translate(ui, key) {
221
+ return ui[key] || key;
222
+ }
223
+
224
+ function localized(value, locale, defaultLocale) {
225
+ if (typeof value === 'string') return value;
226
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return '';
227
+ return value[locale] || value[defaultLocale] || Object.values(value)[0] || '';
228
+ }
229
+
230
+ function capitalize(value) {
231
+ return String(value).charAt(0).toUpperCase() + String(value).slice(1);
232
+ }
233
+
234
+ function makeButton(label, className, iconName) {
235
+ const button = document.createElement('button');
236
+ button.type = 'button';
237
+ button.className = className;
238
+ button.append(makeIcon(iconName), document.createTextNode(label));
239
+ return button;
240
+ }
241
+
242
+ function makeIcon(name) {
243
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
244
+ svg.setAttribute('viewBox', '0 0 24 24');
245
+ svg.setAttribute('aria-hidden', 'true');
246
+ svg.setAttribute('focusable', 'false');
247
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
248
+ const paths = {
249
+ settings: 'M4 6h16M4 12h16M4 18h16M8 4v4m8 2v4m-5 2v4',
250
+ manage: 'M4 6h16M4 12h16M4 18h16M8 4v4m8 2v4m-5 2v4',
251
+ accept: 'm5 12 4 4L19 6',
252
+ reject: 'm6 6 12 12M18 6 6 18',
253
+ close: 'm6 6 12 12M18 6 6 18',
254
+ save: 'M5 12h14m-6-6 6 6-6 6'
255
+ };
256
+ path.setAttribute('d', paths[name] || paths.manage);
257
+ path.setAttribute('fill', 'none');
258
+ path.setAttribute('stroke', 'currentColor');
259
+ path.setAttribute('stroke-width', '2');
260
+ path.setAttribute('stroke-linecap', 'round');
261
+ path.setAttribute('stroke-linejoin', 'round');
262
+ svg.append(path);
263
+ return svg;
140
264
  }
141
265
 
142
266
  function makeChoice(privacy, allowed) {
143
- const expiresAt = Date.now() + Math.max(1, Math.min(730, privacy.consent?.expiresDays || 180)) * 86400000;
144
- const fingerprint = JSON.stringify({ controller: privacy.controller, policyUrl: privacy.policyUrl, integrations: privacy.integrations });
145
- return { version: privacy.consent?.version || '1', fingerprint, expiresAt, allowed: [...new Set(allowed)] };
267
+ const consent = privacy.consent || {};
268
+ const expiresAt = Date.now() + Math.max(1, Math.min(730, consent.expiresDays || 180)) * 86400000;
269
+ const fingerprint = JSON.stringify({ controller: privacy.controller, policyUrl: privacy.policyUrl, consent, integrations: privacy.integrations });
270
+ return {
271
+ proposedDate: consent.proposedDate || '',
272
+ effectiveDate: consent.effectiveDate || '',
273
+ fingerprint,
274
+ expiresAt,
275
+ allowed: [...new Set(allowed)]
276
+ };
146
277
  }
147
278
 
148
279
  function readChoice(privacy) {
149
280
  try {
150
281
  const saved = JSON.parse(localStorage.getItem(storageKey));
151
- const fingerprint = JSON.stringify({ controller: privacy.controller, policyUrl: privacy.policyUrl, integrations: privacy.integrations });
152
- if (!saved || saved.version !== (privacy.consent?.version || '1') || saved.fingerprint !== fingerprint || saved.expiresAt <= Date.now() || !Array.isArray(saved.allowed)) {
282
+ const consent = privacy.consent || {};
283
+ const fingerprint = JSON.stringify({ controller: privacy.controller, policyUrl: privacy.policyUrl, consent, integrations: privacy.integrations });
284
+ if (!saved || saved.proposedDate !== (consent.proposedDate || '') || saved.effectiveDate !== (consent.effectiveDate || '') ||
285
+ saved.fingerprint !== fingerprint || saved.expiresAt <= Date.now() || !Array.isArray(saved.allowed)) {
153
286
  localStorage.removeItem(storageKey);
154
287
  return null;
155
288
  }
@@ -1,6 +1,7 @@
1
1
  export function load(integration) {
2
2
  if (document.querySelector('script[data-edgepress-provider="cloudflare-web-analytics"]')) return;
3
3
  const script = document.createElement('script');
4
+ script.type = 'module';
4
5
  script.async = true;
5
6
  script.src = 'https://static.cloudflareinsights.com/beacon.min.js';
6
7
  script.dataset.cfBeacon = JSON.stringify({ token: integration.token });
@@ -19,25 +19,48 @@ body{display:flex;flex-direction:column;min-height:100vh}
19
19
  @media(max-width:760px){.site-footer{width:calc(100% - 38px)}}
20
20
 
21
21
 
22
- .privacy-manager{position:fixed;right:16px;bottom:16px;z-index:1000;color:var(--ink);font:14px/1.5 ui-sans-serif,system-ui,sans-serif}
23
- .privacy-settings-button,.privacy-panel button{font:inherit;font-weight:700;border:1px solid var(--line);border-radius:8px;padding:9px 13px;background:var(--panel,#fff);color:var(--ink);cursor:pointer}
24
- .privacy-settings-button{box-shadow:0 4px 18px #0002}
25
- .privacy-panel{width:min(28rem,calc(100vw - 32px));max-height:min(80vh,42rem);overflow:auto;padding:20px;margin-bottom:10px;border:1px solid var(--line);border-radius:12px;background:var(--paper,#fff);box-shadow:0 12px 40px #0003}
26
- .privacy-panel[hidden]{display:none}
27
- .privacy-panel h2{margin:8px 0;font-size:1.2rem}
28
- .privacy-panel p{margin:10px 0}
29
- .privacy-close{float:right}
30
- .privacy-details{display:grid;gap:8px;margin:16px 0}
31
- .privacy-service{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid var(--line);border-radius:8px}
32
- .privacy-service input{margin-top:4px;accent-color:var(--accent)}
33
- .privacy-service span span{display:inline}
34
- .privacy-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
35
- .privacy-actions button{flex:1 1 auto}
36
- .privacy-accept,.privacy-reject{font-weight:800!important}
37
- .privacy-controller,.privacy-policy-link{font-size:.9rem}
38
- .privacy-settings-button:focus-visible,.privacy-panel button:focus-visible,.privacy-panel a:focus-visible,.privacy-service input:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
39
- @media(max-width:500px){.privacy-manager{left:12px;right:12px;bottom:12px}.privacy-settings-button{float:right}.privacy-panel{width:100%}.privacy-actions button{flex:1 1 100%}}
40
-
22
+ .privacy-manager{position:fixed;inset-inline-end:max(12px,env(safe-area-inset-right));inset-block-end:max(12px,env(safe-area-inset-bottom));z-index:1000;display:grid;align-content:end;gap:8px;box-sizing:border-box;width:min(30rem,calc(100vw - 24px));max-height:calc(100vh - 24px);max-height:calc(100dvh - 24px);color:var(--ink,#1d2824);font:14px/1.45 ui-sans-serif,system-ui,sans-serif;pointer-events:none;isolation:isolate}
23
+ .privacy-manager *{box-sizing:border-box}
24
+ .privacy-manager>*{min-width:0;pointer-events:auto}
25
+ .privacy-manager .privacy-settings-button,.privacy-manager .privacy-panel button{display:inline-flex;justify-content:center;align-items:center;gap:8px;min-width:0;min-height:46px;max-width:100%;padding:10px 12px;border:1px solid var(--line,#c7d0c9);border-radius:10px;background:var(--panel,#fff);color:var(--ink,#1d2824);font:inherit;font-weight:700;line-height:1.25;text-align:center;overflow-wrap:anywhere;cursor:pointer}
26
+ .privacy-manager .privacy-settings-button{order:1;justify-self:end;box-shadow:0 4px 18px #0002}
27
+ .privacy-manager .privacy-panel{position:relative;order:0;display:grid;gap:12px;width:100%;max-height:min(68vh,40rem);max-height:min(68dvh,40rem);overflow:auto;overscroll-behavior:contain;padding:18px;border:1px solid var(--line,#c7d0c9);border-radius:14px;background:var(--paper,#fff);color:var(--ink,#1d2824);box-shadow:0 12px 40px #0003}
28
+ .privacy-manager .privacy-panel[hidden]{display:none}
29
+ .privacy-manager .privacy-panel h2{margin:0;padding-inline-end:44px;font-size:1.15rem;line-height:1.3}
30
+ .privacy-manager .privacy-panel p{margin:0;overflow-wrap:anywhere}
31
+ .privacy-manager .privacy-panel .privacy-close{position:absolute;inset-block-start:8px;inset-inline-end:8px;width:44px;min-width:44px;min-height:44px;padding:8px}
32
+ .privacy-manager .privacy-panel .privacy-close svg{width:20px;height:20px}
33
+ .privacy-manager svg{width:18px;height:18px;flex:none}
34
+ .privacy-manager .privacy-intro,.privacy-manager .privacy-essential,.privacy-manager .privacy-controller{color:var(--muted,#536159)}
35
+ .privacy-manager .privacy-service-preview,.privacy-manager .privacy-service-settings{display:grid;gap:8px;min-width:0}
36
+ .privacy-manager .privacy-preview-item,.privacy-manager .privacy-service-setting{display:grid;gap:7px;min-width:0;padding:11px;border:1px solid var(--line,#c7d0c9);border-radius:10px;background:var(--panel,#fff)}
37
+ .privacy-manager .privacy-preview-item{grid-template-columns:minmax(0,1fr) auto;align-items:start}
38
+ .privacy-manager .privacy-preview-item h3{min-width:0;margin:0;font-size:.96rem;overflow-wrap:anywhere}
39
+ .privacy-manager .privacy-preview-item p{grid-column:1/-1;color:var(--muted,#536159)}
40
+ .privacy-manager .privacy-category{justify-self:start;max-width:100%;padding:2px 7px;border-radius:999px;background:color-mix(in srgb,var(--accent,#315e49) 12%,transparent);color:var(--ink,#1d2824);font-size:.78rem;overflow-wrap:anywhere}
41
+ .privacy-manager .privacy-details{min-width:0;margin:0;border-block:1px solid var(--line,#c7d0c9)}
42
+ .privacy-manager .privacy-details>summary{padding:10px 2px;font-weight:750;cursor:pointer}
43
+ .privacy-manager .privacy-details>summary:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
44
+ .privacy-manager .privacy-service-settings{padding-block-end:10px}
45
+ .privacy-manager .privacy-service-toggle{display:flex;align-items:center;gap:11px;min-width:0;cursor:pointer}
46
+ .privacy-manager .privacy-service-toggle input{position:relative;flex:none;width:42px;height:24px;margin:0;appearance:none;border:1px solid var(--line,#748079);border-radius:999px;background:var(--line,#748079);cursor:pointer}
47
+ .privacy-manager .privacy-service-toggle input::before{position:absolute;inset-block-start:2px;inset-inline-start:2px;width:18px;height:18px;border-radius:50%;background:var(--paper,#fff);content:"";transition:transform .16s ease}
48
+ .privacy-manager .privacy-service-toggle input:checked{border-color:var(--accent,#315e49);background:var(--accent,#315e49)}
49
+ .privacy-manager .privacy-service-toggle input:checked::before{transform:translateX(18px)}
50
+ .privacy-manager .privacy-service-toggle input:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
51
+ .privacy-manager .privacy-service-toggle-text{display:grid;gap:3px;min-width:0;overflow-wrap:anywhere}
52
+ .privacy-manager .privacy-service-toggle-text strong{font-size:.96rem}
53
+ .privacy-manager .privacy-service-disclosures{display:grid;gap:6px;margin:0}
54
+ .privacy-manager .privacy-service-disclosures>div{display:grid;grid-template-columns:minmax(5.5rem,.34fr) minmax(0,1fr);gap:8px;min-width:0}
55
+ .privacy-manager .privacy-service-disclosures dt{color:var(--muted,#536159);font-weight:650}
56
+ .privacy-manager .privacy-service-disclosures dd{min-width:0;margin:0;overflow-wrap:anywhere}
57
+ .privacy-manager .privacy-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
58
+ .privacy-manager .privacy-panel .privacy-accept,.privacy-manager .privacy-panel .privacy-reject{border-color:var(--accent,#315e49);background:var(--accent,#315e49);color:var(--paper,#fff)}
59
+ .privacy-manager .privacy-panel .privacy-save,.privacy-manager .privacy-panel .privacy-manage{background:transparent}
60
+ .privacy-manager .privacy-panel button:disabled{opacity:.55;cursor:not-allowed}
61
+ .privacy-manager .privacy-policy-link{width:fit-content;max-width:100%;font-size:.9rem;overflow-wrap:anywhere}
62
+ .privacy-manager .privacy-policy-link:focus-visible,.privacy-manager .privacy-settings-button:focus-visible,.privacy-manager .privacy-panel button:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
63
+ @media(max-width:560px){.privacy-manager{inset-inline-start:max(12px,env(safe-area-inset-left));width:auto;max-width:none}.privacy-manager .privacy-panel{max-height:min(68vh,40rem);max-height:min(68dvh,40rem);padding:15px}.privacy-manager .privacy-actions{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.privacy-manager .privacy-actions .privacy-manage{grid-column:1/-1;grid-row:1}.privacy-manager .privacy-actions .privacy-reject{grid-column:1;grid-row:2}.privacy-manager .privacy-actions .privacy-accept{grid-column:2;grid-row:2}.privacy-manager .privacy-panel .privacy-save{width:100%}}
41
64
  .page-builder{display:grid;gap:24px}
42
65
  .page-title h1{font-size:clamp(2rem,5vw,3.3rem);line-height:1.1;letter-spacing:-.04em}
43
66
  .hero-block{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(180px,.8fr);gap:28px;padding:clamp(26px,5vw,54px);background:var(--panel);border:1px solid var(--line);margin:0 0 24px}
@@ -165,3 +188,11 @@ body{background:radial-gradient(ellipse at 88% 2%,#ffe5df 0,transparent 32%),var
165
188
  .feature-icon{display:block;width:34px;height:34px;margin:0 0 10px;color:var(--accent);fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
166
189
  .latest-posts-pagination{justify-content:flex-start;gap:16px;margin-top:18px}
167
190
  .latest-posts-pagination a:last-child{margin-left:auto}
191
+ .hero-mascot-picture{display:block;width:min(100%,230px)}
192
+ .post-excerpt{max-width:72ch;color:var(--muted);font-size:.95rem;line-height:1.6}
193
+ .post-excerpt p{margin:.35rem 0 0}
194
+ .post-excerpt p:first-child{margin-top:0}
195
+ .post-excerpt a{color:inherit}
196
+ .post-content .markdown-video{margin:1.5rem 0}
197
+ .post-content .markdown-video video{display:block;width:100%;max-width:100%;height:auto;border-radius:10px;background:#101410}
198
+ .post-content .markdown-video figcaption{margin-top:.5rem;color:var(--muted);font-size:.9rem}
@@ -18,25 +18,48 @@ main{flex:1;min-height:0}
18
18
  .ad-slot:empty{min-height:90px}.captcha-block{min-height:65px}
19
19
 
20
20
 
21
- .privacy-manager{position:fixed;right:16px;bottom:16px;z-index:1000;color:var(--ink);font:14px/1.5 ui-sans-serif,system-ui,sans-serif}
22
- .privacy-settings-button,.privacy-panel button{font:inherit;font-weight:700;border:1px solid var(--line);border-radius:8px;padding:9px 13px;background:var(--panel,#fff);color:var(--ink);cursor:pointer}
23
- .privacy-settings-button{box-shadow:0 4px 18px #0002}
24
- .privacy-panel{width:min(28rem,calc(100vw - 32px));max-height:min(80vh,42rem);overflow:auto;padding:20px;margin-bottom:10px;border:1px solid var(--line);border-radius:12px;background:var(--paper,#fff);box-shadow:0 12px 40px #0003}
25
- .privacy-panel[hidden]{display:none}
26
- .privacy-panel h2{margin:8px 0;font-size:1.2rem}
27
- .privacy-panel p{margin:10px 0}
28
- .privacy-close{float:right}
29
- .privacy-details{display:grid;gap:8px;margin:16px 0}
30
- .privacy-service{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid var(--line);border-radius:8px}
31
- .privacy-service input{margin-top:4px;accent-color:var(--accent)}
32
- .privacy-service span span{display:inline}
33
- .privacy-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
34
- .privacy-actions button{flex:1 1 auto}
35
- .privacy-accept,.privacy-reject{font-weight:800!important}
36
- .privacy-controller,.privacy-policy-link{font-size:.9rem}
37
- .privacy-settings-button:focus-visible,.privacy-panel button:focus-visible,.privacy-panel a:focus-visible,.privacy-service input:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
38
- @media(max-width:500px){.privacy-manager{left:12px;right:12px;bottom:12px}.privacy-settings-button{float:right}.privacy-panel{width:100%}.privacy-actions button{flex:1 1 100%}}
39
-
21
+ .privacy-manager{position:fixed;inset-inline-end:max(12px,env(safe-area-inset-right));inset-block-end:max(12px,env(safe-area-inset-bottom));z-index:1000;display:grid;align-content:end;gap:8px;box-sizing:border-box;width:min(30rem,calc(100vw - 24px));max-height:calc(100vh - 24px);max-height:calc(100dvh - 24px);color:var(--ink,#1d2824);font:14px/1.45 ui-sans-serif,system-ui,sans-serif;pointer-events:none;isolation:isolate}
22
+ .privacy-manager *{box-sizing:border-box}
23
+ .privacy-manager>*{min-width:0;pointer-events:auto}
24
+ .privacy-manager .privacy-settings-button,.privacy-manager .privacy-panel button{display:inline-flex;justify-content:center;align-items:center;gap:8px;min-width:0;min-height:46px;max-width:100%;padding:10px 12px;border:1px solid var(--line,#c7d0c9);border-radius:10px;background:var(--panel,#fff);color:var(--ink,#1d2824);font:inherit;font-weight:700;line-height:1.25;text-align:center;overflow-wrap:anywhere;cursor:pointer}
25
+ .privacy-manager .privacy-settings-button{order:1;justify-self:end;box-shadow:0 4px 18px #0002}
26
+ .privacy-manager .privacy-panel{position:relative;order:0;display:grid;gap:12px;width:100%;max-height:min(68vh,40rem);max-height:min(68dvh,40rem);overflow:auto;overscroll-behavior:contain;padding:18px;border:1px solid var(--line,#c7d0c9);border-radius:14px;background:var(--paper,#fff);color:var(--ink,#1d2824);box-shadow:0 12px 40px #0003}
27
+ .privacy-manager .privacy-panel[hidden]{display:none}
28
+ .privacy-manager .privacy-panel h2{margin:0;padding-inline-end:44px;font-size:1.15rem;line-height:1.3}
29
+ .privacy-manager .privacy-panel p{margin:0;overflow-wrap:anywhere}
30
+ .privacy-manager .privacy-panel .privacy-close{position:absolute;inset-block-start:8px;inset-inline-end:8px;width:44px;min-width:44px;min-height:44px;padding:8px}
31
+ .privacy-manager .privacy-panel .privacy-close svg{width:20px;height:20px}
32
+ .privacy-manager svg{width:18px;height:18px;flex:none}
33
+ .privacy-manager .privacy-intro,.privacy-manager .privacy-essential,.privacy-manager .privacy-controller{color:var(--muted,#536159)}
34
+ .privacy-manager .privacy-service-preview,.privacy-manager .privacy-service-settings{display:grid;gap:8px;min-width:0}
35
+ .privacy-manager .privacy-preview-item,.privacy-manager .privacy-service-setting{display:grid;gap:7px;min-width:0;padding:11px;border:1px solid var(--line,#c7d0c9);border-radius:10px;background:var(--panel,#fff)}
36
+ .privacy-manager .privacy-preview-item{grid-template-columns:minmax(0,1fr) auto;align-items:start}
37
+ .privacy-manager .privacy-preview-item h3{min-width:0;margin:0;font-size:.96rem;overflow-wrap:anywhere}
38
+ .privacy-manager .privacy-preview-item p{grid-column:1/-1;color:var(--muted,#536159)}
39
+ .privacy-manager .privacy-category{justify-self:start;max-width:100%;padding:2px 7px;border-radius:999px;background:color-mix(in srgb,var(--accent,#315e49) 12%,transparent);color:var(--ink,#1d2824);font-size:.78rem;overflow-wrap:anywhere}
40
+ .privacy-manager .privacy-details{min-width:0;margin:0;border-block:1px solid var(--line,#c7d0c9)}
41
+ .privacy-manager .privacy-details>summary{padding:10px 2px;font-weight:750;cursor:pointer}
42
+ .privacy-manager .privacy-details>summary:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
43
+ .privacy-manager .privacy-service-settings{padding-block-end:10px}
44
+ .privacy-manager .privacy-service-toggle{display:flex;align-items:center;gap:11px;min-width:0;cursor:pointer}
45
+ .privacy-manager .privacy-service-toggle input{position:relative;flex:none;width:42px;height:24px;margin:0;appearance:none;border:1px solid var(--line,#748079);border-radius:999px;background:var(--line,#748079);cursor:pointer}
46
+ .privacy-manager .privacy-service-toggle input::before{position:absolute;inset-block-start:2px;inset-inline-start:2px;width:18px;height:18px;border-radius:50%;background:var(--paper,#fff);content:"";transition:transform .16s ease}
47
+ .privacy-manager .privacy-service-toggle input:checked{border-color:var(--accent,#315e49);background:var(--accent,#315e49)}
48
+ .privacy-manager .privacy-service-toggle input:checked::before{transform:translateX(18px)}
49
+ .privacy-manager .privacy-service-toggle input:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
50
+ .privacy-manager .privacy-service-toggle-text{display:grid;gap:3px;min-width:0;overflow-wrap:anywhere}
51
+ .privacy-manager .privacy-service-toggle-text strong{font-size:.96rem}
52
+ .privacy-manager .privacy-service-disclosures{display:grid;gap:6px;margin:0}
53
+ .privacy-manager .privacy-service-disclosures>div{display:grid;grid-template-columns:minmax(5.5rem,.34fr) minmax(0,1fr);gap:8px;min-width:0}
54
+ .privacy-manager .privacy-service-disclosures dt{color:var(--muted,#536159);font-weight:650}
55
+ .privacy-manager .privacy-service-disclosures dd{min-width:0;margin:0;overflow-wrap:anywhere}
56
+ .privacy-manager .privacy-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
57
+ .privacy-manager .privacy-panel .privacy-accept,.privacy-manager .privacy-panel .privacy-reject{border-color:var(--accent,#315e49);background:var(--accent,#315e49);color:var(--paper,#fff)}
58
+ .privacy-manager .privacy-panel .privacy-save,.privacy-manager .privacy-panel .privacy-manage{background:transparent}
59
+ .privacy-manager .privacy-panel button:disabled{opacity:.55;cursor:not-allowed}
60
+ .privacy-manager .privacy-policy-link{width:fit-content;max-width:100%;font-size:.9rem;overflow-wrap:anywhere}
61
+ .privacy-manager .privacy-policy-link:focus-visible,.privacy-manager .privacy-settings-button:focus-visible,.privacy-manager .privacy-panel button:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
62
+ @media(max-width:560px){.privacy-manager{inset-inline-start:max(12px,env(safe-area-inset-left));width:auto;max-width:none}.privacy-manager .privacy-panel{max-height:min(68vh,40rem);max-height:min(68dvh,40rem);padding:15px}.privacy-manager .privacy-actions{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.privacy-manager .privacy-actions .privacy-manage{grid-column:1/-1;grid-row:1}.privacy-manager .privacy-actions .privacy-reject{grid-column:1;grid-row:2}.privacy-manager .privacy-actions .privacy-accept{grid-column:2;grid-row:2}.privacy-manager .privacy-panel .privacy-save{width:100%}}
40
63
  /* Page blocks follow a section-and-widget layout, while articles keep their own reading column. */
41
64
  body{max-width:none;padding:0 clamp(18px,4vw,56px)}
42
65
  .site-header,main,.site-footer{width:min(100%,1240px);margin-inline:auto}
@@ -203,3 +226,11 @@ body{background:radial-gradient(ellipse at 92% 0%,#e9e9ff 0,transparent 33%),var
203
226
  .feature-icon{display:block;width:34px;height:34px;margin:0 0 10px;color:var(--accent);fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
204
227
  .latest-posts-pagination{justify-content:flex-start;gap:16px;margin-top:18px}
205
228
  .latest-posts-pagination a:last-child{margin-left:auto}
229
+ .hero-mascot-picture{display:block;width:min(100%,230px)}
230
+ .post-excerpt{max-width:72ch;color:var(--muted);font-size:.95rem;line-height:1.6}
231
+ .post-excerpt p{margin:.35rem 0 0}
232
+ .post-excerpt p:first-child{margin-top:0}
233
+ .post-excerpt a{color:inherit}
234
+ .post-content .markdown-video{margin:1.5rem 0}
235
+ .post-content .markdown-video video{display:block;width:100%;max-width:100%;height:auto;border-radius:10px;background:#101410}
236
+ .post-content .markdown-video figcaption{margin-top:.5rem;color:var(--muted);font-size:.9rem}
@@ -18,25 +18,48 @@ main{flex:1;min-height:0}
18
18
  .ad-slot:empty{min-height:90px}.captcha-block{min-height:65px}
19
19
 
20
20
 
21
- .privacy-manager{position:fixed;right:16px;bottom:16px;z-index:1000;color:var(--ink);font:14px/1.5 ui-sans-serif,system-ui,sans-serif}
22
- .privacy-settings-button,.privacy-panel button{font:inherit;font-weight:700;border:1px solid var(--line);border-radius:8px;padding:9px 13px;background:var(--panel,#fff);color:var(--ink);cursor:pointer}
23
- .privacy-settings-button{box-shadow:0 4px 18px #0002}
24
- .privacy-panel{width:min(28rem,calc(100vw - 32px));max-height:min(80vh,42rem);overflow:auto;padding:20px;margin-bottom:10px;border:1px solid var(--line);border-radius:12px;background:var(--paper,#fff);box-shadow:0 12px 40px #0003}
25
- .privacy-panel[hidden]{display:none}
26
- .privacy-panel h2{margin:8px 0;font-size:1.2rem}
27
- .privacy-panel p{margin:10px 0}
28
- .privacy-close{float:right}
29
- .privacy-details{display:grid;gap:8px;margin:16px 0}
30
- .privacy-service{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid var(--line);border-radius:8px}
31
- .privacy-service input{margin-top:4px;accent-color:var(--accent)}
32
- .privacy-service span span{display:inline}
33
- .privacy-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
34
- .privacy-actions button{flex:1 1 auto}
35
- .privacy-accept,.privacy-reject{font-weight:800!important}
36
- .privacy-controller,.privacy-policy-link{font-size:.9rem}
37
- .privacy-settings-button:focus-visible,.privacy-panel button:focus-visible,.privacy-panel a:focus-visible,.privacy-service input:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
38
- @media(max-width:500px){.privacy-manager{left:12px;right:12px;bottom:12px}.privacy-settings-button{float:right}.privacy-panel{width:100%}.privacy-actions button{flex:1 1 100%}}
39
-
21
+ .privacy-manager{position:fixed;inset-inline-end:max(12px,env(safe-area-inset-right));inset-block-end:max(12px,env(safe-area-inset-bottom));z-index:1000;display:grid;align-content:end;gap:8px;box-sizing:border-box;width:min(30rem,calc(100vw - 24px));max-height:calc(100vh - 24px);max-height:calc(100dvh - 24px);color:var(--ink,#1d2824);font:14px/1.45 ui-sans-serif,system-ui,sans-serif;pointer-events:none;isolation:isolate}
22
+ .privacy-manager *{box-sizing:border-box}
23
+ .privacy-manager>*{min-width:0;pointer-events:auto}
24
+ .privacy-manager .privacy-settings-button,.privacy-manager .privacy-panel button{display:inline-flex;justify-content:center;align-items:center;gap:8px;min-width:0;min-height:46px;max-width:100%;padding:10px 12px;border:1px solid var(--line,#c7d0c9);border-radius:10px;background:var(--panel,#fff);color:var(--ink,#1d2824);font:inherit;font-weight:700;line-height:1.25;text-align:center;overflow-wrap:anywhere;cursor:pointer}
25
+ .privacy-manager .privacy-settings-button{order:1;justify-self:end;box-shadow:0 4px 18px #0002}
26
+ .privacy-manager .privacy-panel{position:relative;order:0;display:grid;gap:12px;width:100%;max-height:min(68vh,40rem);max-height:min(68dvh,40rem);overflow:auto;overscroll-behavior:contain;padding:18px;border:1px solid var(--line,#c7d0c9);border-radius:14px;background:var(--paper,#fff);color:var(--ink,#1d2824);box-shadow:0 12px 40px #0003}
27
+ .privacy-manager .privacy-panel[hidden]{display:none}
28
+ .privacy-manager .privacy-panel h2{margin:0;padding-inline-end:44px;font-size:1.15rem;line-height:1.3}
29
+ .privacy-manager .privacy-panel p{margin:0;overflow-wrap:anywhere}
30
+ .privacy-manager .privacy-panel .privacy-close{position:absolute;inset-block-start:8px;inset-inline-end:8px;width:44px;min-width:44px;min-height:44px;padding:8px}
31
+ .privacy-manager .privacy-panel .privacy-close svg{width:20px;height:20px}
32
+ .privacy-manager svg{width:18px;height:18px;flex:none}
33
+ .privacy-manager .privacy-intro,.privacy-manager .privacy-essential,.privacy-manager .privacy-controller{color:var(--muted,#536159)}
34
+ .privacy-manager .privacy-service-preview,.privacy-manager .privacy-service-settings{display:grid;gap:8px;min-width:0}
35
+ .privacy-manager .privacy-preview-item,.privacy-manager .privacy-service-setting{display:grid;gap:7px;min-width:0;padding:11px;border:1px solid var(--line,#c7d0c9);border-radius:10px;background:var(--panel,#fff)}
36
+ .privacy-manager .privacy-preview-item{grid-template-columns:minmax(0,1fr) auto;align-items:start}
37
+ .privacy-manager .privacy-preview-item h3{min-width:0;margin:0;font-size:.96rem;overflow-wrap:anywhere}
38
+ .privacy-manager .privacy-preview-item p{grid-column:1/-1;color:var(--muted,#536159)}
39
+ .privacy-manager .privacy-category{justify-self:start;max-width:100%;padding:2px 7px;border-radius:999px;background:color-mix(in srgb,var(--accent,#315e49) 12%,transparent);color:var(--ink,#1d2824);font-size:.78rem;overflow-wrap:anywhere}
40
+ .privacy-manager .privacy-details{min-width:0;margin:0;border-block:1px solid var(--line,#c7d0c9)}
41
+ .privacy-manager .privacy-details>summary{padding:10px 2px;font-weight:750;cursor:pointer}
42
+ .privacy-manager .privacy-details>summary:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
43
+ .privacy-manager .privacy-service-settings{padding-block-end:10px}
44
+ .privacy-manager .privacy-service-toggle{display:flex;align-items:center;gap:11px;min-width:0;cursor:pointer}
45
+ .privacy-manager .privacy-service-toggle input{position:relative;flex:none;width:42px;height:24px;margin:0;appearance:none;border:1px solid var(--line,#748079);border-radius:999px;background:var(--line,#748079);cursor:pointer}
46
+ .privacy-manager .privacy-service-toggle input::before{position:absolute;inset-block-start:2px;inset-inline-start:2px;width:18px;height:18px;border-radius:50%;background:var(--paper,#fff);content:"";transition:transform .16s ease}
47
+ .privacy-manager .privacy-service-toggle input:checked{border-color:var(--accent,#315e49);background:var(--accent,#315e49)}
48
+ .privacy-manager .privacy-service-toggle input:checked::before{transform:translateX(18px)}
49
+ .privacy-manager .privacy-service-toggle input:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
50
+ .privacy-manager .privacy-service-toggle-text{display:grid;gap:3px;min-width:0;overflow-wrap:anywhere}
51
+ .privacy-manager .privacy-service-toggle-text strong{font-size:.96rem}
52
+ .privacy-manager .privacy-service-disclosures{display:grid;gap:6px;margin:0}
53
+ .privacy-manager .privacy-service-disclosures>div{display:grid;grid-template-columns:minmax(5.5rem,.34fr) minmax(0,1fr);gap:8px;min-width:0}
54
+ .privacy-manager .privacy-service-disclosures dt{color:var(--muted,#536159);font-weight:650}
55
+ .privacy-manager .privacy-service-disclosures dd{min-width:0;margin:0;overflow-wrap:anywhere}
56
+ .privacy-manager .privacy-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
57
+ .privacy-manager .privacy-panel .privacy-accept,.privacy-manager .privacy-panel .privacy-reject{border-color:var(--accent,#315e49);background:var(--accent,#315e49);color:var(--paper,#fff)}
58
+ .privacy-manager .privacy-panel .privacy-save,.privacy-manager .privacy-panel .privacy-manage{background:transparent}
59
+ .privacy-manager .privacy-panel button:disabled{opacity:.55;cursor:not-allowed}
60
+ .privacy-manager .privacy-policy-link{width:fit-content;max-width:100%;font-size:.9rem;overflow-wrap:anywhere}
61
+ .privacy-manager .privacy-policy-link:focus-visible,.privacy-manager .privacy-settings-button:focus-visible,.privacy-manager .privacy-panel button:focus-visible{outline:3px solid var(--accent,#315e49);outline-offset:3px}
62
+ @media(max-width:560px){.privacy-manager{inset-inline-start:max(12px,env(safe-area-inset-left));width:auto;max-width:none}.privacy-manager .privacy-panel{max-height:min(68vh,40rem);max-height:min(68dvh,40rem);padding:15px}.privacy-manager .privacy-actions{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.privacy-manager .privacy-actions .privacy-manage{grid-column:1/-1;grid-row:1}.privacy-manager .privacy-actions .privacy-reject{grid-column:1;grid-row:2}.privacy-manager .privacy-actions .privacy-accept{grid-column:2;grid-row:2}.privacy-manager .privacy-panel .privacy-save{width:100%}}
40
63
  .page-builder{display:grid;gap:22px}.page-title h1{font-size:clamp(2rem,5vw,3.5rem);line-height:1.08;letter-spacing:-.04em}
41
64
  .hero-block{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(190px,.8fr);align-items:center;gap:28px;padding:clamp(28px,6vw,70px);background:linear-gradient(145deg,#202a48,#171e35);border:1px solid var(--line);border-radius:18px;margin:0}
42
65
  .hero-block:not(.hero-block--split){display:block}.hero-highlights{display:grid;gap:10px;list-style:none;padding:0}.hero-highlights li,.feature-card,.steps-widget li,.privacy-service-list li,.link-list li{padding:18px;background:var(--panel);border:1px solid var(--line);border-radius:12px}
@@ -158,3 +181,11 @@ body{background:radial-gradient(ellipse at 14% 0%,#302052 0,transparent 38%),var
158
181
  .feature-icon{display:block;width:34px;height:34px;margin:0 0 10px;color:var(--accent);fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
159
182
  .latest-posts-pagination{justify-content:flex-start;gap:16px;margin-top:18px}
160
183
  .latest-posts-pagination a:last-child{margin-left:auto}
184
+ .hero-mascot-picture{display:block;width:min(100%,230px)}
185
+ .post-excerpt{max-width:72ch;color:var(--muted);font-size:.95rem;line-height:1.6}
186
+ .post-excerpt p{margin:.35rem 0 0}
187
+ .post-excerpt p:first-child{margin-top:0}
188
+ .post-excerpt a{color:inherit}
189
+ .post-content .markdown-video{margin:1.5rem 0}
190
+ .post-content .markdown-video video{display:block;width:100%;max-width:100%;height:auto;border-radius:10px;background:#101410}
191
+ .post-content .markdown-video figcaption{margin-top:.5rem;color:var(--muted);font-size:.9rem}
package/CHANGELOG.txt DELETED
@@ -1,12 +0,0 @@
1
- EdgePress Changelog
2
- ===================
3
-
4
- 2026.9.6 — 2026-09-25
5
- ---------------------
6
-
7
- - Added a shared, locally hosted SVG icon library with 22 reusable icons. Theme navigation uses common icons, and feature-grid cards can opt into named icons.
8
- - Added the EdgePress page mark, folded-paper sparrow mascot, and a 1200 × 630 Open Graph image. Themes include the mark as their favicon and show the mascot on the homepage.
9
- - Added accessible older/newer archive navigation for paginated homepage post lists.
10
- - Added localized post author metadata, visible bylines, and matching BlogPosting structured data.
11
- - Removed stale demo-video references from page and post content. No video assets are generated by this release.
12
- - Kept icons, branding, and share assets local, with no new runtime dependencies or third-party asset requests.