@anthusai/papyrus 1.0.0-next.2 → 1.0.0-next.21

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 (140) hide show
  1. package/amplify/auth/resource.js +17 -4
  2. package/amplify/auth/resource.ts +21 -4
  3. package/amplify/data/resource.js +52 -70
  4. package/amplify/data/resource.ts +57 -70
  5. package/amplify/data/schema.js +52 -58
  6. package/amplify/data/schema.ts +57 -58
  7. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
  8. package/amplify/functions/console-chat-responder/resource.js +0 -7
  9. package/amplify/functions/console-chat-responder/resource.ts +0 -8
  10. package/amplify/functions/console-chat-responder/src/main.rs +0 -9
  11. package/amplify/functions/content-actions/handler.py +149 -0
  12. package/amplify/functions/content-actions/requirements.txt +4 -0
  13. package/amplify/functions/content-actions/resource.js +40 -0
  14. package/amplify/functions/content-actions/resource.ts +54 -0
  15. package/amplify/functions/shared/python-bundle.js +17 -2
  16. package/amplify/functions/shared/python-bundle.ts +23 -2
  17. package/amplify/rebuild-trigger-settings.js +22 -0
  18. package/amplify/rebuild-trigger-settings.ts +42 -0
  19. package/amplify/site-backend-config.ts +12 -1
  20. package/amplify/site-backend.js +51 -27
  21. package/amplify/site-backend.ts +66 -34
  22. package/amplify/storage/resource.js +3 -0
  23. package/amplify/storage/resource.ts +3 -0
  24. package/app/%5F_preview/[[...path]]/route.ts +8 -0
  25. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +4 -2
  26. package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
  27. package/app/api/revalidate/route.ts +8 -7
  28. package/app/articles/[slug]/page.tsx +6 -4
  29. package/app/blog-rhythm.css +278 -0
  30. package/app/globals.css +22 -4
  31. package/app/layout.tsx +24 -12
  32. package/app/newsroom/articles/[id]/page.tsx +15 -0
  33. package/app/newsroom/articles/page.tsx +13 -0
  34. package/app/page.tsx +12 -4
  35. package/app/robots.ts +11 -0
  36. package/bin/papyrus-app.mjs +12 -2
  37. package/bin/papyrus-infra.mjs +74 -18
  38. package/components/archive-shell.tsx +2 -22
  39. package/components/article-video.tsx +247 -3
  40. package/components/blog-page-background.tsx +23 -6
  41. package/components/markus-ir-preview.tsx +300 -0
  42. package/components/news-desk-page.tsx +1 -4
  43. package/components/newspaper.tsx +6 -2
  44. package/components/newsroom-access-gate.tsx +66 -0
  45. package/components/newsroom-article-editor.tsx +741 -0
  46. package/components/newsroom-articles-view.tsx +129 -0
  47. package/components/newsroom-ops-overview.tsx +28 -11
  48. package/components/newsroom-ops-shell.tsx +3 -1
  49. package/components/pictogram-figure.tsx +9 -2
  50. package/components/presentation-header.tsx +135 -0
  51. package/components/presentation-rhythm-hrule.tsx +17 -0
  52. package/components/settings-page.tsx +2 -1
  53. package/components/staging-banner.tsx +11 -0
  54. package/components/topic-steering-workspace.tsx +1 -59
  55. package/components/use-blog-header-obstacles.ts +81 -0
  56. package/components/use-brand-fonts-ready.ts +28 -0
  57. package/components/use-rhythm-overlay.ts +29 -0
  58. package/infra/amplify-app-shell.js +285 -80
  59. package/infra/amplify-app-shell.ts +326 -113
  60. package/infra/build-specs.js +94 -0
  61. package/infra/build-specs.ts +141 -0
  62. package/infra/github-oidc-provider.js +24 -0
  63. package/infra/github-oidc-provider.ts +30 -0
  64. package/infra/index.js +3 -0
  65. package/infra/site-config.js +273 -0
  66. package/infra/site-config.ts +336 -0
  67. package/lib/blog-feature-solver.ts +459 -0
  68. package/lib/blog-rhythm.ts +134 -0
  69. package/lib/brand-fonts-watch.ts +43 -0
  70. package/lib/brand-fonts.ts +112 -0
  71. package/lib/cached-content-repository.ts +14 -1
  72. package/lib/category-repository.ts +1 -93
  73. package/lib/content-actions-client.ts +98 -0
  74. package/lib/content-actions-demo.ts +190 -0
  75. package/lib/content-repository.ts +6 -0
  76. package/lib/content-source-context.ts +26 -0
  77. package/lib/content-types.ts +4 -0
  78. package/lib/edition-routes.ts +31 -29
  79. package/lib/empty-theme.css +1 -0
  80. package/lib/graphql-content-repository.ts +239 -43
  81. package/lib/layout-scenarios.ts +8 -8
  82. package/lib/newspaper-layout.ts +3 -2
  83. package/lib/newsroom-articles.ts +325 -0
  84. package/lib/newsroom-demo-articles.ts +90 -0
  85. package/lib/newsroom-media-backend.ts +142 -0
  86. package/lib/newsroom-media.ts +182 -0
  87. package/lib/newsroom-nav.ts +3 -0
  88. package/lib/papyrus-web-locations.ts +7 -6
  89. package/lib/presentation-footer.ts +3 -2
  90. package/lib/pretext-layout.ts +12 -3
  91. package/lib/reader-base-path.ts +24 -0
  92. package/lib/reader-static-params.ts +8 -0
  93. package/lib/renderer.ts +7 -0
  94. package/lib/revalidate-secret.ts +59 -0
  95. package/lib/root-route-redirect.ts +16 -0
  96. package/lib/site-brand-tenant.ts +3 -22
  97. package/lib/site-brand.ts +95 -42
  98. package/lib/site-env.ts +52 -0
  99. package/lib/staging-gate.ts +37 -0
  100. package/lib/staging-gated-path.ts +22 -0
  101. package/lib/staging-preview-object.ts +98 -0
  102. package/lib/video-mode.ts +42 -0
  103. package/lib/video-script.ts +77 -0
  104. package/middleware.ts +56 -42
  105. package/package.json +40 -1
  106. package/renderers/pretext/article-page.tsx +40 -9
  107. package/renderers/pretext/presentation-shared.tsx +113 -0
  108. package/renderers/pretext/presentation-shell.tsx +18 -112
  109. package/renderers/pretext/rhythm-article-page.tsx +165 -0
  110. package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
  111. package/renderers/pretext/rhythm-shell-style.ts +35 -0
  112. package/routes.manifest.json +37 -2
  113. package/scripts/videoml/build-browser-bundle.mjs +22 -0
  114. package/scripts/videoml/build-preview-bundle.mjs +50 -0
  115. package/scripts/videoml/bundle-common.mjs +129 -0
  116. package/scripts/videoml/preview/preview-entry.tsx +381 -0
  117. package/scripts/videoml/preview/preview-stage-fit.ts +28 -0
  118. package/scripts/videoml/preview/preview-timing.ts +42 -0
  119. package/scripts/videoml/preview.html +77 -0
  120. package/scripts/videoml/standard-entry.tsx +46 -0
  121. package/src/sync.mjs +30 -6
  122. package/src/with-papyrus.mjs +15 -2
  123. package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
  124. package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
  125. package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
  126. package/publications/anth_us/brand.ts +0 -68
  127. package/publications/anth_us/theme.css +0 -66
  128. package/publications/pilobol_us/brand.ts +0 -37
  129. package/publications/pilobol_us/theme.css +0 -140
  130. package/publications/threat_intelligence/blog-defense/graph.ts +0 -636
  131. package/publications/threat_intelligence/blog-defense/layout.ts +0 -934
  132. package/publications/threat_intelligence/blog-defense/page-background.tsx +0 -590
  133. package/publications/threat_intelligence/brand.ts +0 -40
  134. package/publications/threat_intelligence/pictograms/art.tsx +0 -608
  135. package/publications/threat_intelligence/pictograms/figure.tsx +0 -82
  136. package/publications/threat_intelligence/pictograms/registry.ts +0 -30
  137. package/publications/threat_intelligence/pictograms/system.tsx +0 -305
  138. package/publications/threat_intelligence/seed/seed-edition-content.json +0 -629
  139. package/publications/threat_intelligence/theme.css +0 -1440
  140. /package/{publications/papyrus/theme.css → app/papyrus-theme.css} +0 -0
@@ -1,608 +0,0 @@
1
- "use client";
2
-
3
- import { motion } from "motion/react";
4
- import type { CSSProperties, ReactElement } from "react";
5
- import type { ThreatIntelligencePictogramSlug } from "./registry";
6
- import {
7
- Box,
8
- Line,
9
- Node,
10
- PictogramSvg,
11
- pointsPath,
12
- type PictogramMotion,
13
- type PictogramPalette,
14
- } from "./system";
15
-
16
- export type RegisteredPictogramProps = {
17
- alt: string;
18
- timing: PictogramMotion;
19
- palette: PictogramPalette;
20
- };
21
-
22
- export type PictogramRegistryEntry = {
23
- aspectRatio?: number;
24
- render: (props: RegisteredPictogramProps) => ReactElement;
25
- };
26
-
27
- function BalanceShiftPictogram({ alt, palette, timing }: RegisteredPictogramProps) {
28
- const scalePalette = {
29
- ...palette,
30
- edge: "var(--foreground-strong)",
31
- node: "var(--foreground-strong)",
32
- muted: "color-mix(in srgb, var(--foreground-strong) 54%, var(--background) 46%)",
33
- };
34
- const nodeTransition = timing.prefersReducedMotion
35
- ? { duration: 0 }
36
- : {
37
- duration: timing.cycleS,
38
- repeat: Infinity,
39
- ease: "easeInOut",
40
- times: [0, 0.24, 0.42, 0.62, 1],
41
- delay: timing.delayS(900),
42
- };
43
- const animationDelay = `${timing.delayS()}s`;
44
- const beamStyle = {
45
- animation: timing.prefersReducedMotion ? "none" : `ti-scale-beam ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
46
- transform: timing.prefersReducedMotion ? "rotate(7deg)" : undefined,
47
- transformBox: "view-box",
48
- transformOrigin: "160px 118px",
49
- } as CSSProperties;
50
- const leftPanStyle = {
51
- animation: timing.prefersReducedMotion ? "none" : `ti-scale-left-pan ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
52
- transform: timing.prefersReducedMotion ? "translateY(-10px)" : undefined,
53
- } as CSSProperties;
54
- const rightPanStyle = {
55
- animation: timing.prefersReducedMotion ? "none" : `ti-scale-right-pan ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
56
- transform: timing.prefersReducedMotion ? "translateY(12px)" : undefined,
57
- } as CSSProperties;
58
-
59
- return (
60
- <PictogramSvg alt={alt} viewBox="0 0 320 320">
61
- <style>
62
- {`
63
- @keyframes ti-scale-beam {
64
- 0%, 16%, 100% { transform: rotate(0deg); }
65
- 38%, 82% { transform: rotate(7deg); }
66
- }
67
-
68
- @keyframes ti-scale-left-pan {
69
- 0%, 16%, 100% { transform: translateY(0); }
70
- 38%, 82% { transform: translateY(-10px); }
71
- }
72
-
73
- @keyframes ti-scale-right-pan {
74
- 0%, 16%, 100% { transform: translateY(0); }
75
- 38%, 82% { transform: translateY(12px); }
76
- }
77
- `}
78
- </style>
79
- <g transform="translate(160 166) scale(1.14) translate(-160 -166)">
80
- <Line d="M 160 86 V 246" palette={scalePalette} width={8} />
81
- <Line d="M 124 246 H 196" palette={scalePalette} width={9} />
82
- <g>
83
- <g style={beamStyle}>
84
- <path
85
- d="M 72 118 L 248 118"
86
- fill="none"
87
- stroke={scalePalette.edge}
88
- strokeLinecap="round"
89
- strokeLinejoin="round"
90
- strokeWidth={8}
91
- />
92
- <path
93
- d="M 160 118 L 248 118"
94
- fill="none"
95
- stroke={palette.accent}
96
- strokeLinecap="round"
97
- strokeLinejoin="round"
98
- strokeWidth={8}
99
- />
100
- </g>
101
- <g style={leftPanStyle}>
102
- <path
103
- d="M 78 118 L 42 198 H 128 L 78 118 Z"
104
- fill="none"
105
- stroke={scalePalette.edge}
106
- strokeLinejoin="round"
107
- strokeWidth={6}
108
- />
109
- <circle cx={70} cy={180} fill={scalePalette.node} opacity={0.88} r={15} />
110
- <circle cx={92} cy={190} fill={scalePalette.muted} opacity={0.86} r={11} />
111
- </g>
112
- <g style={rightPanStyle}>
113
- <path
114
- d="M 246 118 L 190 212 H 288 L 246 118 Z"
115
- fill="none"
116
- stroke={palette.accent}
117
- strokeLinejoin="round"
118
- strokeWidth={7}
119
- />
120
- <motion.circle
121
- animate={timing.prefersReducedMotion ? { opacity: 0.78, scale: 1 } : { opacity: [0.42, 0.42, 0.62, 0.82, 0.82], scale: [1, 1, 1.02, 1.04, 1.04] }}
122
- cx={208}
123
- cy={198}
124
- fill={palette.accent}
125
- initial={false}
126
- opacity={0.78}
127
- r={13}
128
- transition={nodeTransition as any}
129
- />
130
- <motion.circle
131
- animate={timing.prefersReducedMotion ? { opacity: 0.9, scale: 1 } : { opacity: [0.5, 0.5, 0.76, 0.95, 0.95], scale: [1, 1, 1.03, 1.07, 1.07] }}
132
- cx={232}
133
- cy={188}
134
- fill={palette.accent}
135
- initial={false}
136
- opacity={0.9}
137
- r={14}
138
- transition={{ ...nodeTransition, delay: timing.delayS(1200) } as any}
139
- />
140
- <motion.circle
141
- animate={timing.prefersReducedMotion ? { opacity: 1, scale: 1 } : { opacity: [0.55, 0.55, 0.84, 1, 1], scale: [1, 1, 1.03, 1.1, 1.1] }}
142
- cx={254}
143
- cy={200}
144
- fill={palette.accent}
145
- initial={false}
146
- opacity={1}
147
- r={16}
148
- transition={{ ...nodeTransition, delay: timing.delayS(1550) } as any}
149
- />
150
- <motion.circle
151
- animate={timing.prefersReducedMotion ? { opacity: 0.82, scale: 1 } : { opacity: [0.38, 0.38, 0.68, 0.88, 0.88], scale: [1, 1, 1.02, 1.06, 1.06] }}
152
- cx={274}
153
- cy={189}
154
- fill={palette.accent}
155
- initial={false}
156
- opacity={0.82}
157
- r={12}
158
- transition={{ ...nodeTransition, delay: timing.delayS(1900) } as any}
159
- />
160
- </g>
161
- </g>
162
- <circle cx={160} cy={118} fill={palette.frame} r={23} stroke={scalePalette.edge} strokeWidth={7} />
163
- </g>
164
- </PictogramSvg>
165
- );
166
- }
167
-
168
- function NewsroomFlowPictogram({ alt, palette, timing }: RegisteredPictogramProps) {
169
- const newsroomPalette = {
170
- ...palette,
171
- edge: "var(--foreground-strong)",
172
- node: "var(--foreground-strong)",
173
- muted: "color-mix(in srgb, var(--foreground-strong) 54%, var(--background) 46%)",
174
- };
175
- const animationDelay = `${timing.delayS()}s`;
176
- const wheelStyle = {
177
- animation: timing.prefersReducedMotion ? "none" : `ti-newsroom-wheel ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
178
- transform: timing.prefersReducedMotion ? "rotate(7deg)" : undefined,
179
- transformBox: "view-box",
180
- transformOrigin: "160px 70px",
181
- } as CSSProperties;
182
- const dotStyle = {
183
- animation: timing.prefersReducedMotion ? "none" : `ti-newsroom-dot ${timing.cycleS}s linear ${timing.delayS(1200)}s infinite`,
184
- transform: timing.prefersReducedMotion ? "translate(196px, 0)" : undefined,
185
- } as CSSProperties;
186
- const activeLineStyle = {
187
- animation: timing.prefersReducedMotion ? "none" : `ti-newsroom-flow ${timing.cycleS}s linear ${timing.delayS(1200)}s infinite`,
188
- opacity: timing.prefersReducedMotion ? 0.85 : undefined,
189
- strokeDashoffset: timing.prefersReducedMotion ? -224 : undefined,
190
- } as CSSProperties;
191
- const flowCenters = [
192
- [62, 214],
193
- [160, 160],
194
- [258, 214],
195
- [160, 264],
196
- [62, 214],
197
- ] as Array<[number, number]>;
198
- const flowPath = pointsPath(flowCenters);
199
- const states = [
200
- { cx: 62, cy: 214, r: 30, inner: "M 48 214 H 76" },
201
- { cx: 160, cy: 160, r: 32, inner: "M 146 152 H 174 M 146 168 H 174" },
202
- { cx: 258, cy: 214, r: 30, inner: "M 246 204 H 270 M 246 214 H 270 M 246 224 H 270" },
203
- { cx: 160, cy: 264, r: 30, inner: "M 148 256 L 160 268 L 176 250" },
204
- ];
205
-
206
- return (
207
- <PictogramSvg alt={alt} viewBox="0 0 320 320">
208
- <style>
209
- {`
210
- @keyframes ti-newsroom-wheel {
211
- 0%, 16%, 100% { transform: rotate(-5deg); }
212
- 34% { transform: rotate(8deg); }
213
- 54% { transform: rotate(-3deg); }
214
- 72% { transform: rotate(6deg); }
215
- }
216
-
217
- @keyframes ti-newsroom-dot {
218
- 0%, 100% { transform: translate(0, 0); }
219
- 18% { transform: translate(98px, -54px); }
220
- 36% { transform: translate(196px, 0); }
221
- 54% { transform: translate(98px, 50px); }
222
- 72% { transform: translate(0, 0); }
223
- }
224
-
225
- @keyframes ti-newsroom-flow {
226
- 0% { opacity: 0.2; stroke-dashoffset: 0; }
227
- 18% { opacity: 0.95; stroke-dashoffset: -112; }
228
- 36% { opacity: 0.95; stroke-dashoffset: -224; }
229
- 54% { opacity: 0.95; stroke-dashoffset: -334; }
230
- 72%, 100% { opacity: 0.2; stroke-dashoffset: -444; }
231
- }
232
- `}
233
- </style>
234
- <g transform="translate(160 172) scale(1.12) translate(-160 -172)">
235
- <g style={wheelStyle}>
236
- <circle cx={160} cy={70} fill="none" r={44} stroke={newsroomPalette.edge} strokeWidth={8} />
237
- <circle cx={160} cy={70} fill={palette.frame} r={8} stroke={newsroomPalette.edge} strokeWidth={5} />
238
- <Line d="M 160 70 V 28" palette={newsroomPalette} width={5} />
239
- <Line d="M 160 70 L 124 94" palette={newsroomPalette} width={5} />
240
- <Line d="M 160 70 L 196 94" palette={newsroomPalette} width={5} />
241
- </g>
242
- <Line d="M 160 116 V 128" opacity={0.7} palette={newsroomPalette} tone="muted" width={4} />
243
- <Line d={flowPath} opacity={0.42} palette={newsroomPalette} tone="muted" width={5} />
244
- <path
245
- d={flowPath}
246
- fill="none"
247
- stroke={palette.accent}
248
- strokeDasharray="44 440"
249
- strokeLinecap="round"
250
- strokeLinejoin="round"
251
- strokeWidth={5}
252
- style={activeLineStyle}
253
- />
254
- {states.map((state, index) => (
255
- <g key={`${state.cx}-${state.cy}`}>
256
- <circle cx={state.cx} cy={state.cy} fill={palette.frame} r={state.r} stroke={newsroomPalette.edge} strokeWidth={6} />
257
- <Line d={state.inner} opacity={index === 2 ? 0.9 : 0.72} palette={index === 2 ? palette : newsroomPalette} tone={index === 2 ? "accent" : "muted"} width={4} />
258
- <Node palette={index === 2 ? palette : newsroomPalette} tone={index === 2 ? "accent" : "node"} x={state.cx} y={state.cy} />
259
- </g>
260
- ))}
261
- <circle cx={62} cy={214} fill={palette.accent} r={7} style={dotStyle} />
262
- </g>
263
- </PictogramSvg>
264
- );
265
- }
266
-
267
- function AwsDiscoveryPictogram({ alt, palette, timing }: RegisteredPictogramProps) {
268
- const awsPalette = {
269
- ...palette,
270
- edge: "var(--foreground-strong)",
271
- node: "var(--foreground-strong)",
272
- muted: "color-mix(in srgb, var(--foreground-strong) 54%, var(--background) 46%)",
273
- };
274
- const riskStyle = (offsetMs: number, resting = false) =>
275
- ({
276
- animation: timing.prefersReducedMotion ? "none" : `ti-aws-risk ${timing.cycleS}s ease-in-out ${timing.delayS(offsetMs)}s infinite`,
277
- opacity: timing.prefersReducedMotion && resting ? 1 : timing.prefersReducedMotion ? 0.86 : 0.18,
278
- transformBox: "fill-box",
279
- transformOrigin: "center",
280
- }) as CSSProperties;
281
- const buckets = [
282
- { x: 92, y: 74, risk: null },
283
- { x: 160, y: 74, risk: null },
284
- { x: 228, y: 74, risk: "warning", offset: 2200, markerDx: 15, markerDy: -20 },
285
- { x: 92, y: 150, risk: "warning", offset: 4200 },
286
- { x: 160, y: 150, risk: null },
287
- { x: 228, y: 150, risk: null },
288
- { x: 92, y: 226, risk: null },
289
- { x: 160, y: 226, risk: "accent", offset: 6200, markerDx: 24, markerDy: -16, markerR: 20 },
290
- { x: 228, y: 226, risk: "accent", offset: 8200, markerDx: 12, markerDy: -24 },
291
- ];
292
-
293
- return (
294
- <PictogramSvg alt={alt} viewBox="0 0 320 320">
295
- <style>
296
- {`
297
- @keyframes ti-aws-risk {
298
- 0%, 18%, 100% { opacity: 0.18; transform: scale(0.9); }
299
- 26% { opacity: 1; transform: scale(1.14); }
300
- 34%, 78% { opacity: 1; transform: scale(1); }
301
- 88% { opacity: 0.42; transform: scale(0.96); }
302
- }
303
- `}
304
- </style>
305
- <g transform="translate(160 160) scale(1.14) translate(-160 -150)">
306
- {buckets.map((bucket) => {
307
- const stroke = bucket.risk === "accent" ? palette.accent : bucket.risk === "warning" ? palette.warning : awsPalette.edge;
308
- const markerX = bucket.x + (bucket.markerDx ?? 20);
309
- const markerY = bucket.y + (bucket.markerDy ?? -19);
310
- const markerR = bucket.markerR ?? 10;
311
- return (
312
- <g key={`aws-bucket-${bucket.x}-${bucket.y}`}>
313
- <path
314
- d={`M ${bucket.x - 22} ${bucket.y - 16} C ${bucket.x - 22} ${bucket.y - 27}, ${bucket.x + 22} ${bucket.y - 27}, ${bucket.x + 22} ${bucket.y - 16} V ${bucket.y + 18} C ${bucket.x + 22} ${bucket.y + 29}, ${bucket.x - 22} ${bucket.y + 29}, ${bucket.x - 22} ${bucket.y + 18} Z`}
315
- fill={palette.frame}
316
- stroke={awsPalette.edge}
317
- strokeLinejoin="round"
318
- strokeWidth={5}
319
- />
320
- <path
321
- d={`M ${bucket.x - 22} ${bucket.y - 16} C ${bucket.x - 22} ${bucket.y - 5}, ${bucket.x + 22} ${bucket.y - 5}, ${bucket.x + 22} ${bucket.y - 16}`}
322
- fill="none"
323
- opacity={0.72}
324
- stroke={awsPalette.muted}
325
- strokeLinecap="round"
326
- strokeWidth={4}
327
- />
328
- <Node palette={awsPalette} x={bucket.x} y={bucket.y + 2} />
329
- {bucket.risk ? (
330
- <g style={riskStyle(bucket.offset ?? 0, bucket.risk === "accent")}>
331
- <circle cx={markerX} cy={markerY} fill={stroke} r={markerR} />
332
- <path d={`M ${markerX - markerR * 0.8} ${markerY} H ${markerX + markerR * 0.8} M ${markerX} ${markerY - markerR * 0.8} V ${markerY + markerR * 0.8}`} stroke={palette.frame} strokeLinecap="round" strokeWidth={markerR >= 16 ? 4.8 : 3.4} />
333
- </g>
334
- ) : null}
335
- </g>
336
- );
337
- })}
338
- </g>
339
- </PictogramSvg>
340
- );
341
- }
342
-
343
- function AzureBlastRadiusPictogram({ alt, palette, timing }: RegisteredPictogramProps) {
344
- const azurePalette = {
345
- ...palette,
346
- edge: "var(--foreground-strong)",
347
- node: "var(--foreground-strong)",
348
- muted: "color-mix(in srgb, var(--foreground-strong) 54%, var(--background) 46%)",
349
- };
350
- const animationDelay = `${timing.delayS(1000)}s`;
351
- const pathStyle = {
352
- animation: timing.prefersReducedMotion ? "none" : `ti-azure-path ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
353
- opacity: timing.prefersReducedMotion ? 0.95 : undefined,
354
- strokeDashoffset: timing.prefersReducedMotion ? -270 : undefined,
355
- } as CSSProperties;
356
- const riskStyle = (offsetMs: number) =>
357
- ({
358
- animation: timing.prefersReducedMotion ? "none" : `ti-azure-risk ${timing.cycleS}s ease-in-out ${timing.delayS(offsetMs)}s infinite`,
359
- opacity: timing.prefersReducedMotion ? 0.95 : 0.18,
360
- transformBox: "fill-box",
361
- transformOrigin: "center",
362
- }) as CSSProperties;
363
-
364
- return (
365
- <PictogramSvg alt={alt} viewBox="0 0 320 320">
366
- <style>
367
- {`
368
- @keyframes ti-azure-path {
369
- 0%, 16%, 100% { opacity: 0.18; stroke-dashoffset: 0; }
370
- 34%, 66% { opacity: 0.95; stroke-dashoffset: -270; }
371
- 78% { opacity: 0.28; stroke-dashoffset: -270; }
372
- }
373
-
374
- @keyframes ti-azure-risk {
375
- 0%, 20%, 100% { opacity: 0.18; transform: scale(0.9); }
376
- 32%, 70% { opacity: 1; transform: scale(1.08); }
377
- 82% { opacity: 0.35; transform: scale(1); }
378
- }
379
- `}
380
- </style>
381
- <g transform="translate(160 160) scale(1.1) translate(-160 -160)">
382
- <rect fill={palette.frame} height={214} rx={12} stroke={azurePalette.edge} strokeWidth={7} width={222} x={49} y={54} />
383
- <Line d="M 82 88 H 238 M 82 232 H 238" opacity={0.38} palette={azurePalette} tone="muted" width={4} />
384
- <circle cx={104} cy={112} fill={palette.frame} r={34} stroke={azurePalette.edge} strokeWidth={6} />
385
- <Line d="M 92 118 C 94 102, 114 102, 116 118" palette={azurePalette} tone="muted" width={4} />
386
- <Node palette={azurePalette} x={104} y={104} />
387
- <rect fill={palette.frame} height={52} rx={8} stroke={azurePalette.edge} strokeWidth={6} width={74} x={184} y={86} />
388
- <rect fill={palette.frame} height={52} rx={8} stroke={azurePalette.edge} strokeWidth={6} width={74} x={184} y={154} />
389
- <rect fill={palette.frame} height={40} rx={8} stroke={azurePalette.edge} strokeWidth={6} width={64} x={96} y={196} />
390
- <Line d="M 198 112 H 244 M 198 180 H 244 M 108 216 H 148" opacity={0.72} palette={azurePalette} tone="muted" width={4} />
391
- <Line d={pointsPath([[138, 112], [184, 112], [184, 180], [160, 216]])} opacity={0.34} palette={azurePalette} tone="muted" width={4} />
392
- <path
393
- d={pointsPath([[104, 112], [142, 112], [184, 112], [222, 112], [222, 180], [160, 216]])}
394
- fill="none"
395
- stroke={palette.accent}
396
- strokeDasharray="44 270"
397
- strokeLinecap="round"
398
- strokeLinejoin="round"
399
- strokeWidth={6}
400
- style={pathStyle}
401
- />
402
- <Node palette={azurePalette} x={104} y={112} />
403
- <Node palette={azurePalette} x={222} y={112} />
404
- <Node palette={azurePalette} x={222} y={180} />
405
- <Node palette={azurePalette} x={160} y={216} />
406
- <g style={riskStyle(3400)}>
407
- <circle cx={222} cy={112} fill={palette.warning} r={12} />
408
- <path d="M 213 112 H 231 M 222 103 V 121" stroke={palette.frame} strokeLinecap="round" strokeWidth={4} />
409
- </g>
410
- <g style={riskStyle(6200)}>
411
- <circle cx={160} cy={216} fill={palette.accent} r={18} />
412
- <path d="M 146 216 H 174 M 160 202 V 230" stroke={palette.frame} strokeLinecap="round" strokeWidth={5} />
413
- </g>
414
- </g>
415
- </PictogramSvg>
416
- );
417
- }
418
-
419
- function OpenAIInfraPictogram({ alt, palette, timing }: RegisteredPictogramProps) {
420
- const aiPalette = {
421
- ...palette,
422
- edge: "var(--foreground-strong)",
423
- node: "var(--foreground-strong)",
424
- muted: "color-mix(in srgb, var(--foreground-strong) 54%, var(--background) 46%)",
425
- };
426
- const animationDelay = `${timing.delayS(1400)}s`;
427
- const pathStyle = {
428
- animation: timing.prefersReducedMotion ? "none" : `ti-ai-path ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
429
- opacity: timing.prefersReducedMotion ? 0.95 : undefined,
430
- strokeDashoffset: timing.prefersReducedMotion ? -280 : undefined,
431
- } as CSSProperties;
432
- const riskStyle = (offsetMs: number) =>
433
- ({
434
- animation: timing.prefersReducedMotion ? "none" : `ti-ai-risk ${timing.cycleS}s ease-in-out ${timing.delayS(offsetMs)}s infinite`,
435
- opacity: timing.prefersReducedMotion ? 0.95 : 0.16,
436
- transformBox: "fill-box",
437
- transformOrigin: "center",
438
- }) as CSSProperties;
439
-
440
- return (
441
- <PictogramSvg alt={alt} viewBox="0 0 320 320">
442
- <style>
443
- {`
444
- @keyframes ti-ai-path {
445
- 0%, 16%, 100% { opacity: 0.16; stroke-dashoffset: 0; }
446
- 34%, 68% { opacity: 0.95; stroke-dashoffset: -280; }
447
- 82% { opacity: 0.28; stroke-dashoffset: -280; }
448
- }
449
-
450
- @keyframes ti-ai-risk {
451
- 0%, 20%, 100% { opacity: 0.16; transform: scale(0.9); }
452
- 30%, 72% { opacity: 1; transform: scale(1.08); }
453
- 84% { opacity: 0.34; transform: scale(1); }
454
- }
455
- `}
456
- </style>
457
- <g transform="translate(160 160) scale(1.1) translate(-160 -160)">
458
- <rect fill={palette.frame} height={72} rx={12} stroke={aiPalette.edge} strokeWidth={7} width={94} x={113} y={44} />
459
- <Line d="M 130 68 H 190 M 130 92 H 190" opacity={0.72} palette={aiPalette} tone="muted" width={4} />
460
- <Node palette={aiPalette} x={160} y={80} />
461
- <rect fill={palette.frame} height={54} rx={9} stroke={aiPalette.edge} strokeWidth={6} width={78} x={44} y={154} />
462
- <rect fill={palette.frame} height={54} rx={9} stroke={aiPalette.edge} strokeWidth={6} width={78} x={198} y={154} />
463
- <rect fill={palette.frame} height={48} rx={9} stroke={aiPalette.edge} strokeWidth={6} width={72} x={124} y={226} />
464
- <Line d={pointsPath([[160, 116], [83, 154], [83, 181], [160, 226]])} opacity={0.32} palette={aiPalette} tone="muted" width={4} />
465
- <Line d={pointsPath([[160, 116], [237, 154], [237, 181], [160, 226]])} opacity={0.32} palette={aiPalette} tone="muted" width={4} />
466
- <path
467
- d={pointsPath([[83, 181], [120, 142], [160, 80], [200, 142], [237, 181], [160, 250]])}
468
- fill="none"
469
- stroke={palette.accent}
470
- strokeDasharray="44 280"
471
- strokeLinecap="round"
472
- strokeLinejoin="round"
473
- strokeWidth={6}
474
- style={pathStyle}
475
- />
476
- <Line d="M 58 181 H 108 M 212 181 H 262 M 140 250 H 180" opacity={0.72} palette={aiPalette} tone="muted" width={4} />
477
- <Node palette={aiPalette} x={83} y={181} />
478
- <Node palette={aiPalette} x={237} y={181} />
479
- <Node palette={aiPalette} x={160} y={250} />
480
- <g style={riskStyle(2600)}>
481
- <circle cx={83} cy={181} fill={palette.warning} r={12} />
482
- <path d="M 74 181 H 92 M 83 172 V 190" stroke={palette.frame} strokeLinecap="round" strokeWidth={4} />
483
- </g>
484
- <g style={riskStyle(5600)}>
485
- <circle cx={237} cy={181} fill={palette.accent} r={14} />
486
- <path d="M 226 181 H 248 M 237 170 V 192" stroke={palette.frame} strokeLinecap="round" strokeWidth={4.5} />
487
- </g>
488
- <g style={riskStyle(8200)}>
489
- <circle cx={160} cy={250} fill={palette.accent} r={18} />
490
- <path d="M 146 250 H 174 M 160 236 V 264" stroke={palette.frame} strokeLinecap="round" strokeWidth={5} />
491
- </g>
492
- </g>
493
- </PictogramSvg>
494
- );
495
- }
496
-
497
- function GamingIsolationPictogram({ alt, palette, timing }: RegisteredPictogramProps) {
498
- const gamingPalette = {
499
- ...palette,
500
- edge: "var(--foreground-strong)",
501
- node: "var(--foreground-strong)",
502
- muted: "color-mix(in srgb, var(--foreground-strong) 54%, var(--background) 46%)",
503
- };
504
- const animationDelay = `${timing.delayS(1200)}s`;
505
- const attemptStyle = {
506
- animation: timing.prefersReducedMotion ? "none" : `ti-gaming-attempt ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
507
- opacity: timing.prefersReducedMotion ? 0.86 : undefined,
508
- } as CSSProperties;
509
- const barrierStyle = {
510
- animation: timing.prefersReducedMotion ? "none" : `ti-gaming-barrier ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
511
- } as CSSProperties;
512
- const blockedDotStyle = {
513
- animation: timing.prefersReducedMotion ? "none" : `ti-gaming-blocked-dot ${timing.cycleS}s ease-in-out ${animationDelay} infinite`,
514
- opacity: timing.prefersReducedMotion ? 1 : undefined,
515
- } as CSSProperties;
516
-
517
- return (
518
- <PictogramSvg alt={alt} viewBox="0 0 320 320">
519
- <style>
520
- {`
521
- @keyframes ti-gaming-attempt {
522
- 0%, 16%, 100% { opacity: 0.18; }
523
- 34%, 58% { opacity: 0.95; }
524
- 72% { opacity: 0.3; }
525
- }
526
-
527
- @keyframes ti-gaming-barrier {
528
- 0%, 20%, 100% { opacity: 0.62; }
529
- 38%, 62% { opacity: 1; }
530
- }
531
-
532
- @keyframes ti-gaming-blocked-dot {
533
- 0%, 24%, 100% { opacity: 0; }
534
- 38%, 64% { opacity: 1; }
535
- 76% { opacity: 0.24; }
536
- }
537
- `}
538
- </style>
539
- <g transform="translate(160 160) scale(1.12) translate(-160 -160)">
540
- <rect fill={palette.frame} height={152} rx={10} stroke={gamingPalette.edge} strokeWidth={7} width={92} x={202} y={84} />
541
- <Line d="M 160 58 V 262" opacity={0.72} palette={gamingPalette} tone="muted" width={5} />
542
- <path
543
- d="M 160 58 V 262"
544
- fill="none"
545
- stroke={palette.accent}
546
- strokeDasharray="10 13"
547
- strokeLinecap="round"
548
- strokeWidth={5}
549
- style={barrierStyle}
550
- />
551
-
552
- <g transform="translate(34 67) scale(0.47)">
553
- <path
554
- d="M 24 170 C 31 136, 58 122, 88 128 C 98 130, 101 135, 106 135 H 114 C 119 135, 122 130, 132 128 C 162 122, 189 136, 196 170 L 207 200 C 216 228, 194 246, 170 236 C 154 230, 142 214, 132 198 L 125 187 H 95 L 88 198 C 78 214, 66 230, 50 236 C 26 246, 4 228, 13 200 Z"
555
- fill={palette.frame}
556
- stroke={gamingPalette.edge}
557
- strokeLinejoin="round"
558
- strokeWidth={7}
559
- />
560
- <Line d="M 52 176 H 86 M 69 159 V 193" opacity={0.9} palette={gamingPalette} tone="muted" width={7} />
561
- <circle cx={151} cy={166} fill={gamingPalette.node} r={8} />
562
- <circle cx={173} cy={184} fill={gamingPalette.muted} r={8} />
563
- <circle cx={173} cy={148} fill={gamingPalette.muted} r={8} />
564
- </g>
565
- <Node palette={palette} tone="warning" x={102} y={136} />
566
-
567
- <path
568
- d={pointsPath([
569
- [102, 136],
570
- [124, 150],
571
- [142, 160],
572
- [151, 160],
573
- ])}
574
- fill="none"
575
- stroke={palette.accent}
576
- strokeLinecap="round"
577
- strokeLinejoin="round"
578
- strokeWidth={6}
579
- style={attemptStyle}
580
- />
581
- <g style={blockedDotStyle}>
582
- <Line d="M 151 151 L 169 169 M 169 151 L 151 169" palette={palette} tone="accent" width={5} />
583
- </g>
584
-
585
- <Box fillOpacity={0.04} height={34} palette={gamingPalette} strokeTone="edge" width={50} x={224} y={110} />
586
- <Box fillOpacity={0.04} height={34} palette={gamingPalette} strokeTone="edge" width={50} x={224} y={156} />
587
- <Box fillOpacity={0.04} height={34} palette={gamingPalette} strokeTone="edge" width={50} x={224} y={202} />
588
- <Line d="M 236 127 H 262 M 236 173 H 262 M 236 219 H 262" opacity={0.72} palette={gamingPalette} tone="muted" width={4} />
589
- <Node palette={gamingPalette} x={249} y={127} />
590
- <Node palette={gamingPalette} x={249} y={173} />
591
- <Node palette={gamingPalette} x={249} y={219} />
592
- </g>
593
- </PictogramSvg>
594
- );
595
- }
596
-
597
- export const THREAT_INTELLIGENCE_PICTOGRAM_REGISTRY: Record<ThreatIntelligencePictogramSlug, PictogramRegistryEntry> = {
598
- "the-balance-of-power-is-shifting": { aspectRatio: 1, render: BalanceShiftPictogram },
599
- "how-our-newsroom-learns": { aspectRatio: 1, render: NewsroomFlowPictogram },
600
- "audit-aws-exposure-before-attackers-do": { aspectRatio: 1, render: AwsDiscoveryPictogram },
601
- "audit-azure-blast-radius-before-attackers-do": { aspectRatio: 1, render: AzureBlastRadiusPictogram },
602
- "treat-openai-accounts-like-production-infrastructure": { aspectRatio: 1, render: OpenAIInfraPictogram },
603
- "how-to-play-games-securely": { aspectRatio: 1, render: GamingIsolationPictogram },
604
- };
605
-
606
- export function getPictogramRegistryEntry(slug: ThreatIntelligencePictogramSlug): PictogramRegistryEntry {
607
- return THREAT_INTELLIGENCE_PICTOGRAM_REGISTRY[slug];
608
- }
@@ -1,82 +0,0 @@
1
- "use client";
2
-
3
- import Image from "next/image";
4
- import type { ArticleImageLayout, ArticleImageThemeVariants } from "../../../lib/articles";
5
- import { shouldBypassImageOptimization } from "../../../lib/image-url";
6
- import { resolveThemedImageSrc } from "../../../lib/themed-image";
7
- import {
8
- getThreatIntelligencePictogramPhaseOffset,
9
- isThreatIntelligencePictogramSlug,
10
- } from "./registry";
11
- import { useResolvedPapyrusTheme } from "../../../components/use-resolved-papyrus-theme";
12
- import { THREAT_INTELLIGENCE_PICTOGRAM_REGISTRY } from "./art";
13
- import { PICTOGRAM_PALETTE, PictogramFrame, usePictogramMotion } from "./system";
14
-
15
- type PictogramFigureProps = {
16
- alt: string;
17
- caption?: string;
18
- credit: string;
19
- figureClassName: string;
20
- height: number;
21
- layout?: ArticleImageLayout;
22
- priority?: boolean;
23
- sizes: string;
24
- slug: string;
25
- src?: string;
26
- themeVariants?: ArticleImageThemeVariants;
27
- width: number;
28
- };
29
-
30
- export function PictogramFigure({
31
- alt,
32
- caption,
33
- credit,
34
- figureClassName,
35
- height,
36
- layout,
37
- priority = false,
38
- sizes,
39
- slug,
40
- src = "",
41
- themeVariants,
42
- width,
43
- }: PictogramFigureProps) {
44
- const resolvedTheme = useResolvedPapyrusTheme();
45
- const themedImageSrc = resolveThemedImageSrc(src, themeVariants, resolvedTheme);
46
- const registeredSlug = isThreatIntelligencePictogramSlug(slug) ? slug : null;
47
- const motionState = usePictogramMotion(registeredSlug ? getThreatIntelligencePictogramPhaseOffset(registeredSlug) : 0);
48
-
49
- if (!registeredSlug) {
50
- if (!themedImageSrc) {
51
- return null;
52
- }
53
- return (
54
- <figure className={figureClassName}>
55
- <Image
56
- src={themedImageSrc}
57
- alt={alt}
58
- width={width}
59
- height={height}
60
- sizes={sizes}
61
- priority={priority}
62
- unoptimized={shouldBypassImageOptimization(themedImageSrc)}
63
- />
64
- <figcaption>{caption ?? credit}</figcaption>
65
- </figure>
66
- );
67
- }
68
-
69
- const entry = THREAT_INTELLIGENCE_PICTOGRAM_REGISTRY[registeredSlug];
70
- const palette = PICTOGRAM_PALETTE;
71
- const aspectRatio = layout?.aspectRatio ?? entry.aspectRatio ?? 1;
72
- const Pictogram = entry.render;
73
-
74
- return (
75
- <figure className={`${figureClassName} pictogram-figure`} data-pictogram-slug={slug}>
76
- <PictogramFrame aspectRatio={aspectRatio}>
77
- <Pictogram alt={alt} palette={palette} timing={motionState} />
78
- </PictogramFrame>
79
- <figcaption>{caption ?? credit}</figcaption>
80
- </figure>
81
- );
82
- }