diff --git a/README.md b/README.md index 6df9d8a..c9cb3aa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ## TODOs - [ ] replace mock wallet connection/disconnection - [ ] replace mock Ordinal verification (API) -- [ ] \ No newline at end of file +- [ ] figure out using actual icons for cells +- [ ] store message cache in indexedDB -- make app local-first (update from/to Waku when available) \ No newline at end of file diff --git a/src/components/CellList.tsx b/src/components/CellList.tsx index a37a95b..3a32510 100644 --- a/src/components/CellList.tsx +++ b/src/components/CellList.tsx @@ -5,6 +5,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { Layout, MessageSquare, RefreshCw } from 'lucide-react'; import { CreateCellDialog } from './CreateCellDialog'; import { Button } from '@/components/ui/button'; +import { CypherImage } from './ui/CypherImage'; const CellList = () => { const { cells, isInitialLoading, posts, refreshData, isRefreshing } = useForum(); @@ -67,10 +68,11 @@ const CellList = () => { cells.map((cell) => (
- {cell.name}

{cell.name}

diff --git a/src/components/PostDetail.tsx b/src/components/PostDetail.tsx index 37f712d..6d526b4 100644 --- a/src/components/PostDetail.tsx +++ b/src/components/PostDetail.tsx @@ -8,6 +8,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { ArrowLeft, ArrowUp, ArrowDown, Clock, MessageCircle, Send, RefreshCw } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { Comment } from '@/types'; +import { CypherImage } from './ui/CypherImage'; const PostDetail = () => { const { postId } = useParams<{ postId: string }>(); diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx index 6f9314b..1b5920c 100644 --- a/src/components/PostList.tsx +++ b/src/components/PostList.tsx @@ -8,6 +8,7 @@ import { Textarea } from '@/components/ui/textarea'; import { Skeleton } from '@/components/ui/skeleton'; import { ArrowLeft, MessageSquare, MessageCircle, ArrowUp, ArrowDown, Clock, RefreshCw } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; +import { CypherImage } from './ui/CypherImage'; const PostList = () => { const { cellId } = useParams<{ cellId: string }>(); @@ -99,10 +100,11 @@ const PostList = () => {
- {cell.name}
diff --git a/src/components/ui/CypherImage.tsx b/src/components/ui/CypherImage.tsx new file mode 100644 index 0000000..31104b6 --- /dev/null +++ b/src/components/ui/CypherImage.tsx @@ -0,0 +1,161 @@ +import React, { useState } from 'react'; +import { cn } from '@/lib/utils'; + +type CypherImageProps = { + src: string; + alt: string; + className?: string; + fallbackClassName?: string; + generateUniqueFallback?: boolean; +} & Omit, 'src' | 'alt' | 'className'>; + +/** + * CypherImage component that renders a cypherpunk-style fallback image + * when the actual image cannot be loaded. + */ +export function CypherImage({ + src, + alt, + className, + fallbackClassName, + generateUniqueFallback = false, + ...props +}: CypherImageProps) { + const [imageError, setImageError] = useState(false); + + // Generate a seed based on the alt text or src to create consistent fallbacks for the same resource + const seed = generateUniqueFallback ? + (alt || src).split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) : 0; + + // Handle image load error + const handleError = () => { + setImageError(true); + }; + + if (imageError) { + // Generate some values based on the seed for variety + const hue = (seed % 60) + 140; // Cyan-ish colors (140-200) + const gridSize = (seed % 8) + 8; // 8-16px + const noiseIntensity = (seed % 30) + 5; // 5-35% + const scanlineOpacity = ((seed % 4) + 1) / 10; // 0.1-0.5 + + return ( +
+ {/* Noise overlay */} +
+ + {/* Scanlines */} +
+ + {/* CRT glow effect */} +
+ +
+ {/* Glitch effect lines */} +
+ + {/* Main content container with glitch effect */} +
+ {/* Glitched text behind the main letter */} +
+ {Array.from({ length: 3 }, (_, i) => { + const chars = '01[]{}><#$%&@!?;:+=*/\\|~^'; + return chars[(seed + i) % chars.length]; + }).join('')} +
+ + {/* First letter of alt text in center */} +
+ {alt.charAt(0).toUpperCase()} +
+ + {/* Random characters that occasionally "glitch" in */} +
+ {seed.toString(16).substring(0, 4)} +
+
+
+
+ ); + } + + return ( + {alt} + ); +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 412e038..f434f1c 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,3 @@ - @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap'); @tailwind base; @@ -110,3 +109,40 @@ @apply border-l-2 border-cyber-muted pl-4 py-2 my-3 hover:border-cyber-accent transition-colors; } } + +/* Cyberpunk glow animation for CypherImage */ +@keyframes cyber-flicker { + 0%, 100% { + opacity: 1; + filter: drop-shadow(0 0 2px rgba(0, 255, 255, 0.8)); + } + 8%, 10% { + opacity: 0.8; + filter: drop-shadow(0 0 5px rgba(0, 255, 255, 0.8)); + } + 20%, 25% { + opacity: 1; + filter: drop-shadow(0 0 1px rgba(0, 255, 255, 0.5)); + } + 30% { + opacity: 0.6; + filter: drop-shadow(0 0 8px rgba(0, 255, 255, 1)); + } + 40%, 45% { + opacity: 1; + filter: drop-shadow(0 0 2px rgba(0, 255, 255, 0.6)); + } + 50%, 55% { + opacity: 0.9; + filter: drop-shadow(0 0 3px rgba(0, 255, 255, 0.8)); + } + 60%, 100% { + opacity: 1; + filter: drop-shadow(0 0 2px rgba(0, 255, 255, 0.6)); + } +} + +.cyberpunk-glow { + animation: cyber-flicker 8s infinite; + will-change: filter, opacity; +} diff --git a/src/lib/waku/index.ts b/src/lib/waku/index.ts index 2bdc1dc..a341dde 100644 --- a/src/lib/waku/index.ts +++ b/src/lib/waku/index.ts @@ -156,6 +156,7 @@ class MessageManager { const messages = await this.storeManager.queryStore(); for (const message of messages) { + console.log("message", message); this.updateCache(message); }