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

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/amplify/data/resource.js +26 -0
  2. package/amplify/data/resource.ts +30 -0
  3. package/amplify/data/schema.js +26 -0
  4. package/amplify/data/schema.ts +30 -0
  5. package/amplify/functions/content-actions/handler.py +107 -0
  6. package/amplify/functions/content-actions/requirements.txt +4 -0
  7. package/amplify/functions/content-actions/resource.js +40 -0
  8. package/amplify/functions/content-actions/resource.ts +54 -0
  9. package/amplify/functions/shared/python-bundle.js +17 -2
  10. package/amplify/functions/shared/python-bundle.ts +23 -2
  11. package/amplify/site-backend.js +10 -0
  12. package/amplify/site-backend.ts +15 -0
  13. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +2 -2
  14. package/app/globals.css +1 -4
  15. package/app/layout.tsx +4 -12
  16. package/components/news-desk-page.tsx +1 -4
  17. package/lib/content-actions-client.ts +98 -0
  18. package/lib/empty-theme.css +1 -0
  19. package/lib/layout-scenarios.ts +8 -8
  20. package/lib/site-brand-tenant.ts +3 -22
  21. package/lib/site-brand.ts +44 -42
  22. package/middleware.ts +10 -35
  23. package/package.json +2 -1
  24. package/renderers/pretext/article-page.tsx +1 -6
  25. package/renderers/pretext/presentation-shell.tsx +2 -9
  26. package/routes.manifest.json +1 -1
  27. package/src/with-papyrus.mjs +15 -2
  28. package/publications/anth_us/brand.ts +0 -68
  29. package/publications/anth_us/theme.css +0 -66
  30. package/publications/pilobol_us/brand.ts +0 -37
  31. package/publications/pilobol_us/theme.css +0 -140
  32. package/publications/threat_intelligence/blog-defense/graph.ts +0 -636
  33. package/publications/threat_intelligence/blog-defense/layout.ts +0 -934
  34. package/publications/threat_intelligence/blog-defense/page-background.tsx +0 -590
  35. package/publications/threat_intelligence/brand.ts +0 -40
  36. package/publications/threat_intelligence/pictograms/art.tsx +0 -608
  37. package/publications/threat_intelligence/pictograms/figure.tsx +0 -82
  38. package/publications/threat_intelligence/pictograms/registry.ts +0 -30
  39. package/publications/threat_intelligence/pictograms/system.tsx +0 -305
  40. package/publications/threat_intelligence/seed/seed-edition-content.json +0 -629
  41. package/publications/threat_intelligence/theme.css +0 -1440
  42. /package/{publications/papyrus/theme.css → app/papyrus-theme.css} +0 -0
@@ -1,590 +0,0 @@
1
- "use client";
2
-
3
- import { motion } from "motion/react";
4
- import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject } from "react";
5
- import {
6
- BLOG_DEFENSE_CORE_NODE_ID,
7
- buildCompromiseSequenceFromPath,
8
- buildVisibleAttackPath,
9
- type BlogDefenseCompromiseStep,
10
- } from "./graph";
11
- import { layoutDefenseGraph, type LayoutDefenseNode } from "./layout";
12
- import { SITE_BRAND } from "../../../lib/site-brand";
13
- import { PICTOGRAM_CYCLE_MS } from "../pictograms/registry";
14
- import { useResolvedPapyrusTheme } from "../../../components/use-resolved-papyrus-theme";
15
-
16
- const THROB_MS = 190;
17
- const COMPROMISE_MS = 120;
18
- const STEP_GAP_MS = 36;
19
- const START_DELAY_MS = 40;
20
- const COMPROMISE_TRANSITION_S = COMPROMISE_MS / 1000;
21
- const THROB_TRANSITION_S = 0.21;
22
- const BREACH_RING_DELAYS_S = [0, 0.6, 1.2];
23
- const BREACH_RING_DURATION_S = 2.4;
24
-
25
- type PictogramColors = {
26
- edge: string;
27
- node: string;
28
- throb: string;
29
- coreGlow: string;
30
- compromised: string;
31
- compromisedStroke: string;
32
- };
33
-
34
- const FALLBACK_COLORS: Record<"light" | "dark", PictogramColors> = {
35
- light: {
36
- edge: "var(--slate-8)",
37
- node: "var(--slate-8)",
38
- throb: "var(--tomato-8)",
39
- coreGlow: "var(--ti-alarm-red)",
40
- compromised: "var(--tomato-11)",
41
- compromisedStroke: "var(--sand-1)",
42
- },
43
- dark: {
44
- edge: "var(--slate-6)",
45
- node: "var(--slate-5)",
46
- throb: "var(--tomato-8)",
47
- coreGlow: "var(--ti-alarm-red)",
48
- compromised: "var(--tomato-9)",
49
- compromisedStroke: "var(--sand-2)",
50
- },
51
- };
52
-
53
- type Size = {
54
- width: number;
55
- height: number;
56
- };
57
-
58
- type ObstacleRect = {
59
- x: number;
60
- y: number;
61
- width: number;
62
- height: number;
63
- };
64
-
65
- type BlogPageBackgroundProps = {
66
- pageRef: RefObject<HTMLElement | null>;
67
- };
68
-
69
- function intersectRect(source: DOMRect, bounds: DOMRect): ObstacleRect | null {
70
- const left = Math.max(source.left, bounds.left);
71
- const top = Math.max(source.top, bounds.top);
72
- const right = Math.min(source.right, bounds.right);
73
- const bottom = Math.min(source.bottom, bounds.bottom);
74
- const width = right - left;
75
- const height = bottom - top;
76
- if (width <= 0 || height <= 0) return null;
77
- return {
78
- x: left - bounds.left,
79
- y: top - bounds.top,
80
- width,
81
- height,
82
- };
83
- }
84
-
85
- function textFragmentRects(element: HTMLElement, bounds: DOMRect): ObstacleRect[] {
86
- const ownerDocument = element.ownerDocument;
87
- const elementRect = element.getBoundingClientRect();
88
- const computedStyle = ownerDocument.defaultView?.getComputedStyle(element);
89
- const context = ownerDocument.createElement("canvas").getContext("2d");
90
- if (context && computedStyle) {
91
- context.font = computedStyle.font;
92
- }
93
- const walker = ownerDocument.createTreeWalker(
94
- element,
95
- NodeFilter.SHOW_TEXT,
96
- {
97
- acceptNode(node) {
98
- return /\S/.test(node.textContent ?? "")
99
- ? NodeFilter.FILTER_ACCEPT
100
- : NodeFilter.FILTER_REJECT;
101
- },
102
- },
103
- );
104
- const range = ownerDocument.createRange();
105
- const rects: ObstacleRect[] = [];
106
-
107
- try {
108
- let node = walker.nextNode();
109
- while (node) {
110
- const text = node.textContent ?? "";
111
- const tokenPattern = /\S+/g;
112
- let match: RegExpExecArray | null;
113
- while ((match = tokenPattern.exec(text)) !== null) {
114
- range.setStart(node, match.index);
115
- range.setEnd(node, match.index + match[0].length);
116
- for (const fragmentRect of Array.from(range.getClientRects())) {
117
- const measuredWidth = context?.measureText(match[0]).width ?? 0;
118
- const textWidth = measuredWidth > 0 && measuredWidth < (fragmentRect.width * 0.98)
119
- ? measuredWidth
120
- : fragmentRect.width;
121
- const sourceRect = new DOMRect(
122
- fragmentRect.left,
123
- elementRect.top,
124
- textWidth,
125
- elementRect.height,
126
- );
127
- const rect = intersectRect(sourceRect, bounds);
128
- if (rect) rects.push(rect);
129
- }
130
- }
131
- node = walker.nextNode();
132
- }
133
- } finally {
134
- range.detach();
135
- }
136
-
137
- return rects;
138
- }
139
-
140
- function elementObstacleRects(element: HTMLElement, bounds: DOMRect): ObstacleRect[] {
141
- const fragments = textFragmentRects(element, bounds);
142
- if (fragments.length > 0) return fragments;
143
- return Array.from(element.getClientRects())
144
- .map((lineRect) => intersectRect(lineRect, bounds))
145
- .filter((rect): rect is ObstacleRect => Boolean(rect));
146
- }
147
-
148
- function readPictogramColors(container: HTMLElement): PictogramColors {
149
- const style = getComputedStyle(container);
150
- return {
151
- edge: style.getPropertyValue("--ti-pictogram-edge").trim() || FALLBACK_COLORS.light.edge,
152
- node: style.getPropertyValue("--ti-pictogram-node").trim() || FALLBACK_COLORS.light.node,
153
- throb: style.getPropertyValue("--ti-pictogram-throb").trim() || FALLBACK_COLORS.light.throb,
154
- coreGlow: style.getPropertyValue("--ti-pictogram-core-glow").trim() || FALLBACK_COLORS.light.coreGlow,
155
- compromised: style.getPropertyValue("--ti-pictogram-compromised").trim() || FALLBACK_COLORS.light.compromised,
156
- compromisedStroke: style.getPropertyValue("--ti-pictogram-compromised-stroke").trim() || FALLBACK_COLORS.light.compromisedStroke,
157
- };
158
- }
159
-
160
- function stepDurationMs(): number {
161
- return THROB_MS + COMPROMISE_MS + STEP_GAP_MS;
162
- }
163
-
164
- function isSameThrobStep(a: BlogDefenseCompromiseStep | null, b: BlogDefenseCompromiseStep | null): boolean {
165
- if (!a || !b || a.kind !== b.kind) return false;
166
- if (a.kind === "node" && b.kind === "node") return a.nodeId === b.nodeId;
167
- if (a.kind === "edge" && b.kind === "edge") return a.edgeId === b.edgeId;
168
- return false;
169
- }
170
-
171
- function CoreBreachEffect({
172
- coreNode,
173
- colors,
174
- filterId,
175
- prefersReducedMotion,
176
- scale,
177
- layer,
178
- }: {
179
- coreNode: LayoutDefenseNode;
180
- colors: PictogramColors;
181
- filterId: string;
182
- prefersReducedMotion: boolean;
183
- scale: number;
184
- layer: "rings" | "halo";
185
- }) {
186
- const ringStartRadius = coreNode.radius * 1.2;
187
- const ringEndRadius = coreNode.radius * 3;
188
- const haloRadius = coreNode.radius * 1.85;
189
-
190
- if (layer === "rings") {
191
- return (
192
- <g className="blog-page-background__breach-rings" style={{ color: colors.coreGlow }}>
193
- {BREACH_RING_DELAYS_S.map((delay) => (
194
- <motion.circle
195
- animate={
196
- prefersReducedMotion
197
- ? { fillOpacity: 0.22, r: ringEndRadius * 0.75 }
198
- : {
199
- fillOpacity: [0.45, 0],
200
- r: [ringStartRadius, ringEndRadius],
201
- }
202
- }
203
- cx={coreNode.x}
204
- cy={coreNode.y}
205
- fill="currentColor"
206
- initial={false}
207
- key={`breach-ring-${delay}`}
208
- transition={
209
- prefersReducedMotion
210
- ? { duration: 0 }
211
- : {
212
- delay,
213
- duration: BREACH_RING_DURATION_S,
214
- ease: "easeOut",
215
- repeat: Infinity,
216
- }
217
- }
218
- />
219
- ))}
220
- </g>
221
- );
222
- }
223
-
224
- const blurStdDeviation = Math.max(5, scale * 0.45);
225
- return (
226
- <g className="blog-page-background__breach-halo" style={{ color: colors.coreGlow }}>
227
- <motion.circle
228
- animate={
229
- prefersReducedMotion
230
- ? { fillOpacity: 0.42, r: haloRadius }
231
- : {
232
- fillOpacity: [0.35, 0.7, 0.35],
233
- r: haloRadius,
234
- }
235
- }
236
- cx={coreNode.x}
237
- cy={coreNode.y}
238
- fill="currentColor"
239
- filter={`url(#${filterId})`}
240
- initial={false}
241
- transition={
242
- prefersReducedMotion
243
- ? { duration: 0 }
244
- : {
245
- duration: BREACH_RING_DURATION_S,
246
- ease: "easeInOut",
247
- repeat: Infinity,
248
- }
249
- }
250
- />
251
- <defs>
252
- <filter height="300%" id={filterId} width="300%" x="-100%" y="-100%">
253
- <feGaussianBlur result="blur" stdDeviation={blurStdDeviation} />
254
- <feMerge>
255
- <feMergeNode in="blur" />
256
- <feMergeNode in="SourceGraphic" />
257
- </feMerge>
258
- </filter>
259
- </defs>
260
- </g>
261
- );
262
- }
263
-
264
- export function BlogPageBackground({ pageRef }: BlogPageBackgroundProps) {
265
- const svgId = useId().replace(/:/g, "-");
266
- const coreBreachFilterId = `${svgId}-core-breach-glow`;
267
- const containerRef = useRef<HTMLDivElement | null>(null);
268
- const resolvedTheme = useResolvedPapyrusTheme();
269
- const [colors, setColors] = useState<PictogramColors>(FALLBACK_COLORS[resolvedTheme]);
270
- const [size, setSize] = useState<Size>({ width: 0, height: 0 });
271
- const [obstacles, setObstacles] = useState<ObstacleRect[]>([]);
272
- const [enableStochasticPath, setEnableStochasticPath] = useState(false);
273
- const [attackCycle, setAttackCycle] = useState(0);
274
- const [compromisedNodes, setCompromisedNodes] = useState<Set<string>>(() => new Set());
275
- const [compromisedEdges, setCompromisedEdges] = useState<Set<string>>(() => new Set());
276
- const [throbbingStep, setThrobbingStep] = useState<BlogDefenseCompromiseStep | null>(null);
277
- const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
278
-
279
- useLayoutEffect(() => {
280
- const container = containerRef.current;
281
- if (!container) return;
282
-
283
- const update = () => {
284
- const rect = container.getBoundingClientRect();
285
- const width = Math.ceil(rect.width);
286
- const height = Math.ceil(rect.height);
287
- if (width > 0 && height > 0) {
288
- setSize((current) => (current.width === width && current.height === height ? current : { width, height }));
289
- }
290
- };
291
-
292
- update();
293
- const observer = new ResizeObserver(update);
294
- observer.observe(container);
295
- window.addEventListener("resize", update);
296
-
297
- return () => {
298
- observer.disconnect();
299
- window.removeEventListener("resize", update);
300
- };
301
- }, []);
302
-
303
- useLayoutEffect(() => {
304
- const container = containerRef.current;
305
- if (!container) return;
306
-
307
- let page = pageRef.current ?? container.closest<HTMLElement>("main.presentation-page--blog");
308
- let raf = 0;
309
- let tries = 0;
310
-
311
- const update = () => {
312
- if (!page) {
313
- page = pageRef.current ?? container.closest<HTMLElement>("main.presentation-page--blog");
314
- }
315
- if (!page) return false;
316
-
317
- const containerRect = container.getBoundingClientRect();
318
- const padding = containerRect.width < 420 ? 6 : containerRect.width < 780 ? 9 : 12;
319
- const selectors = [
320
- ".presentation-header h1 span",
321
- ".presentation-header__meta",
322
- ".presentation-header__subtitle",
323
- ".presentation-header__date",
324
- ".presentation-header__tagline",
325
- ".presentation-section-nav a",
326
- ];
327
- const next: ObstacleRect[] = [];
328
- for (const selector of selectors) {
329
- const elements = Array.from(page.querySelectorAll<HTMLElement>(selector));
330
- for (const element of elements) {
331
- for (const rect of elementObstacleRects(element, containerRect)) {
332
- next.push({
333
- x: Math.max(0, rect.x - padding),
334
- y: Math.max(0, rect.y - padding),
335
- width: rect.width + padding * 2,
336
- height: rect.height + padding * 2,
337
- });
338
- }
339
- }
340
- }
341
- const unique = next.filter((candidate, index) => {
342
- return !next.slice(0, index).some((existing) => (
343
- Math.abs(existing.x - candidate.x) < 1
344
- && Math.abs(existing.y - candidate.y) < 1
345
- && Math.abs(existing.width - candidate.width) < 1
346
- && Math.abs(existing.height - candidate.height) < 1
347
- ));
348
- });
349
- setObstacles(unique);
350
- return unique.length > 0;
351
- };
352
-
353
- const ensureInitial = () => {
354
- const found = update();
355
- tries += 1;
356
- if (!found && tries < 30) {
357
- raf = window.requestAnimationFrame(ensureInitial);
358
- }
359
- };
360
- ensureInitial();
361
-
362
- const observer = new ResizeObserver(() => {
363
- void update();
364
- });
365
- observer.observe(container);
366
- if (page) observer.observe(page);
367
- window.addEventListener("resize", update);
368
- void document.fonts?.ready.then(() => {
369
- void update();
370
- });
371
-
372
- return () => {
373
- if (raf) window.cancelAnimationFrame(raf);
374
- observer.disconnect();
375
- window.removeEventListener("resize", update);
376
- };
377
- }, [pageRef]);
378
-
379
- useEffect(() => {
380
- setEnableStochasticPath(true);
381
- }, []);
382
-
383
- useEffect(() => {
384
- if (!enableStochasticPath) return;
385
- const interval = window.setInterval(() => {
386
- setAttackCycle((current) => current + 1);
387
- }, PICTOGRAM_CYCLE_MS);
388
- return () => {
389
- window.clearInterval(interval);
390
- };
391
- }, [enableStochasticPath]);
392
-
393
- const graphLayout = useMemo(() => {
394
- if (!size.width || !size.height) return null;
395
- return layoutDefenseGraph({
396
- width: size.width,
397
- height: size.height,
398
- obstacles,
399
- });
400
- }, [obstacles, size.height, size.width]);
401
-
402
- const animationLayoutKey = `${size.width}:${size.height}`;
403
- const compromiseSequence = useMemo(() => {
404
- void attackCycle; // force recompute on attack cycle change
405
- if (!graphLayout) return [];
406
- const visiblePath = buildVisibleAttackPath({
407
- nodes: graphLayout.nodes,
408
- edges: graphLayout.edges,
409
- coreId: BLOG_DEFENSE_CORE_NODE_ID,
410
- stochastic: enableStochasticPath,
411
- });
412
- return buildCompromiseSequenceFromPath(visiblePath.nodeIds, graphLayout.edges);
413
- }, [attackCycle, enableStochasticPath, graphLayout]);
414
- const coreNode = useMemo(
415
- () => graphLayout?.nodes.find((node) => node.id === BLOG_DEFENSE_CORE_NODE_ID),
416
- [graphLayout?.nodes],
417
- );
418
- const coreBreached = compromisedNodes.has(BLOG_DEFENSE_CORE_NODE_ID);
419
-
420
- useEffect(() => {
421
- const container = containerRef.current;
422
- if (!container) return;
423
- setColors(readPictogramColors(container));
424
- }, [resolvedTheme, animationLayoutKey]);
425
-
426
- useEffect(() => {
427
- if (!graphLayout || compromiseSequence.length === 0) return;
428
-
429
- const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
430
- setPrefersReducedMotion(motionQuery.matches);
431
- setCompromisedNodes(new Set());
432
- setCompromisedEdges(new Set());
433
- setThrobbingStep(null);
434
-
435
- if (motionQuery.matches) {
436
- const nodes = new Set<string>();
437
- const edges = new Set<string>();
438
- for (const step of compromiseSequence) {
439
- if (step.kind === "node") nodes.add(step.nodeId);
440
- else edges.add(step.edgeId);
441
- }
442
- setCompromisedNodes(nodes);
443
- setCompromisedEdges(edges);
444
- return;
445
- }
446
-
447
- const timeouts: number[] = [];
448
- const perStepMs = stepDurationMs();
449
- compromiseSequence.forEach((step, index) => {
450
- const stepStartMs = START_DELAY_MS + index * perStepMs;
451
- const compromiseAtMs = stepStartMs + THROB_MS;
452
-
453
- timeouts.push(window.setTimeout(() => {
454
- setThrobbingStep(step);
455
- }, stepStartMs));
456
-
457
- timeouts.push(window.setTimeout(() => {
458
- setThrobbingStep((current) => (isSameThrobStep(current, step) ? null : current));
459
- if (step.kind === "node") {
460
- setCompromisedNodes((current) => new Set([...current, step.nodeId]));
461
- } else {
462
- setCompromisedEdges((current) => new Set([...current, step.edgeId]));
463
- }
464
- }, compromiseAtMs));
465
- });
466
-
467
- return () => {
468
- timeouts.forEach((timeout) => window.clearTimeout(timeout));
469
- };
470
- }, [animationLayoutKey, attackCycle, compromiseSequence, graphLayout]);
471
-
472
- if (SITE_BRAND.id !== "threat-intelligence") return null;
473
-
474
- return (
475
- <div
476
- aria-hidden="true"
477
- className="blog-page-background"
478
- data-blog-page-background="true"
479
- data-blog-page-background-obstacles={JSON.stringify(obstacles)}
480
- ref={containerRef}
481
- >
482
- {graphLayout ? (
483
- <svg
484
- className="blog-page-background__svg"
485
- preserveAspectRatio="xMaxYMin meet"
486
- viewBox={`0 0 ${size.width} ${size.height}`}
487
- >
488
- <g className="blog-page-background__edges">
489
- {graphLayout.edges.map((edge) => {
490
- const isCompromised = compromisedEdges.has(edge.id);
491
- const isThrobbing = throbbingStep?.kind === "edge" && throbbingStep.edgeId === edge.id;
492
- const baseStrokeWidth = Math.max(1.4, graphLayout.scale * 1.2);
493
- return (
494
- <motion.line
495
- animate={{
496
- stroke: isCompromised
497
- ? colors.compromised
498
- : isThrobbing
499
- ? [colors.edge, colors.throb, colors.edge]
500
- : colors.edge,
501
- strokeWidth: baseStrokeWidth,
502
- }}
503
- data-blog-edge-id={edge.id}
504
- initial={false}
505
- key={edge.id}
506
- stroke={isCompromised ? colors.compromised : colors.edge}
507
- strokeLinecap="round"
508
- strokeWidth={baseStrokeWidth}
509
- transition={
510
- isThrobbing
511
- ? { duration: THROB_TRANSITION_S, ease: "easeInOut", repeat: 1 }
512
- : { duration: COMPROMISE_TRANSITION_S, ease: "easeOut" }
513
- }
514
- x1={edge.x1}
515
- x2={edge.x2}
516
- y1={edge.y1}
517
- y2={edge.y2}
518
- />
519
- );
520
- })}
521
- </g>
522
- {coreBreached && coreNode ? (
523
- <CoreBreachEffect
524
- colors={colors}
525
- coreNode={coreNode}
526
- filterId={coreBreachFilterId}
527
- layer="rings"
528
- prefersReducedMotion={prefersReducedMotion}
529
- scale={graphLayout.scale}
530
- />
531
- ) : null}
532
- <g className="blog-page-background__nodes">
533
- {graphLayout.nodes.map((node) => {
534
- const isCompromised = compromisedNodes.has(node.id);
535
- const isThrobbing = throbbingStep?.kind === "node" && throbbingStep.nodeId === node.id;
536
- const isCore = node.role === "core";
537
- const coreBreachedNode = isCore && coreBreached;
538
- const coreStrokeWidth = isCore ? Math.max(4, graphLayout.scale * 1.5) : 0;
539
- const breachedCoreStrokeWidth = isCore
540
- ? Math.max(coreStrokeWidth * 1.35, graphLayout.scale * 2)
541
- : 0;
542
- return (
543
- <motion.circle
544
- animate={{
545
- fill: isCompromised
546
- ? colors.compromised
547
- : isThrobbing
548
- ? [colors.node, colors.throb, colors.node]
549
- : colors.node,
550
- r: node.radius,
551
- stroke: coreBreachedNode || (isCompromised && isCore)
552
- ? colors.compromisedStroke
553
- : "transparent",
554
- strokeWidth: coreBreachedNode
555
- ? breachedCoreStrokeWidth
556
- : isCompromised && isCore
557
- ? coreStrokeWidth
558
- : 0,
559
- }}
560
- cx={node.x}
561
- cy={node.y}
562
- data-blog-node-id={node.id}
563
- fill={isCompromised ? colors.compromised : colors.node}
564
- initial={false}
565
- key={node.id}
566
- r={node.radius}
567
- transition={
568
- isThrobbing
569
- ? { duration: THROB_TRANSITION_S, ease: "easeInOut", repeat: 1 }
570
- : { duration: COMPROMISE_TRANSITION_S, ease: "easeOut" }
571
- }
572
- />
573
- );
574
- })}
575
- </g>
576
- {coreBreached && coreNode ? (
577
- <CoreBreachEffect
578
- colors={colors}
579
- coreNode={coreNode}
580
- filterId={coreBreachFilterId}
581
- layer="halo"
582
- prefersReducedMotion={prefersReducedMotion}
583
- scale={graphLayout.scale}
584
- />
585
- ) : null}
586
- </svg>
587
- ) : null}
588
- </div>
589
- );
590
- }
@@ -1,40 +0,0 @@
1
- import type { SiteBrand } from "../../lib/site-brand";
2
- import { DEFAULT_THEME_PACK_TOKENS } from "../../lib/site-stack";
3
-
4
- export const threatIntelligenceBrand: SiteBrand = {
5
- id: "threat-intelligence",
6
- appTitle: "Threat Intelligence",
7
- appDescription: "ANTHUS THREAT INTELLIGENCE from Anthus AI Solutions.",
8
- mastheadTitle: "THREAT INTELLIGENCE",
9
- mastheadSubtitle: "from Anthus AI Solutions",
10
- mastheadTagline: "Practical advice for staying secure as the threat landscape shifts.",
11
- backToHomeLabel: "Back to Threat Intelligence",
12
- articleTitleSuffix: "Threat Intelligence",
13
- placeholderByline: "Anthus AI Solutions",
14
- defaultPresentation: "blog",
15
- forcedPresentation: "blog",
16
- textFont: 'system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
17
- footerTitle: "ANTHUS THREAT INTELLIGENCE",
18
- footerSubtitleOverride: "",
19
- mastheadWordSplit: true,
20
- mastheadDateFormat: "formatted",
21
- mastheadSource: "brand",
22
- sectionLinkStrategy: "anchor",
23
- defaultVideoCredit: "Anthus Threat Intelligence video",
24
- themePack: "threat-intelligence",
25
- themeTokens: {
26
- ...DEFAULT_THEME_PACK_TOKENS,
27
- paper: "#f5f0e6",
28
- moss: "#1c1917",
29
- ochre: "#ea580c",
30
- ink: "#44403c",
31
- },
32
- renderer: { kind: "pretext" },
33
- hosting: { kind: "amplify-ssr" },
34
- opsChrome: "app",
35
- corpusKey: "threat-intelligence",
36
- steeringConfigPath: "corpora/papyrus-steering.yml",
37
- newsroomSectionsConfigPath: "corpora/papyrus-newsroom-sections.yml",
38
- analysisProfilesPath: "corpora/papyrus-analysis-profiles.yml",
39
- publicationName: "Anthus Threat Intelligence",
40
- };