@buley/hexgrid-3d 1.0.0

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 (46) hide show
  1. package/.eslintrc.json +28 -0
  2. package/LICENSE +39 -0
  3. package/README.md +291 -0
  4. package/examples/basic-usage.tsx +52 -0
  5. package/package.json +65 -0
  6. package/public/hexgrid-worker.js +1763 -0
  7. package/rust/Cargo.toml +41 -0
  8. package/rust/src/lib.rs +740 -0
  9. package/rust/src/math.rs +574 -0
  10. package/rust/src/spatial.rs +245 -0
  11. package/rust/src/statistics.rs +496 -0
  12. package/src/HexGridEnhanced.ts +16 -0
  13. package/src/Snapshot.ts +1402 -0
  14. package/src/adapters.ts +65 -0
  15. package/src/algorithms/AdvancedStatistics.ts +328 -0
  16. package/src/algorithms/BayesianStatistics.ts +317 -0
  17. package/src/algorithms/FlowField.ts +126 -0
  18. package/src/algorithms/FluidSimulation.ts +99 -0
  19. package/src/algorithms/GraphAlgorithms.ts +184 -0
  20. package/src/algorithms/OutlierDetection.ts +391 -0
  21. package/src/algorithms/ParticleSystem.ts +85 -0
  22. package/src/algorithms/index.ts +13 -0
  23. package/src/compat.ts +96 -0
  24. package/src/components/HexGrid.tsx +31 -0
  25. package/src/components/NarrationOverlay.tsx +221 -0
  26. package/src/components/index.ts +2 -0
  27. package/src/features.ts +125 -0
  28. package/src/index.ts +30 -0
  29. package/src/math/HexCoordinates.ts +15 -0
  30. package/src/math/Matrix4.ts +35 -0
  31. package/src/math/Quaternion.ts +37 -0
  32. package/src/math/SpatialIndex.ts +114 -0
  33. package/src/math/Vector3.ts +69 -0
  34. package/src/math/index.ts +11 -0
  35. package/src/note-adapter.ts +124 -0
  36. package/src/ontology-adapter.ts +77 -0
  37. package/src/stores/index.ts +1 -0
  38. package/src/stores/uiStore.ts +85 -0
  39. package/src/types/index.ts +3 -0
  40. package/src/types.ts +152 -0
  41. package/src/utils/image-utils.ts +25 -0
  42. package/src/wasm/HexGridWasmWrapper.ts +753 -0
  43. package/src/wasm/index.ts +7 -0
  44. package/src/workers/hexgrid-math.ts +177 -0
  45. package/src/workers/hexgrid-worker.worker.ts +1807 -0
  46. package/tsconfig.json +18 -0
@@ -0,0 +1,391 @@
1
+ export interface OutlierStats {
2
+ mean: number;
3
+ stdDev: number;
4
+ }
5
+
6
+ export interface OutlierResult {
7
+ outlierIndices: number[];
8
+ scores: number[];
9
+ stats: OutlierStats;
10
+ threshold: number;
11
+ }
12
+
13
+ export interface TimeSeriesAnomaly {
14
+ index: number;
15
+ isAnomaly: boolean;
16
+ zScore: number;
17
+ expectedValue: number;
18
+ actualValue: number;
19
+ confidence: number;
20
+ anomalyType: 'spike' | 'drop' | 'change';
21
+ }
22
+
23
+ export interface VarianceChangeResult {
24
+ index: number;
25
+ ratio: number;
26
+ beforeVariance: number;
27
+ afterVariance: number;
28
+ }
29
+
30
+ function mean(values: number[]): number {
31
+ if (values.length === 0) return 0;
32
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
33
+ }
34
+
35
+ function stdDev(values: number[], avg: number): number {
36
+ if (values.length === 0) return 0;
37
+ const variance =
38
+ values.reduce((sum, value) => sum + (value - avg) ** 2, 0) / values.length;
39
+ return Math.sqrt(variance);
40
+ }
41
+
42
+ export function detectOutliersZScore(
43
+ values: number[],
44
+ threshold: number = 3
45
+ ): OutlierResult {
46
+ const avg = mean(values);
47
+ const deviation = stdDev(values, avg);
48
+ const scores = values.map((value) =>
49
+ deviation === 0 ? 0 : (value - avg) / deviation
50
+ );
51
+
52
+ const outlierIndices = scores
53
+ .map((score, index) => ({ score, index }))
54
+ .filter(({ score }) => Math.abs(score) >= threshold)
55
+ .map(({ index }) => index);
56
+
57
+ return {
58
+ outlierIndices,
59
+ scores,
60
+ stats: { mean: avg, stdDev: deviation },
61
+ threshold,
62
+ };
63
+ }
64
+
65
+ export function detectOutliersModifiedZScore(
66
+ values: number[],
67
+ threshold: number = 3.5
68
+ ): OutlierResult {
69
+ if (values.length === 0) {
70
+ return {
71
+ outlierIndices: [],
72
+ scores: [],
73
+ stats: { mean: 0, stdDev: 0 },
74
+ threshold,
75
+ };
76
+ }
77
+
78
+ const sorted = [...values].sort((a, b) => a - b);
79
+ const median = sorted[Math.floor(sorted.length / 2)] ?? 0;
80
+ const deviations = values.map((value) => Math.abs(value - median));
81
+ const sortedDeviations = [...deviations].sort((a, b) => a - b);
82
+ const mad = sortedDeviations[Math.floor(sortedDeviations.length / 2)] || 0;
83
+
84
+ const scores = values.map((value) =>
85
+ mad === 0 ? 0 : (0.6745 * (value - median)) / mad
86
+ );
87
+
88
+ const outlierIndices = scores
89
+ .map((score, index) => ({ score, index }))
90
+ .filter(({ score }) => Math.abs(score) >= threshold)
91
+ .map(({ index }) => index);
92
+
93
+ return {
94
+ outlierIndices,
95
+ scores,
96
+ stats: { mean: median, stdDev: mad },
97
+ threshold,
98
+ };
99
+ }
100
+
101
+ export function detectOutliersIQR(
102
+ values: number[],
103
+ threshold: number = 1.5
104
+ ): OutlierResult {
105
+ if (values.length === 0) {
106
+ return {
107
+ outlierIndices: [],
108
+ scores: [],
109
+ stats: { mean: 0, stdDev: 0 },
110
+ threshold,
111
+ };
112
+ }
113
+
114
+ const sorted = [...values].sort((a, b) => a - b);
115
+ const q1 = sorted[Math.floor(sorted.length * 0.25)] ?? 0;
116
+ const q3 = sorted[Math.floor(sorted.length * 0.75)] ?? 0;
117
+ const iqr = q3 - q1;
118
+
119
+ const lowerBound = q1 - threshold * iqr;
120
+ const upperBound = q3 + threshold * iqr;
121
+
122
+ const outlierIndices = values
123
+ .map((value, index) => ({ value, index }))
124
+ .filter(({ value }) => value < lowerBound || value > upperBound)
125
+ .map(({ index }) => index);
126
+
127
+ const avg = mean(values);
128
+ const deviation = stdDev(values, avg);
129
+ const scores = values.map((value) =>
130
+ deviation === 0 ? 0 : (value - avg) / deviation
131
+ );
132
+
133
+ return {
134
+ outlierIndices,
135
+ scores,
136
+ stats: { mean: avg, stdDev: deviation },
137
+ threshold,
138
+ };
139
+ }
140
+
141
+ export function detectGrowthSpikes(values: number[]): TimeSeriesAnomaly[] {
142
+ if (values.length < 2) return [];
143
+
144
+ const diffs = values.slice(1).map((value, index) => value - values[index]);
145
+ const diffStats = detectOutliersZScore(diffs, 2.5);
146
+
147
+ return diffs.map((diff, index) => {
148
+ const score = diffStats.scores[index] ?? 0;
149
+ const isAnomaly = Math.abs(score) >= 2.5;
150
+ return {
151
+ index: index + 1,
152
+ isAnomaly,
153
+ zScore: Math.abs(score),
154
+ expectedValue: values[index],
155
+ actualValue: values[index + 1],
156
+ confidence: Math.min(0.99, Math.abs(score) / 4),
157
+ anomalyType: diff >= 0 ? 'spike' : 'drop',
158
+ };
159
+ });
160
+ }
161
+
162
+ export function detectVarianceChanges(values: number[]): VarianceChangeResult[] {
163
+ if (values.length < 6) return [];
164
+ const results: VarianceChangeResult[] = [];
165
+ const windowSize = Math.max(3, Math.floor(values.length / 4));
166
+
167
+ for (let i = windowSize; i < values.length - windowSize; i++) {
168
+ const before = values.slice(i - windowSize, i);
169
+ const after = values.slice(i, i + windowSize);
170
+ const beforeMean = mean(before);
171
+ const afterMean = mean(after);
172
+ const beforeVar = stdDev(before, beforeMean) ** 2;
173
+ const afterVar = stdDev(after, afterMean) ** 2;
174
+ const ratio = beforeVar === 0 ? (afterVar === 0 ? 1 : Infinity) : afterVar / beforeVar;
175
+
176
+ results.push({
177
+ index: i,
178
+ ratio,
179
+ beforeVariance: beforeVar,
180
+ afterVariance: afterVar,
181
+ });
182
+ }
183
+
184
+ return results;
185
+ }
186
+
187
+ export function mahalanobisOutliers(
188
+ values: number[],
189
+ threshold: number = 3
190
+ ): OutlierResult {
191
+ return detectOutliersZScore(values, threshold);
192
+ }
193
+
194
+ // Game Anomaly Detection
195
+ export interface GameAnomaly {
196
+ index: number;
197
+ type: 'sudden_change' | 'pattern_break' | 'statistical_outlier';
198
+ severity: number;
199
+ description: string;
200
+ }
201
+
202
+ export function detectGameAnomalies(values: number[], windowSize: number = 5): GameAnomaly[] {
203
+ const anomalies: GameAnomaly[] = [];
204
+ if (values.length < windowSize * 2) return anomalies;
205
+
206
+ for (let i = windowSize; i < values.length - windowSize; i++) {
207
+ const before = values.slice(i - windowSize, i);
208
+ const after = values.slice(i, i + windowSize);
209
+ const beforeMean = before.reduce((s, v) => s + v, 0) / before.length;
210
+ const afterMean = after.reduce((s, v) => s + v, 0) / after.length;
211
+ const change = Math.abs(afterMean - beforeMean);
212
+ const threshold = before.reduce((s, v) => s + Math.abs(v - beforeMean), 0) / before.length;
213
+
214
+ if (change > threshold * 2) {
215
+ anomalies.push({
216
+ index: i,
217
+ type: 'sudden_change',
218
+ severity: Math.min(1, change / (threshold || 1)),
219
+ description: `Sudden change detected at index ${i}`,
220
+ });
221
+ }
222
+ }
223
+
224
+ return anomalies;
225
+ }
226
+
227
+ // Comprehensive Outlier Analysis
228
+ export interface MultivariateOutlierResult {
229
+ outliers: number[];
230
+ scores: number[];
231
+ method: string;
232
+ }
233
+
234
+ export function comprehensiveOutlierAnalysis(values: number[]): MultivariateOutlierResult {
235
+ const zScore = detectOutliersZScore(values);
236
+ const iqr = detectOutliersIQR(values);
237
+ const combined = new Set([...zScore.outlierIndices, ...iqr.outlierIndices]);
238
+
239
+ return {
240
+ outliers: Array.from(combined),
241
+ scores: values.map((v, i) =>
242
+ combined.has(i) ? Math.max(Math.abs(zScore.scores[i] ?? 0), Math.abs(iqr.scores[i] ?? 0)) : 0
243
+ ),
244
+ method: 'comprehensive',
245
+ };
246
+ }
247
+
248
+ // Local Outlier Factor (simplified)
249
+ export function localOutlierFactor(values: number[], k: number = 5): OutlierResult {
250
+ // Simplified LOF implementation
251
+ if (values.length < k + 1) {
252
+ return {
253
+ outlierIndices: [],
254
+ scores: values.map(() => 0),
255
+ stats: { mean: 0, stdDev: 0 },
256
+ threshold: 1.5,
257
+ };
258
+ }
259
+
260
+ const scores: number[] = [];
261
+ for (let i = 0; i < values.length; i++) {
262
+ const distances = values.map((v, j) =>
263
+ i === j ? Infinity : Math.abs(v - values[i]!)
264
+ ).sort((a, b) => a - b);
265
+ const kthDistance = distances[k] ?? 0;
266
+ const reachability = values.map((v, j) =>
267
+ Math.max(kthDistance, Math.abs(v - values[i]!))
268
+ );
269
+ const lrd = k / reachability.reduce((s, r) => s + r, 0);
270
+ scores.push(lrd);
271
+ }
272
+
273
+ const avgLrd = scores.reduce((s, lrd) => s + lrd, 0) / scores.length;
274
+ const lofScores = scores.map(lrd => lrd === 0 ? 0 : avgLrd / lrd);
275
+ const threshold = 1.5;
276
+ const outlierIndices = lofScores
277
+ .map((score, idx) => ({ score, idx }))
278
+ .filter(({ score }) => score > threshold)
279
+ .map(({ idx }) => idx);
280
+
281
+ const avg = values.reduce((s, v) => s + v, 0) / values.length;
282
+ const variance = values.reduce((s, v) => s + Math.pow(v - avg, 2), 0) / values.length;
283
+ const stdDev = Math.sqrt(variance);
284
+
285
+ return {
286
+ outlierIndices,
287
+ scores: lofScores,
288
+ stats: { mean: avg, stdDev },
289
+ threshold,
290
+ };
291
+ }
292
+
293
+ // Isolation Forest (simplified)
294
+ export function isolationForest(values: number[], trees: number = 100, samples: number = 256): OutlierResult {
295
+ // Simplified isolation forest
296
+ const scores: number[] = [];
297
+ const sampleSize = Math.min(samples, values.length);
298
+
299
+ for (let i = 0; i < values.length; i++) {
300
+ let pathLengthSum = 0;
301
+ for (let t = 0; t < trees; t++) {
302
+ const sample = [];
303
+ for (let s = 0; s < sampleSize; s++) {
304
+ sample.push(values[Math.floor(Math.random() * values.length)] ?? 0);
305
+ }
306
+ const min = Math.min(...sample);
307
+ const max = Math.max(...sample);
308
+ const range = max - min || 1;
309
+ const normalized = (values[i]! - min) / range;
310
+ pathLengthSum += Math.log2(Math.max(1, normalized * sampleSize));
311
+ }
312
+ const avgPathLength = pathLengthSum / trees;
313
+ const anomalyScore = Math.pow(2, -avgPathLength / Math.log2(sampleSize));
314
+ scores.push(anomalyScore);
315
+ }
316
+
317
+ const threshold = 0.5;
318
+ const outlierIndices = scores
319
+ .map((score, idx) => ({ score, idx }))
320
+ .filter(({ score }) => score > threshold)
321
+ .map(({ idx }) => idx);
322
+
323
+ const avg = values.reduce((s, v) => s + v, 0) / values.length;
324
+ const variance = values.reduce((s, v) => s + Math.pow(v - avg, 2), 0) / values.length;
325
+ const stdDev = Math.sqrt(variance);
326
+
327
+ return {
328
+ outlierIndices,
329
+ scores,
330
+ stats: { mean: avg, stdDev },
331
+ threshold,
332
+ };
333
+ }
334
+
335
+ // CUSUM Chart
336
+ export function cusumChart(values: number[], target: number, h: number = 5, k: number = 0.5): TimeSeriesAnomaly[] {
337
+ const anomalies: TimeSeriesAnomaly[] = [];
338
+ let sPos = 0;
339
+ let sNeg = 0;
340
+
341
+ for (let i = 0; i < values.length; i++) {
342
+ const deviation = values[i]! - target;
343
+ sPos = Math.max(0, sPos + deviation - k);
344
+ sNeg = Math.max(0, sNeg - deviation - k);
345
+
346
+ if (sPos > h || sNeg > h) {
347
+ anomalies.push({
348
+ index: i,
349
+ isAnomaly: true,
350
+ zScore: Math.max(sPos, sNeg) / h,
351
+ expectedValue: target,
352
+ actualValue: values[i]!,
353
+ confidence: Math.min(0.99, Math.max(sPos, sNeg) / (h * 2)),
354
+ anomalyType: sPos > sNeg ? 'spike' : 'drop',
355
+ });
356
+ }
357
+ }
358
+
359
+ return anomalies;
360
+ }
361
+
362
+ // EWMA Chart (Exponentially Weighted Moving Average)
363
+ export function ewmaChart(values: number[], lambda: number = 0.2, lcl: number = -3, ucl: number = 3): TimeSeriesAnomaly[] {
364
+ const anomalies: TimeSeriesAnomaly[] = [];
365
+ if (values.length === 0) return anomalies;
366
+
367
+ let ewma = values[0]!;
368
+ const mean = values.reduce((s, v) => s + v, 0) / values.length;
369
+ const variance = values.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / values.length;
370
+ const stdDev = Math.sqrt(variance);
371
+ const ewmaStdDev = stdDev * Math.sqrt(lambda / (2 - lambda));
372
+
373
+ for (let i = 1; i < values.length; i++) {
374
+ ewma = lambda * values[i]! + (1 - lambda) * ewma;
375
+ const zScore = (ewma - mean) / (ewmaStdDev || 1);
376
+
377
+ if (zScore < lcl || zScore > ucl) {
378
+ anomalies.push({
379
+ index: i,
380
+ isAnomaly: true,
381
+ zScore: Math.abs(zScore),
382
+ expectedValue: mean,
383
+ actualValue: values[i]!,
384
+ confidence: Math.min(0.99, Math.abs(zScore) / 5),
385
+ anomalyType: zScore > 0 ? 'spike' : 'drop',
386
+ });
387
+ }
388
+ }
389
+
390
+ return anomalies;
391
+ }
@@ -0,0 +1,85 @@
1
+ import { Vector2 } from '../math/Vector3';
2
+
3
+ type ParticleEffect = 'birth' | 'trail' | 'victory' | 'sparkle';
4
+
5
+ export interface ParticleConfig {
6
+ maxParticles: number;
7
+ gravity: Vector2;
8
+ }
9
+
10
+ export interface Particle {
11
+ x: number;
12
+ y: number;
13
+ size: number;
14
+ color: string;
15
+ alpha: number;
16
+ }
17
+
18
+ export class ParticleSystem {
19
+ private particles: Particle[] = [];
20
+
21
+ addParticle(particle: Particle): void {
22
+ this.particles.push(particle);
23
+ }
24
+
25
+ update(_deltaTime: number): void {
26
+ // No-op for stub
27
+ }
28
+
29
+ getPositions(): Particle[] {
30
+ return this.particles;
31
+ }
32
+
33
+ clear(): void {
34
+ this.particles = [];
35
+ }
36
+ }
37
+
38
+ export class ParticleEffectManager {
39
+ private systems: Map<string, ParticleSystem> = new Map();
40
+ private config: ParticleConfig;
41
+
42
+ constructor(config: ParticleConfig) {
43
+ this.config = config;
44
+ this.systems.set('default', new ParticleSystem());
45
+ }
46
+
47
+ triggerEffect(effect: ParticleEffect, position: Vector2, options?: { count?: number; color?: [number, number, number]; velocity?: Vector2 }): void {
48
+ const system = this.systems.get('default');
49
+ if (!system) return;
50
+ const count = options?.count ?? 1;
51
+ const color = options?.color ?? [1, 1, 1];
52
+ for (let i = 0; i < count; i++) {
53
+ system.addParticle({
54
+ x: position.x,
55
+ y: position.y,
56
+ size: 4,
57
+ color: `rgb(${Math.round(color[0] * 255)}, ${Math.round(color[1] * 255)}, ${Math.round(color[2] * 255)})`,
58
+ alpha: 1,
59
+ });
60
+ }
61
+ }
62
+
63
+ update(deltaTime: number): void {
64
+ for (const system of this.systems.values()) {
65
+ system.update(deltaTime);
66
+ }
67
+ }
68
+
69
+ getSystem(name: string): ParticleSystem | undefined {
70
+ return this.systems.get(name);
71
+ }
72
+
73
+ clearAll(): void {
74
+ for (const system of this.systems.values()) {
75
+ system.clear();
76
+ }
77
+ }
78
+ }
79
+
80
+ export const ParticlePresets: Record<string, ParticleEffect> = {
81
+ birth: 'birth',
82
+ trail: 'trail',
83
+ victory: 'victory',
84
+ sparkle: 'sparkle',
85
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Algorithms Module Exports
3
+ *
4
+ * @module algorithms
5
+ */
6
+
7
+ export * from './GraphAlgorithms'
8
+ export * from './FlowField'
9
+ export * from './ParticleSystem'
10
+ export * from './FluidSimulation'
11
+ export * from './AdvancedStatistics'
12
+ export * from './BayesianStatistics'
13
+ export * from './OutlierDetection'
package/src/compat.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Backward compatibility layer for Photo and GridItem
3
+ *
4
+ * Provides conversion utilities to ensure existing Photo-based code
5
+ * continues to work while new code can use GridItem.
6
+ */
7
+
8
+ import type { Photo, GridItem } from './types'
9
+
10
+ /**
11
+ * Convert Photo to GridItem for backward compatibility
12
+ */
13
+ export function photoToGridItem(photo: Photo): GridItem<Photo> {
14
+ return {
15
+ id: photo.id,
16
+ type: 'photo',
17
+ imageUrl: photo.imageUrl,
18
+ thumbnailUrl: photo.thumbnailUrl,
19
+ title: photo.title,
20
+ alt: photo.alt,
21
+ description: photo.description,
22
+ category: photo.category,
23
+ data: photo,
24
+ // Map all Photo fields
25
+ url: photo.imageUrl,
26
+ userId: photo.userId,
27
+ username: photo.username,
28
+ videoUrl: photo.videoUrl,
29
+ platform: photo.platform,
30
+ author: photo.author,
31
+ authorUrl: photo.authorUrl,
32
+ likes: photo.likes,
33
+ views: photo.views,
34
+ comments: photo.comments,
35
+ dominantColor: photo.dominantColor,
36
+ source: photo.source,
37
+ sourceUrl: photo.sourceUrl,
38
+ createdAt: photo.createdAt,
39
+ velocity: photo.velocity,
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Convert GridItem back to Photo if possible
45
+ */
46
+ export function gridItemToPhoto(item: GridItem<Photo>): Photo | null {
47
+ // If item contains original Photo data, return it
48
+ if (item.type === 'photo' && item.data) {
49
+ return item.data
50
+ }
51
+
52
+ // Fallback: construct Photo from GridItem fields
53
+ if (item.imageUrl || item.url) {
54
+ return {
55
+ id: item.id,
56
+ title: item.title ?? '',
57
+ alt: item.alt ?? item.title ?? '',
58
+ imageUrl: item.imageUrl || item.url || '',
59
+ category: item.category ?? 'uncategorized',
60
+ description: item.description,
61
+ source: item.source || 'unknown',
62
+ sourceUrl: item.sourceUrl,
63
+ createdAt: item.createdAt,
64
+ thumbnailUrl: item.thumbnailUrl,
65
+ userId: item.userId,
66
+ username: item.username,
67
+ videoUrl: item.videoUrl,
68
+ platform: item.platform,
69
+ author: item.author,
70
+ authorUrl: item.authorUrl,
71
+ likes: item.likes,
72
+ views: item.views,
73
+ comments: item.comments,
74
+ dominantColor: item.dominantColor,
75
+ velocity: item.velocity,
76
+ }
77
+ }
78
+
79
+ return null
80
+ }
81
+
82
+ /**
83
+ * Convert an array of Photos to GridItems
84
+ */
85
+ export function photosToGridItems(photos: Photo[]): GridItem<Photo>[] {
86
+ return photos.map(photoToGridItem)
87
+ }
88
+
89
+ /**
90
+ * Convert an array of GridItems to Photos (filtering out non-photo items)
91
+ */
92
+ export function gridItemsToPhotos(items: GridItem<Photo>[]): Photo[] {
93
+ return items
94
+ .map(gridItemToPhoto)
95
+ .filter((photo): photo is Photo => photo !== null)
96
+ }
@@ -0,0 +1,31 @@
1
+ import type { CSSProperties } from 'react';
2
+ import React from 'react';
3
+
4
+ export interface Photo {
5
+ id: string;
6
+ title: string;
7
+ alt: string;
8
+ imageUrl: string;
9
+ thumbnailUrl: string;
10
+ category: string;
11
+ description: string;
12
+ source: string;
13
+ createdAt: string;
14
+ velocity: number;
15
+ sourceUrl: string;
16
+ likes: number;
17
+ age_in_hours: number;
18
+ }
19
+
20
+ export interface HexGridProps {
21
+ photos?: Photo[];
22
+ className?: string;
23
+ style?: CSSProperties;
24
+ onPhotoClick?: (photo: Photo) => void;
25
+ }
26
+
27
+ export function HexGrid(_props: HexGridProps): JSX.Element | null {
28
+ return null;
29
+ }
30
+
31
+ export default HexGrid;