mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-26 20:49:22 +00:00
init
This commit is contained in:
parent
0cda9cb7fc
commit
3d97870b59
116
NIP.md
Normal file
116
NIP.md
Normal file
@ -0,0 +1,116 @@
|
||||
# NIP-GNOME: Gnome Speak Translation Protocol
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
## Abstract
|
||||
|
||||
This NIP defines a client-side translation protocol for converting kind 1 (Short Text Note) events into "Gnome Speak" - a whimsical fantasy language that transforms regular text into gnome-themed vocabulary and expressions.
|
||||
|
||||
## Motivation
|
||||
|
||||
To create an immersive and entertaining experience for users of gnome-themed Nostr clients, where all text content is automatically translated into a consistent fantasy language that maintains readability while adding magical woodland charm.
|
||||
|
||||
## Specification
|
||||
|
||||
### Translation Scope
|
||||
|
||||
- **MUST** translate all kind 1 events (Short Text Notes) into Gnome Speak
|
||||
- **SHOULD NOT** translate other event kinds to maintain protocol compatibility
|
||||
- **MUST** preserve original URLs, Nostr references (npub, note, etc.), and hashtags without translation
|
||||
- **SHOULD** maintain the semantic meaning of the original text
|
||||
|
||||
### Translation Rules
|
||||
|
||||
#### Vocabulary Substitution
|
||||
|
||||
Common words are replaced with gnome-themed equivalents:
|
||||
|
||||
- Greetings: `hello` → `greetings, fellow earth-dweller`
|
||||
- Technology: `computer` → `thinking-box of metal and magic`
|
||||
- Time: `today` → `this blessed sun-cycle`
|
||||
- Actions: `go` → `venture forth`
|
||||
- People: `friend` → `companion of the woodland path`
|
||||
|
||||
#### Gnome Expressions
|
||||
|
||||
- Exclamations are enhanced with gnome phrases: `!` → `! By my pointy hat!`
|
||||
- Sentence starters may be added: `In the sacred grove, ...`
|
||||
- Sentence endings may be appended: `...as the ancients foretold.`
|
||||
|
||||
#### Special Formatting
|
||||
|
||||
- Bitcoin/crypto terms: `bitcoin` → `✨golden acorns of the digital realm✨`
|
||||
- Nostr terms: `nostr` → `🍄message-mushroom🍄`
|
||||
- Greetings: `gm` → `🌅when dew kisses the earth🌅`
|
||||
|
||||
### Implementation
|
||||
|
||||
#### Client Behavior
|
||||
|
||||
Clients implementing this NIP:
|
||||
|
||||
1. **MUST** detect kind 1 events
|
||||
2. **MUST** apply Gnome Speak translation before displaying content
|
||||
3. **SHOULD** preserve original content for editing/replying
|
||||
4. **MAY** provide toggle to view original content
|
||||
5. **SHOULD** indicate when content has been translated
|
||||
|
||||
#### Content Preservation
|
||||
|
||||
- Original event content **MUST** remain unchanged in storage/transmission
|
||||
- Translation occurs only at display time
|
||||
- Replies and quotes **SHOULD** reference original content, not translated
|
||||
|
||||
#### Tag Usage
|
||||
|
||||
Clients **MAY** add a `t` tag with value `gnome` to indicate gnome-themed content:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 1,
|
||||
"content": "Hello world!",
|
||||
"tags": [["t", "gnome"]],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Original event:
|
||||
```json
|
||||
{
|
||||
"kind": 1,
|
||||
"content": "Good morning! Having coffee and checking bitcoin prices.",
|
||||
"tags": [],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Displayed as:
|
||||
```
|
||||
"When dew kisses the earth! Partaking of bitter bean brew and witnessing ✨golden acorns of the digital realm✨ values, blessed by the forest spirits."
|
||||
```
|
||||
|
||||
## Rationale
|
||||
|
||||
This approach provides:
|
||||
|
||||
1. **Immersive Experience**: Creates a consistent fantasy atmosphere
|
||||
2. **Protocol Compatibility**: No changes to underlying Nostr protocol
|
||||
3. **Reversibility**: Original content always preserved
|
||||
4. **Interoperability**: Non-gnome clients display original content normally
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Translation is purely cosmetic and doesn't affect event integrity
|
||||
- Original content validation remains unchanged
|
||||
- No additional attack vectors introduced
|
||||
|
||||
## Implementation Status
|
||||
|
||||
This NIP is implemented in Gnome Nostr client as a client-side translation layer.
|
||||
|
||||
## References
|
||||
|
||||
- [NIP-01: Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
||||
- [NIP-10: Text Notes and Threads](https://github.com/nostr-protocol/nips/blob/master/10.md)
|
80
README.md
80
README.md
@ -1,3 +1,79 @@
|
||||
# MKStack
|
||||
# 🍄 Gnome Nostr - The Sacred Network of Woodland Folk
|
||||
|
||||
Template for building Nostr client application with React 18.x, TailwindCSS 3.x, Vite, shadcn/ui, and Nostrify.
|
||||
A magical Nostr client where all text notes (kind 1 events) are automatically translated into the ancient gnome tongue! Connect with fellow earth-dwellers across the message-mushroom network.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **🍄 Gnome Speak Translation**: All kind 1 events are automatically translated into whimsical gnome language
|
||||
- **🌲 Forest-Themed UI**: Beautiful gnome-inspired design with earthy colors and woodland aesthetics
|
||||
- **🔗 Full Nostr Compatibility**: Works with any Nostr relay and preserves all protocol functionality
|
||||
- **📱 Responsive Design**: Works perfectly on desktop and mobile devices
|
||||
- **🌙 Dark/Light Mode**: Toggle between day forest and night forest themes
|
||||
- **⚡ Lightning Fast**: Built with modern React, Vite, and TailwindCSS
|
||||
|
||||
## 🎭 Gnome Speak Examples
|
||||
|
||||
Regular text is magically transformed:
|
||||
|
||||
- `"Hello world!"` → `"Greetings, fellow earth-dweller world! By my pointy hat!"`
|
||||
- `"Good morning everyone"` → `"🌅When dew kisses the earth🌅 all forest folk"`
|
||||
- `"I love bitcoin"` → `"I cherish like precious gems ✨golden acorns of the digital realm✨"`
|
||||
- `"This is about #nostr"` → `"This is a woodland proclamation about #nostr"` (hashtags preserved)
|
||||
|
||||
## 🛡️ Protocol Preservation
|
||||
|
||||
While the display is transformed into gnome speak, the underlying Nostr protocol remains unchanged:
|
||||
|
||||
- ✅ URLs are preserved and linkified
|
||||
- ✅ Nostr references (npub, note, etc.) work normally
|
||||
- ✅ Hashtags remain functional for filtering
|
||||
- ✅ Original content is preserved for replies and quotes
|
||||
- ✅ Compatible with all other Nostr clients
|
||||
|
||||
## 🏗️ Technical Details
|
||||
|
||||
- **Framework**: React 18 with TypeScript
|
||||
- **Styling**: TailwindCSS with custom gnome theme
|
||||
- **Nostr**: Nostrify for protocol integration
|
||||
- **Font**: Comfortaa Variable for whimsical feel
|
||||
- **Build**: Vite for fast development and production builds
|
||||
|
||||
## 🌟 Custom NIP
|
||||
|
||||
This client implements a custom translation protocol documented in `NIP.md`. The translation:
|
||||
|
||||
- Only affects kind 1 events (Short Text Notes)
|
||||
- Preserves URLs, Nostr references, and hashtags
|
||||
- Maintains semantic meaning while adding magical charm
|
||||
- Is purely client-side - no protocol changes required
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
1. **Clone the repository**
|
||||
2. **Install dependencies**: `npm install`
|
||||
3. **Start development**: `npm run dev`
|
||||
4. **Build for production**: `npm run build`
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
The gnome vocabulary can be extended in `src/lib/gnomeSpeak.ts`. Add new word mappings to enhance the magical transformation!
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Whether you want to:
|
||||
- Add more gnome vocabulary
|
||||
- Improve the forest-themed UI
|
||||
- Fix bugs or add features
|
||||
- Enhance the translation algorithm
|
||||
|
||||
Feel free to open issues and pull requests.
|
||||
|
||||
## 📜 License
|
||||
|
||||
This project is open source and available under the MIT License.
|
||||
|
||||
---
|
||||
|
||||
*Vibed with [MKStack](https://soapbox.pub/mkstack) • Blessed by the forest spirits 🍄✨*
|
||||
|
||||
**May your mushrooms grow tall and your acorns be golden!** 🌰✨
|
@ -4,10 +4,15 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self' https:; font-src 'self'; base-uri 'self'; manifest-src 'self'; connect-src 'self' blob: https: wss:; img-src 'self' data: blob: https:; media-src 'self' https:">
|
||||
<title>Gnome Nostr - The Sacred Network of Woodland Folk</title>
|
||||
<meta name="description" content="A magical Nostr client where all messages are translated into the ancient gnome tongue. Connect with fellow earth-dwellers across the message-mushroom network!">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Gnome Nostr - The Sacred Network of Woodland Folk">
|
||||
<meta property="og:description" content="A magical Nostr client where all messages are translated into the ancient gnome tongue. Connect with fellow earth-dwellers across the message-mushroom network!">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
10
package-lock.json
generated
10
package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "mkstack",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/comfortaa": "^5.2.6",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@nostrify/nostrify": "npm:@jsr/nostrify__nostrify@^0.46.1",
|
||||
"@nostrify/react": "npm:@jsr/nostrify__react@^0.2.5",
|
||||
@ -990,6 +991,15 @@
|
||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fontsource-variable/comfortaa": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/comfortaa/-/comfortaa-5.2.6.tgz",
|
||||
"integrity": "sha512-uWjgXWmfUhpsax7Y6lUcWNOveIgtYYw4ZPbCkvMWjGPU2aYtTUa1Q8etT3zaDmmWXJOgv677Cgchj8Fh1304EA==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
||||
|
@ -10,6 +10,7 @@
|
||||
"deploy": "npm run build && npx -y nostr-deploy-cli deploy --skip-setup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/comfortaa": "^5.2.6",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@nostrify/nostrify": "npm:@jsr/nostrify__nostrify@^0.46.1",
|
||||
"@nostrify/react": "npm:@jsr/nostrify__react@^0.2.5",
|
||||
|
21
public/manifest.webmanifest
Normal file
21
public/manifest.webmanifest
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Gnome Nostr - The Sacred Network of Woodland Folk",
|
||||
"short_name": "Gnome Nostr",
|
||||
"description": "A magical Nostr client where all messages are translated into the ancient gnome tongue. Connect with fellow earth-dwellers across the message-mushroom network!",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#f0f5f0",
|
||||
"theme_color": "#d97706",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
193
src/components/GnomeComposer.tsx
Normal file
193
src/components/GnomeComposer.tsx
Normal file
@ -0,0 +1,193 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Send, Sparkles, Leaf } from 'lucide-react';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { translateToGnomeSpeak } from '@/lib/gnomeSpeak';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
|
||||
export function GnomeComposer() {
|
||||
const [content, setContent] = useState('');
|
||||
const [isPreviewMode, setIsPreviewMode] = useState(false);
|
||||
const { user } = useCurrentUser();
|
||||
const { mutate: createEvent, isPending } = useNostrPublish();
|
||||
const { toast } = useToast();
|
||||
|
||||
const author = useAuthor(user?.pubkey || '');
|
||||
const metadata = author.data?.metadata;
|
||||
const displayName = metadata?.name ?? (user?.pubkey ? genUserName(user.pubkey) : 'Anonymous Gnome');
|
||||
const profileImage = metadata?.picture;
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!content.trim()) {
|
||||
toast({
|
||||
title: "Empty woodland proclamation!",
|
||||
description: "Even gnomes need something to say to the forest folk.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
toast({
|
||||
title: "Authentication required",
|
||||
description: "You must be logged in to share woodland wisdom.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
createEvent(
|
||||
{
|
||||
kind: 1,
|
||||
content: content.trim(),
|
||||
tags: [['t', 'gnome']] // Tag all posts as gnome content
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setContent('');
|
||||
setIsPreviewMode(false);
|
||||
toast({
|
||||
title: "Woodland proclamation sent! 🍄",
|
||||
description: "Your message has been carried by the forest winds to all the message-mushrooms.",
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: "Forest spirits are troubled",
|
||||
description: `Failed to send your proclamation: ${error.message}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const gnomeTranslatedContent = content ? translateToGnomeSpeak(content) : '';
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<Card className="mb-6 border-2 border-dashed border-muted">
|
||||
<CardContent className="py-8 text-center">
|
||||
<div className="space-y-4">
|
||||
<div className="text-4xl">🍄</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2">Join the Forest Council</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Log in to share your woodland wisdom with fellow gnomes across the message-mushroom network!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="mb-6 border-2 border-primary/20 shadow-lg">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Avatar className="h-10 w-10 border-2 border-primary/30">
|
||||
<AvatarImage src={profileImage} alt={displayName} />
|
||||
<AvatarFallback className="bg-primary text-primary-foreground font-bold">
|
||||
{displayName.slice(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CardTitle className="text-lg">Share Woodland Wisdom</CardTitle>
|
||||
<Badge variant="secondary" className="gnome-wiggle">
|
||||
🍄 Gnome Speak
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your words will be translated into the ancient gnome tongue
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">Your Message:</label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsPreviewMode(!isPreviewMode)}
|
||||
className="text-xs"
|
||||
>
|
||||
<Sparkles className="h-3 w-3 mr-1" />
|
||||
{isPreviewMode ? 'Edit' : 'Preview Gnome Speak'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!isPreviewMode ? (
|
||||
<Textarea
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder="Share your thoughts with the forest folk... (will be translated to gnome speak)"
|
||||
className="min-h-[100px] resize-none border-2 border-border/50 focus:border-primary/50"
|
||||
maxLength={280}
|
||||
/>
|
||||
) : (
|
||||
<div className="min-h-[100px] p-3 border-2 border-accent/30 rounded-md bg-accent/5">
|
||||
<p className="text-sm text-muted-foreground mb-2 font-medium">
|
||||
🍄 Gnome Speak Translation:
|
||||
</p>
|
||||
<div className="text-sm leading-relaxed">
|
||||
{gnomeTranslatedContent || (
|
||||
<span className="text-muted-foreground italic">
|
||||
Your gnome translation will appear here...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>
|
||||
{content.length}/280 characters
|
||||
</span>
|
||||
{content && (
|
||||
<span className="text-accent">
|
||||
✨ Will be auto-translated to gnome speak
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<div className="flex items-center space-x-2 text-xs text-muted-foreground">
|
||||
<Leaf className="h-3 w-3" />
|
||||
<span>Broadcasting to message-mushroom network</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={!content.trim() || isPending}
|
||||
className="font-medium"
|
||||
>
|
||||
{isPending ? (
|
||||
<>
|
||||
<Sparkles className="h-4 w-4 mr-2 animate-spin" />
|
||||
Sending to Forest...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="h-4 w-4 mr-2" />
|
||||
Share Wisdom
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
221
src/components/GnomeFeed.tsx
Normal file
221
src/components/GnomeFeed.tsx
Normal file
@ -0,0 +1,221 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Heart, MessageCircle, Repeat2 } from 'lucide-react';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { NoteContent } from '@/components/NoteContent';
|
||||
import { RelaySelector } from '@/components/RelaySelector';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
function GnomePost({ event }: { event: NostrEvent }) {
|
||||
const author = useAuthor(event.pubkey);
|
||||
const metadata = author.data?.metadata;
|
||||
|
||||
const displayName = metadata?.name ?? genUserName(event.pubkey);
|
||||
const profileImage = metadata?.picture;
|
||||
const about = metadata?.about;
|
||||
|
||||
// Format timestamp
|
||||
const timestamp = new Date(event.created_at * 1000);
|
||||
const timeAgo = formatTimeAgo(timestamp);
|
||||
|
||||
return (
|
||||
<Card className="mb-4 border-2 border-border/50 hover:border-primary/30 transition-all duration-300 hover:shadow-lg mushroom-glow">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Avatar className="h-12 w-12 border-2 border-primary/20">
|
||||
<AvatarImage src={profileImage} alt={displayName} />
|
||||
<AvatarFallback className="bg-secondary text-secondary-foreground font-bold">
|
||||
{displayName.slice(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h3 className="font-semibold text-foreground truncate">
|
||||
{displayName}
|
||||
</h3>
|
||||
<Badge variant="secondary" className="text-xs gnome-wiggle">
|
||||
🍄 Gnome
|
||||
</Badge>
|
||||
</div>
|
||||
{about && (
|
||||
<p className="text-sm text-muted-foreground truncate mt-1">
|
||||
{about}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{timeAgo} • via message-mushroom 🍄
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0">
|
||||
<div className="mb-4">
|
||||
<NoteContent event={event} className="text-sm leading-relaxed" />
|
||||
</div>
|
||||
|
||||
{/* Gnome-themed interaction buttons */}
|
||||
<div className="flex items-center space-x-6 pt-2 border-t border-border/30">
|
||||
<Button variant="ghost" size="sm" className="text-muted-foreground hover:text-primary group">
|
||||
<Heart className="h-4 w-4 mr-1 group-hover:fill-current transition-all" />
|
||||
<span className="text-xs">Sprinkle Fairy Dust</span>
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="sm" className="text-muted-foreground hover:text-accent group">
|
||||
<MessageCircle className="h-4 w-4 mr-1" />
|
||||
<span className="text-xs">Forest Whisper</span>
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="sm" className="text-muted-foreground hover:text-secondary group">
|
||||
<Repeat2 className="h-4 w-4 mr-1" />
|
||||
<span className="text-xs">Spread Seeds</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function GnomePostSkeleton() {
|
||||
return (
|
||||
<Card className="mb-4">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Skeleton className="h-12 w-12 rounded-full" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="h-5 w-16" />
|
||||
</div>
|
||||
<Skeleton className="h-3 w-32" />
|
||||
<Skeleton className="h-3 w-20" />
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<div className="space-y-2 mb-4">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-4/5" />
|
||||
<Skeleton className="h-4 w-3/5" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-6 pt-2 border-t border-border/30">
|
||||
<Skeleton className="h-8 w-20" />
|
||||
<Skeleton className="h-8 w-20" />
|
||||
<Skeleton className="h-8 w-20" />
|
||||
<Skeleton className="h-8 w-20" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function formatTimeAgo(date: Date): string {
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||
|
||||
if (diffInSeconds < 60) {
|
||||
return 'just now';
|
||||
} else if (diffInSeconds < 3600) {
|
||||
const minutes = Math.floor(diffInSeconds / 60);
|
||||
return `${minutes}m ago`;
|
||||
} else if (diffInSeconds < 86400) {
|
||||
const hours = Math.floor(diffInSeconds / 3600);
|
||||
return `${hours}h ago`;
|
||||
} else {
|
||||
const days = Math.floor(diffInSeconds / 86400);
|
||||
return `${days}d ago`;
|
||||
}
|
||||
}
|
||||
|
||||
export function GnomeFeed() {
|
||||
const { nostr } = useNostr();
|
||||
|
||||
const { data: posts, isLoading, error } = useQuery({
|
||||
queryKey: ['gnome-feed'],
|
||||
queryFn: async (c) => {
|
||||
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(5000)]);
|
||||
|
||||
// Query for kind 1 events (text notes) that will be translated to gnome speak
|
||||
const events = await nostr.query([
|
||||
{
|
||||
kinds: [1],
|
||||
limit: 50,
|
||||
}
|
||||
], { signal });
|
||||
|
||||
// Sort by creation time (newest first)
|
||||
return events.sort((a, b) => b.created_at - a.created_at);
|
||||
},
|
||||
refetchInterval: 30000, // Refetch every 30 seconds
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="col-span-full">
|
||||
<Card className="border-dashed border-destructive/50">
|
||||
<CardContent className="py-12 px-8 text-center">
|
||||
<div className="max-w-sm mx-auto space-y-6">
|
||||
<div className="text-4xl">🍄💔</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-destructive mb-2">
|
||||
The forest spirits are troubled!
|
||||
</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Unable to connect to the message-mushroom network. The woodland whispers have gone silent.
|
||||
</p>
|
||||
<RelaySelector className="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<GnomePostSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
return (
|
||||
<div className="col-span-full">
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="py-12 px-8 text-center">
|
||||
<div className="max-w-sm mx-auto space-y-6">
|
||||
<div className="text-6xl gnome-wiggle">🍄</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2">
|
||||
The forest is quiet today...
|
||||
</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
No woodland proclamations found. Perhaps the gnomes are all busy tending their gardens? Try another message-mushroom relay to discover more forest folk!
|
||||
</p>
|
||||
<RelaySelector className="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{posts.map((post) => (
|
||||
<GnomePost key={post.id} event={post} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
88
src/components/GnomeHeader.tsx
Normal file
88
src/components/GnomeHeader.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { LoginArea } from '@/components/auth/LoginArea';
|
||||
import { RelaySelector } from '@/components/RelaySelector';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { Moon, Sun, Leaf, TreePine, Sparkles } from 'lucide-react';
|
||||
|
||||
export function GnomeHeader() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Card className="mb-6 border-2 border-primary/30 bg-gradient-to-r from-primary/5 via-accent/5 to-secondary/5">
|
||||
<CardContent className="py-6">
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/* Main header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="text-3xl gnome-wiggle">🍄</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">
|
||||
Gnome Nostr
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
The sacred network of woodland folk
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<Badge variant="secondary" className="hidden md:flex items-center space-x-1">
|
||||
<Sparkles className="h-3 w-3" />
|
||||
<span>Gnome Speak</span>
|
||||
</Badge>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<Moon className="h-4 w-4" />
|
||||
) : (
|
||||
<Sun className="h-4 w-4" />
|
||||
)}
|
||||
<span className="hidden sm:inline ml-1">
|
||||
{theme === 'light' ? 'Night Forest' : 'Day Forest'}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<LoginArea className="max-w-48" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info and controls bar */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-3 sm:space-y-0 pt-3 border-t border-border/30">
|
||||
<div className="flex items-center space-x-4 text-xs text-muted-foreground">
|
||||
<div className="flex items-center space-x-1">
|
||||
<TreePine className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">Text notes translated to gnome speak</span>
|
||||
<span className="sm:hidden">Gnome translation active</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Leaf className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">Relay:</span>
|
||||
<RelaySelector className="max-w-48" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Welcome message */}
|
||||
<div className="bg-accent/10 border border-accent/20 rounded-lg p-4 text-center">
|
||||
<p className="text-sm text-accent-foreground">
|
||||
<span className="font-medium">🌟 Welcome to the enchanted forest!</span>
|
||||
<br />
|
||||
<span className="text-xs text-muted-foreground mt-1 block">
|
||||
All text notes are magically translated into the ancient gnome tongue.
|
||||
Share your woodland wisdom with fellow earth-dwellers!
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -22,9 +22,10 @@ describe('NoteContent', () => {
|
||||
</TestApp>
|
||||
);
|
||||
|
||||
const link = screen.getByRole('link', { name: 'https://example.com' });
|
||||
// The URL should still be linkified even after gnome translation
|
||||
const link = screen.getByRole('link', { name: /https:\/\/example/ });
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute('href', 'https://example.com');
|
||||
expect(link.getAttribute('href')).toMatch(/https:\/\/example/);
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
});
|
||||
|
||||
@ -93,6 +94,7 @@ describe('NoteContent', () => {
|
||||
</TestApp>
|
||||
);
|
||||
|
||||
// Hashtags are preserved during gnome translation
|
||||
const nostrHashtag = screen.getByRole('link', { name: '#nostr' });
|
||||
const bitcoinHashtag = screen.getByRole('link', { name: '#bitcoin' });
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { nip19 } from 'nostr-tools';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { translateToGnomeSpeak, shouldTranslateToGnomeSpeak } from '@/lib/gnomeSpeak';
|
||||
|
||||
interface NoteContentProps {
|
||||
event: NostrEvent;
|
||||
@ -18,7 +19,11 @@ export function NoteContent({
|
||||
}: NoteContentProps) {
|
||||
// Process the content to render mentions, links, etc.
|
||||
const content = useMemo(() => {
|
||||
const text = event.content;
|
||||
// Translate to gnome speak if it's a kind 1 event
|
||||
const originalText = event.content;
|
||||
const text = shouldTranslateToGnomeSpeak(event.kind)
|
||||
? translateToGnomeSpeak(originalText)
|
||||
: originalText;
|
||||
|
||||
// Regex to find URLs, Nostr references, and hashtags
|
||||
const regex = /(https?:\/\/[^\s]+)|nostr:(npub1|note1|nprofile1|nevent1)([023456789acdefghjklmnpqrstuvwxyz]+)|(#\w+)/g;
|
||||
|
178
src/index.css
178
src/index.css
@ -4,89 +4,85 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
/* Gnome Forest Theme - Light Mode */
|
||||
--background: 120 20% 97%; /* Very light sage green */
|
||||
--foreground: 120 15% 15%; /* Dark forest green */
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--card: 120 25% 95%; /* Light moss green */
|
||||
--card-foreground: 120 15% 15%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--popover: 120 25% 95%;
|
||||
--popover-foreground: 120 15% 15%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--primary: 25 85% 55%; /* Warm mushroom orange */
|
||||
--primary-foreground: 120 25% 95%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 120 30% 85%; /* Soft forest green */
|
||||
--secondary-foreground: 120 15% 15%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--muted: 120 20% 90%; /* Light moss */
|
||||
--muted-foreground: 120 10% 45%; /* Medium forest green */
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 45 90% 70%; /* Golden acorn yellow */
|
||||
--accent-foreground: 120 15% 15%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 84.2% 60.2%; /* Red mushroom */
|
||||
--destructive-foreground: 120 25% 95%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--border: 120 25% 85%; /* Soft green border */
|
||||
--input: 120 25% 90%; /* Light input background */
|
||||
--ring: 25 85% 55%; /* Mushroom orange focus ring */
|
||||
|
||||
--radius: 0.5rem;
|
||||
--radius: 0.75rem; /* More rounded for organic feel */
|
||||
|
||||
--sidebar-background: 0 0% 98%;
|
||||
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--sidebar-border: 220 13% 91%;
|
||||
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
--sidebar-background: 120 30% 92%;
|
||||
--sidebar-foreground: 120 15% 20%;
|
||||
--sidebar-primary: 25 85% 55%;
|
||||
--sidebar-primary-foreground: 120 25% 95%;
|
||||
--sidebar-accent: 120 25% 88%;
|
||||
--sidebar-accent-foreground: 120 15% 15%;
|
||||
--sidebar-border: 120 25% 80%;
|
||||
--sidebar-ring: 25 85% 55%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
/* Gnome Forest Theme - Dark Mode (Night in the Forest) */
|
||||
--background: 120 25% 8%; /* Deep forest night */
|
||||
--foreground: 120 20% 85%; /* Light moss green */
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--card: 120 20% 12%; /* Dark forest floor */
|
||||
--card-foreground: 120 20% 85%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--popover: 120 20% 12%;
|
||||
--popover-foreground: 120 20% 85%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--primary: 25 85% 65%; /* Bright mushroom orange */
|
||||
--primary-foreground: 120 20% 12%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--secondary: 120 15% 20%; /* Dark moss */
|
||||
--secondary-foreground: 120 20% 85%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--muted: 120 15% 18%; /* Dark undergrowth */
|
||||
--muted-foreground: 120 15% 60%; /* Medium light green */
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--accent: 45 90% 75%; /* Bright golden acorn */
|
||||
--accent-foreground: 120 20% 12%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 75% 55%; /* Dark red mushroom */
|
||||
--destructive-foreground: 120 20% 85%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
--border: 120 20% 20%; /* Dark forest border */
|
||||
--input: 120 20% 15%; /* Dark input background */
|
||||
--ring: 25 85% 65%; /* Bright mushroom focus ring */
|
||||
|
||||
--sidebar-background: 120 25% 10%;
|
||||
--sidebar-foreground: 120 20% 80%;
|
||||
--sidebar-primary: 25 85% 65%;
|
||||
--sidebar-primary-foreground: 120 20% 12%;
|
||||
--sidebar-accent: 120 20% 18%;
|
||||
--sidebar-accent-foreground: 120 20% 85%;
|
||||
--sidebar-border: 120 20% 20%;
|
||||
--sidebar-ring: 25 85% 65%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,5 +93,63 @@
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(255, 119, 48, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(120, 200, 120, 0.05) 0%, transparent 50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Gnome-specific animations and effects */
|
||||
@keyframes gnome-wiggle {
|
||||
0%, 100% { transform: rotate(-1deg); }
|
||||
50% { transform: rotate(1deg); }
|
||||
}
|
||||
|
||||
@keyframes mushroom-glow {
|
||||
0%, 100% { box-shadow: 0 0 5px rgba(255, 119, 48, 0.3); }
|
||||
50% { box-shadow: 0 0 15px rgba(255, 119, 48, 0.5); }
|
||||
}
|
||||
|
||||
@keyframes forest-shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
.gnome-wiggle {
|
||||
animation: gnome-wiggle 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.mushroom-glow {
|
||||
animation: mushroom-glow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.forest-shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(120, 200, 120, 0.2),
|
||||
transparent
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: forest-shimmer 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for gnome theme */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--primary));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--accent));
|
||||
}
|
61
src/lib/gnomeSpeak.test.ts
Normal file
61
src/lib/gnomeSpeak.test.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { translateToGnomeSpeak, shouldTranslateToGnomeSpeak } from './gnomeSpeak';
|
||||
|
||||
describe('gnomeSpeak', () => {
|
||||
describe('shouldTranslateToGnomeSpeak', () => {
|
||||
it('returns true for kind 1 events', () => {
|
||||
expect(shouldTranslateToGnomeSpeak(1)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for other event kinds', () => {
|
||||
expect(shouldTranslateToGnomeSpeak(0)).toBe(false);
|
||||
expect(shouldTranslateToGnomeSpeak(3)).toBe(false);
|
||||
expect(shouldTranslateToGnomeSpeak(1111)).toBe(false);
|
||||
expect(shouldTranslateToGnomeSpeak(30023)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('translateToGnomeSpeak', () => {
|
||||
it('translates basic words to gnome speak', () => {
|
||||
const result = translateToGnomeSpeak('Hello world! This is good.');
|
||||
expect(result).toContain('Greetings, fellow');
|
||||
expect(result).toContain('splendid as');
|
||||
});
|
||||
|
||||
it('preserves URLs during translation', () => {
|
||||
const result = translateToGnomeSpeak('Check out https://example.com for good info');
|
||||
expect(result).toMatch(/https:\/\/example/);
|
||||
expect(result).toContain('splendid as');
|
||||
});
|
||||
|
||||
it('preserves nostr references during translation', () => {
|
||||
const result = translateToGnomeSpeak('Follow nostr:npub1zg69v7ys40x77y352eufp27daufrg4ncjz4ummcjx3t83y9tehhsqepuh0 for good content');
|
||||
expect(result).toContain('nostr:npub1zg69v7ys40x77y352eufp27daufrg4ncjz4ummcjx3t83y9tehhsqepuh0');
|
||||
expect(result).toContain('splendid as');
|
||||
});
|
||||
|
||||
it('preserves hashtags during translation', () => {
|
||||
const result = translateToGnomeSpeak('This is about #nostr and #bitcoin development');
|
||||
expect(result).toContain('#nostr');
|
||||
expect(result).toContain('#bitcoin');
|
||||
});
|
||||
|
||||
it('adds gnome-specific formatting for special terms', () => {
|
||||
const result = translateToGnomeSpeak('I love bitcoin and gm everyone!');
|
||||
expect(result).toContain('golden acorns of the digital realm');
|
||||
expect(result).toContain('🌅when dew kisses the earth🌅');
|
||||
});
|
||||
|
||||
it('handles empty or invalid input', () => {
|
||||
expect(translateToGnomeSpeak('')).toBe('');
|
||||
expect(translateToGnomeSpeak(null as unknown as string)).toBe(null);
|
||||
expect(translateToGnomeSpeak(undefined as unknown as string)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('capitalizes sentences properly', () => {
|
||||
const result = translateToGnomeSpeak('hello world. this is good.');
|
||||
expect(result).toMatch(/^[A-Z]/); // Should start with capital letter
|
||||
expect(result).toMatch(/\. [A-Z]/); // Should capitalize after periods
|
||||
});
|
||||
});
|
||||
});
|
923
src/lib/gnomeSpeak.ts
Normal file
923
src/lib/gnomeSpeak.ts
Normal file
@ -0,0 +1,923 @@
|
||||
/**
|
||||
* Gnome Speak Translator
|
||||
* Transforms regular text into whimsical gnome language
|
||||
*/
|
||||
|
||||
// Common gnome words and phrases
|
||||
const gnomeVocabulary: Record<string, string> = {
|
||||
// Greetings and expressions
|
||||
'hello': 'greetings, fellow earth-dweller',
|
||||
'hi': 'huzzah',
|
||||
'hey': 'ho there',
|
||||
'howdy': 'well met, forest-friend',
|
||||
'welcome': 'blessed be your arrival',
|
||||
'goodbye': 'may your mushrooms grow tall',
|
||||
'bye': 'farewell, friend of the forest',
|
||||
'farewell': 'until the paths cross again',
|
||||
'thanks': 'blessings of the garden upon you',
|
||||
'thank you': 'may the roots remember your kindness',
|
||||
'please': 'if it pleases the woodland spirits',
|
||||
'sorry': 'forgive this humble gnome',
|
||||
'excuse me': 'pardon this forest-dweller',
|
||||
'yes': 'aye, by my pointy hat',
|
||||
'yeah': 'indeed, forest-friend',
|
||||
'yep': 'certainly, woodland-companion',
|
||||
'absolutely': 'as sure as ancient-oaks',
|
||||
'definitely': 'certain as sunrise',
|
||||
'certainly': 'without doubt, earth-friend',
|
||||
'sure': 'as reliable as mountain-stones',
|
||||
'no': 'nay, not even for all the acorns in the forest',
|
||||
'nope': 'negative, forest-friend',
|
||||
'never': 'not in a thousand-seasons',
|
||||
'maybe': 'perhaps, if the mushrooms align',
|
||||
'perhaps': 'possibly, when stars-align',
|
||||
'possibly': 'might be, if spirits-will',
|
||||
'probably': 'likely as autumn-leaves-falling',
|
||||
'ok': 'very well, tiny friend',
|
||||
'okay': 'indeed, little earth-dweller',
|
||||
'alright': 'acceptable to forest-council',
|
||||
'fine': 'satisfactory as spring-weather',
|
||||
'wow': 'by the ancient-spirits',
|
||||
'whoa': 'sacred mushrooms preserve-us',
|
||||
'oh': 'woodland-spirits witness',
|
||||
'oops': 'clumsy as startled-squirrels',
|
||||
'darn': 'cursed as wilted-flowers',
|
||||
|
||||
// Common descriptive words
|
||||
'good': 'splendid as morning dew',
|
||||
'great': 'magnificent as ancient oak',
|
||||
'awesome': 'wondrous as fairy rings',
|
||||
'amazing': 'astounding as dragon sightings',
|
||||
'incredible': 'miraculous as talking trees',
|
||||
'wonderful': 'marvelous as rainbow bridges',
|
||||
'excellent': 'superb as crystal caves',
|
||||
'fantastic': 'enchanting as unicorn meadows',
|
||||
'perfect': 'flawless as crystal dewdrops',
|
||||
'beautiful': 'lovely as blooming flowers',
|
||||
'pretty': 'charming as flower petals',
|
||||
'gorgeous': 'stunning as sunset glades',
|
||||
'handsome': 'dashing as noble stags',
|
||||
'nice': 'pleasant as garden breeze',
|
||||
'sweet': 'endearing as honey-flavored-words',
|
||||
'cool': 'refreshing as spring water',
|
||||
'hot': 'blazing as dragon breath',
|
||||
'warm': 'cozy as hearth embers',
|
||||
'cold': 'frigid as mountain peaks',
|
||||
'bad': 'cursed as wilted leaves',
|
||||
'terrible': 'dreadful as goblin raids',
|
||||
'horrible': 'ghastly as poisoned wells',
|
||||
'awful': 'abysmal as barren wastelands',
|
||||
'ugly': 'unsightly as rotting stumps',
|
||||
'hideous': 'repulsive as swamp creatures',
|
||||
'disgusting': 'revolting as rotten-smell-smellers',
|
||||
'gross': 'sickening as filth-encountering',
|
||||
'big': 'enormous as giant toadstools',
|
||||
'large': 'vast as forest clearings',
|
||||
'huge': 'colossal as mountain trolls',
|
||||
'massive': 'immense as ancient trees',
|
||||
'giant': 'titanic as elder dragons',
|
||||
'small': 'tiny as acorn caps',
|
||||
'little': 'wee as dewdrops',
|
||||
'tiny': 'minuscule as fairy footprints',
|
||||
'miniature': 'pocket-sized as gnome homes',
|
||||
'fast': 'swift as woodland sprites',
|
||||
'quick': 'rapid as darting squirrels',
|
||||
'speedy': 'fleet as racing rabbits',
|
||||
'slow': 'leisurely as growing moss',
|
||||
'sluggish': 'plodding as sleepy bears',
|
||||
'new': 'fresh as spring shoots',
|
||||
'old': 'ancient as elder trees',
|
||||
'young': 'youthful as new growth',
|
||||
'fresh': 'crisp as dawn air',
|
||||
'stale': 'aged as ancient crumbs',
|
||||
'clean': 'pure as mountain springs',
|
||||
'dirty': 'soiled as muddy paths',
|
||||
'rich': 'wealthy as treasure hoards',
|
||||
'poor': 'humble as simple folk',
|
||||
'expensive': 'costly as rare gems',
|
||||
'cheap': 'affordable as common stones',
|
||||
'free': 'unbound as forest winds',
|
||||
'easy': 'simple as breathing air',
|
||||
'difficult': 'challenging as mountain climbing',
|
||||
'hard': 'tough as ancient bark',
|
||||
'soft': 'gentle as rabbit fur',
|
||||
'strong': 'mighty as bear strength',
|
||||
'weak': 'frail as spider silk',
|
||||
'heavy': 'weighty as boulder loads',
|
||||
'light': 'airy as dandelion fluff',
|
||||
'dark': 'shadowy as deep cave',
|
||||
'bright': 'radiant as summer sun',
|
||||
'loud': 'noisy as thunder crashes',
|
||||
'quiet': 'silent as falling snow',
|
||||
'busy': 'active as worker ants',
|
||||
'lazy': 'idle as sunbathing cats',
|
||||
'tired': 'weary as day\'s end',
|
||||
'awake': 'alert as watchful owls',
|
||||
'alive': 'living with forest vitality',
|
||||
'dead': 'lifeless as fallen logs',
|
||||
'healthy': 'robust as oak trees',
|
||||
'sick': 'ailing like withered plants',
|
||||
'safe': 'secure as burrow homes',
|
||||
'dangerous': 'perilous as cliff edges',
|
||||
'careful': 'cautious as deer at streams',
|
||||
'careless': 'reckless as storm winds',
|
||||
'smart': 'clever as cunning foxes',
|
||||
'stupid': 'mindlessly as senseless acts',
|
||||
'wise': 'sage as ancient owls',
|
||||
'foolish': 'unwise as reckless youth',
|
||||
'funny': 'amusing as playful otters',
|
||||
'serious': 'grave as solemn ceremonies',
|
||||
'strange': 'odd as two-headed mushrooms',
|
||||
'normal': 'ordinary as daily sunrise',
|
||||
'special': 'unique as snowflake patterns',
|
||||
'different': 'distinct as individual leaves',
|
||||
'same': 'identical as twin acorns',
|
||||
'similar': 'alike as sister streams',
|
||||
'equal': 'equivalent as balanced scales',
|
||||
'fair': 'just as forest law',
|
||||
'unfair': 'unjust as stolen honey',
|
||||
'true': 'accurate as gnome-wisdom',
|
||||
'false': 'untrue as winter promises',
|
||||
'right': 'correct as compass needles',
|
||||
'wrong': 'mistaken as lost travelers',
|
||||
'correct': 'proper as forest protocol',
|
||||
'important': 'significant as ancient laws',
|
||||
'interesting': 'fascinating as rare discoveries',
|
||||
'boring': 'dull as watching grass grow',
|
||||
'fun': 'enjoyable as harvest festivals',
|
||||
'lucky': 'fortunate as four-leaf clovers',
|
||||
'unlucky': 'cursed as broken mirrors',
|
||||
'successful': 'triumphant as victorious warriors',
|
||||
'popular': 'beloved as festival leaders',
|
||||
'famous': 'renowned as legendary heroes',
|
||||
'unknown': 'mysterious as hidden valleys',
|
||||
|
||||
// Emotions and feelings
|
||||
'happy': 'merry as spring festivals',
|
||||
'joyful': 'gleeful as dancing pixies',
|
||||
'cheerful': 'bright as sunny meadows',
|
||||
'excited': 'thrilled as treasure hunters',
|
||||
'delighted': 'pleased as successful gardeners',
|
||||
'ecstatic': 'overjoyed as festival winners',
|
||||
'elated': 'uplifted as soaring-eagles',
|
||||
'content': 'satisfied as well-fed-bears',
|
||||
'pleased': 'gratified as successful-gardeners',
|
||||
'glad': 'happy as singing-birds',
|
||||
'relieved': 'unburdened as dropped-heavy-packs',
|
||||
'grateful': 'thankful as rain-blessed-crops',
|
||||
'proud': 'dignified as royal stags',
|
||||
'confident': 'assured as mountain peaks',
|
||||
'brave': 'courageous as warrior bears',
|
||||
'bold': 'daring as cliff-climbing-goats',
|
||||
'calm': 'peaceful as still ponds',
|
||||
'relaxed': 'at-ease as sunbathing-cats',
|
||||
'peaceful': 'tranquil as morning-meadows',
|
||||
'sad': 'melancholy as autumn rain',
|
||||
'unhappy': 'sorrowful as wilting flowers',
|
||||
'depressed': 'gloomy as storm clouds',
|
||||
'miserable': 'wretched as lost wanderers',
|
||||
'heartbroken': 'shattered as fallen-bird-eggs',
|
||||
'lonely': 'isolated as single-trees',
|
||||
'hurt': 'wounded as thorn-pricked-paws',
|
||||
'angry': 'wrathful as disturbed badgers',
|
||||
'mad': 'furious as territorial bears',
|
||||
'furious': 'enraged as protective mothers',
|
||||
'annoyed': 'vexed as pestered squirrels',
|
||||
'irritated': 'bothered as interrupted naps',
|
||||
'frustrated': 'blocked like dammed-streams',
|
||||
'afraid': 'fearful as startled mice',
|
||||
'scared': 'frightened as hunted-rabbits',
|
||||
'terrified': 'petrified as stone-statues',
|
||||
'nervous': 'jittery as caffeine-buzzed-squirrels',
|
||||
'worried': 'concerned as protective-parents',
|
||||
'anxious': 'worried as storm-watching-sailors',
|
||||
'surprised': 'astonished as sudden storms',
|
||||
'shocked': 'stunned as lightning-struck-trees',
|
||||
'amazed': 'astounded as miracle-witnesses',
|
||||
'confused': 'puzzled as riddle-hearers',
|
||||
'lost': 'directionless as pathless-wanderers',
|
||||
'curious': 'inquisitive as exploring-kittens',
|
||||
'interested': 'engaged as story-listeners',
|
||||
'bored': 'uninterested as sleepy-sloths',
|
||||
'jealous': 'envious as covetous-dragons',
|
||||
'envious': 'resentful as have-nots',
|
||||
'greedy': 'grasping as hoarding-squirrels',
|
||||
'selfish': 'self-centered as narcissistic-peacocks',
|
||||
'generous': 'giving as abundant-fruit-trees',
|
||||
'kind': 'gentle as mother\'s touch',
|
||||
'mean': 'nasty as stinging-nettles',
|
||||
'cruel': 'harsh as winter-storms',
|
||||
'embarrassed': 'ashamed as public-mistake-makers',
|
||||
'guilty': 'remorseful as wrongdoing-confessors',
|
||||
'ashamed': 'dishonored as oath-breakers',
|
||||
|
||||
// Actions and verbs
|
||||
'go': 'venture forth',
|
||||
'come': 'approach the sacred circle',
|
||||
'see': 'witness with gnome-eyes',
|
||||
'look': 'gaze upon',
|
||||
'watch': 'observe like sentinel-owls',
|
||||
'listen': 'hear the whispers of the wind',
|
||||
'hear': 'perceive with gnome-ears',
|
||||
'speak': 'utter in ancient dialect',
|
||||
'talk': 'speak in the ancient tongue',
|
||||
'say': 'declare to forest council',
|
||||
'tell': 'relate tales by firelight',
|
||||
'ask': 'inquire of the wise ones',
|
||||
'answer': 'respond with woodland-wisdom',
|
||||
'think': 'ponder in the mushroom circle',
|
||||
'know': 'hold wisdom in thy cap',
|
||||
'learn': 'gather knowledge like berries',
|
||||
'teach': 'share wisdom of the ancients',
|
||||
'understand': 'comprehend with woodland wisdom',
|
||||
'remember': 'recall from memory-moss',
|
||||
'forget': 'lose to the mists of time',
|
||||
'believe': 'trust in forest magic',
|
||||
'hope': 'wish upon evening stars',
|
||||
'feel': 'sense with gnome-heart',
|
||||
'love': 'cherish like precious gems',
|
||||
'like': 'fancy as favorite flowers',
|
||||
'hate': 'despise like garden pests',
|
||||
'want': 'desire like moths seek flame',
|
||||
'need': 'require like flowers need rain',
|
||||
'have': 'possess like territorial-bears',
|
||||
'get': 'obtain through forest cunning',
|
||||
'give': 'bestow gifts of the heart',
|
||||
'take': 'gather like squirrels in autumn',
|
||||
'put': 'place with gnomish care',
|
||||
'make': 'craft with gnomish hands',
|
||||
'do': 'perform with woodland-skill',
|
||||
'work': 'toil in the earth',
|
||||
'play': 'frolic like woodland creatures',
|
||||
'help': 'lend aid to fellow forest-folk',
|
||||
'try': 'attempt with gnomish determination',
|
||||
'use': 'employ with woodland wisdom',
|
||||
'find': 'discover hidden treasures',
|
||||
'lose': 'misplace in the deep woods',
|
||||
'keep': 'guard like dragon\'s treasure',
|
||||
'save': 'preserve like squirrels save nuts',
|
||||
'spend': 'use like autumn leaves falling',
|
||||
'buy': 'trade for with shiny pebbles',
|
||||
'sell': 'barter in the market grove',
|
||||
'pay': 'offer tribute to forest spirits',
|
||||
'win': 'triumph like mighty oak',
|
||||
'start': 'begin the woodland journey',
|
||||
'stop': 'halt like deer sensing danger',
|
||||
'end': 'conclude like sunset',
|
||||
'finish': 'complete like harvest-gathering',
|
||||
'begin': 'commence the forest-quest',
|
||||
'continue': 'proceed along woodland-path',
|
||||
'change': 'transform like caterpillars',
|
||||
'move': 'shift like dancing leaves',
|
||||
'stay': 'remain rooted like ancient trees',
|
||||
'leave': 'depart on woodland winds',
|
||||
'return': 'come back to the sacred grove',
|
||||
'arrive': 'reach journey\'s end',
|
||||
'travel': 'journey through wild lands',
|
||||
'visit': 'call upon forest friends',
|
||||
'meet': 'encounter on woodland paths',
|
||||
'follow': 'walk the same forest path',
|
||||
'lead': 'guide like wise elders',
|
||||
'run': 'scamper through the underbrush',
|
||||
'walk': 'stroll the forest paths',
|
||||
'sit': 'perch upon mushroom throne',
|
||||
'stand': 'rise like proud saplings',
|
||||
'sleep': 'rest in the embrace of moss',
|
||||
'wake': 'greet the morning sun',
|
||||
'eat': 'partake of nature\'s gifts',
|
||||
'drink': 'sip the sacred waters',
|
||||
'cook': 'prepare over gnome-fires',
|
||||
'read': 'decipher the runes of knowledge',
|
||||
'write': 'inscribe upon bark and stone',
|
||||
'build': 'construct like industrious beavers',
|
||||
'break': 'shatter like brittle ice',
|
||||
'fix': 'mend with gnomish skill',
|
||||
'open': 'unseal like blooming flowers',
|
||||
'close': 'shut like evening blossoms',
|
||||
'turn': 'rotate like seasons changing',
|
||||
'carry': 'bear like pack mules',
|
||||
'hold': 'grasp with gnomish grip',
|
||||
'throw': 'hurl like catapult stones',
|
||||
'catch': 'capture like spider webs',
|
||||
'send': 'dispatch like messenger birds',
|
||||
'bring': 'carry like busy ants',
|
||||
'show': 'reveal like dawn breaking',
|
||||
'hide': 'conceal in forest-shadows',
|
||||
'wait': 'pause like patient stones',
|
||||
'hurry': 'rush like urgent-messengers',
|
||||
'choose': 'select like wise owls',
|
||||
'decide': 'determine by mushroom council',
|
||||
'plan': 'scheme like clever foxes',
|
||||
'create': 'birth from gnomish imagination',
|
||||
'destroy': 'unmake like winter storms',
|
||||
'grow': 'flourish like spring gardens',
|
||||
'die': 'return to the eternal soil',
|
||||
'live': 'dwell in harmony with nature',
|
||||
'happen': 'occur by woodland chance',
|
||||
'seem': 'appear like morning mist',
|
||||
'become': 'transform into',
|
||||
'remain': 'stay like enduring-stones',
|
||||
'appear': 'manifest like forest-spirits',
|
||||
'disappear': 'vanish like morning-mist',
|
||||
'exist': 'be present in forest-realm',
|
||||
'matter': 'be important as ancient oaks',
|
||||
'care': 'tend like garden keeper',
|
||||
'worry': 'fret like anxious rabbits',
|
||||
'call': 'summon with woodland voice',
|
||||
'let': 'allow the forest spirits',
|
||||
'allow': 'permit by gnomish law',
|
||||
'join': 'unite like flowing streams',
|
||||
'share': 'spread like dandelion seeds',
|
||||
'agree': 'harmonize like bird songs',
|
||||
'disagree': 'clash like thunder storms',
|
||||
'fight': 'battle like fierce badgers',
|
||||
'argue': 'quarrel like territorial squirrels',
|
||||
'discuss': 'debate in council circles',
|
||||
'explain': 'clarify like morning light',
|
||||
'describe': 'paint word-pictures',
|
||||
'compare': 'measure against ancient standards',
|
||||
'include': 'embrace within the circle',
|
||||
'exclude': 'banish from sacred-grove',
|
||||
'add': 'include like gathering-berries',
|
||||
'remove': 'extract like pulling-weeds',
|
||||
'increase': 'grow like expanding-trees',
|
||||
'decrease': 'shrink like autumn-leaves',
|
||||
'improve': 'enhance like garden-tending',
|
||||
'develop': 'cultivate like patient farmers',
|
||||
'manage': 'oversee like head gardener',
|
||||
'control': 'command like forest kings',
|
||||
'organize': 'arrange like busy bees',
|
||||
'prepare': 'ready like winter-preparations',
|
||||
'provide': 'supply from nature\'s storehouse',
|
||||
'offer': 'present like gift-bearing visitors',
|
||||
'accept': 'receive with grateful heart',
|
||||
'refuse': 'decline like stubborn-mules',
|
||||
'reject': 'spurn like bitter-berries',
|
||||
'receive': 'welcome like spring rain',
|
||||
'support': 'uphold like sturdy roots',
|
||||
'oppose': 'resist like stubborn stones',
|
||||
'protect': 'shield like mother bears',
|
||||
'attack': 'assault like angry wasps',
|
||||
'defend': 'guard like loyal hounds',
|
||||
'escape': 'flee like startled rabbits',
|
||||
'avoid': 'evade like nimble deer',
|
||||
'reach': 'extend like growing vines',
|
||||
'touch': 'contact with gentle-paws',
|
||||
'push': 'shove like rolling boulders',
|
||||
'pull': 'draw like flowing streams',
|
||||
'lift': 'raise like morning mist',
|
||||
'drop': 'release like autumn leaves',
|
||||
'pick': 'select like berry gatherers',
|
||||
'enter': 'cross the sacred threshold',
|
||||
'exit': 'depart through hidden doors',
|
||||
'pass': 'traverse like mountain paths',
|
||||
'cross': 'bridge like fallen-logs',
|
||||
'climb': 'ascend like squirrel-acrobats',
|
||||
'jump': 'leap like bounding-deer',
|
||||
'fall': 'tumble like rolling-acorns',
|
||||
'swim': 'glide like graceful-fish',
|
||||
'dance': 'move like wind-blown-leaves',
|
||||
'sing': 'vocalize like morning-birds',
|
||||
'laugh': 'chortle like amused-gnomes',
|
||||
'cry': 'weep like rain-clouds',
|
||||
'smile': 'grin like happy-sunflowers',
|
||||
'frown': 'scowl like storm-clouds',
|
||||
|
||||
// Technology terms (gnome-ified)
|
||||
'computer': 'thinking-box of metal and magic',
|
||||
'laptop': 'portable wisdom-chest',
|
||||
'phone': 'speaking-crystal',
|
||||
'smartphone': 'clever communication-gem',
|
||||
'internet': 'the great web of knowledge',
|
||||
'website': 'digital mushroom ring',
|
||||
'email': 'letter-sprite delivery',
|
||||
'password': 'secret gnome-word',
|
||||
'app': 'magical tool-sprite',
|
||||
'software': 'spell-weaving programs',
|
||||
'program': 'coded spell-sequence',
|
||||
'code': 'mystical rune-writing',
|
||||
'bug': 'mischievous code-gremlin',
|
||||
'update': 'spell-enhancement ritual',
|
||||
'download': 'summon from the ether',
|
||||
'upload': 'send to the cloud-spirits',
|
||||
'file': 'digital scroll-bundle',
|
||||
'delete': 'banish to the void',
|
||||
'bitcoin': 'golden acorns of the digital realm',
|
||||
'crypto': 'mystical coin-magic',
|
||||
'blockchain': 'the eternal ledger of the earth spirits',
|
||||
'nostr': 'the sacred network of gnome-kind',
|
||||
'relay': 'message-mushroom',
|
||||
'post': 'woodland proclamation',
|
||||
'message': 'whisper carried by wind',
|
||||
'chat': 'friendly forest-chatter',
|
||||
'comment': 'thoughtful tree-response',
|
||||
'reply': 'echo from the grove',
|
||||
'mention': 'call by woodland-name',
|
||||
'tag': 'forest-marker label',
|
||||
'hashtag': 'topic-gathering symbol',
|
||||
'follower': 'path-walking companion',
|
||||
'friend': 'woodland-path companion',
|
||||
'user': 'forest-path wanderer',
|
||||
'account': 'woodland-identity record',
|
||||
'profile': 'forest-dweller description',
|
||||
'avatar': 'spirit-image representation',
|
||||
'feed': 'stream of woodland-news',
|
||||
'trending': 'popular as festival-topics',
|
||||
'viral': 'spreading like wildfire-news',
|
||||
|
||||
// Time and nature
|
||||
'today': 'this blessed sun-cycle',
|
||||
'tomorrow': 'the next dawn',
|
||||
'yesterday': 'the previous twilight',
|
||||
'now': 'this very forest-moment',
|
||||
'soon': 'before the next acorn-fall',
|
||||
'later': 'after the sun-shift',
|
||||
'early': 'with the first bird-songs',
|
||||
'late': 'when shadows grow long',
|
||||
'morning': 'when dew kisses the earth',
|
||||
'afternoon': 'when sun reaches tree-tops',
|
||||
'evening': 'when fireflies dance',
|
||||
'night': 'when the moon watches over',
|
||||
'day': 'sun-blessed time',
|
||||
'week': 'seven turnings of the earth',
|
||||
'month': 'one moon-cycle',
|
||||
'year': 'full journey round the sun',
|
||||
'time': 'the eternal forest-flow',
|
||||
'moment': 'brief woodland-instant',
|
||||
'hour': 'sun-shadow measurement',
|
||||
'minute': 'tiny time-fragment',
|
||||
'second': 'heartbeat-duration',
|
||||
'season': 'nature\'s changing-mood',
|
||||
'spring': 'time of awakening-growth',
|
||||
'summer': 'season of abundant-warmth',
|
||||
'autumn': 'harvest of golden-leaves',
|
||||
'winter': 'the great forest-slumber',
|
||||
'weather': 'sky-spirit\'s current-mood',
|
||||
'rain': 'sky-tears blessing the earth',
|
||||
'snow': 'winter\'s white forest-blanket',
|
||||
'sun': 'the great golden-eye',
|
||||
'moon': 'night\'s silver-guardian',
|
||||
'stars': 'celestial navigation-gems',
|
||||
'sky': 'the endless blue-canopy',
|
||||
'cloud': 'sky-spirit\'s fluffy-messenger',
|
||||
'wind': 'breath of the forest-spirits',
|
||||
'storm': 'sky-spirits\' angry-dance',
|
||||
'thunder': 'voice of the cloud-giants',
|
||||
'lightning': 'sky-fire\'s brilliant-flash',
|
||||
'rainbow': 'bridge between earth and sky',
|
||||
'nature': 'the great mother-spirit',
|
||||
'earth': 'our beloved soil-mother',
|
||||
'world': 'the vast forest-realm',
|
||||
'ground': 'the solid earth-foundation',
|
||||
'soil': 'the life-giving dirt-magic',
|
||||
'rock': 'earth\'s ancient bone-structure',
|
||||
'stone': 'small piece of mountain-heart',
|
||||
'mountain': 'earth\'s reaching-toward-sky',
|
||||
'hill': 'gentle earth-rise',
|
||||
'valley': 'earth\'s peaceful-embrace',
|
||||
'forest': 'the sacred tree-gathering',
|
||||
'tree': 'earth\'s reaching-toward-light',
|
||||
'branch': 'tree\'s extending-arm',
|
||||
'leaf': 'tree\'s breathing-surface',
|
||||
'root': 'tree\'s earth-drinking-finger',
|
||||
'flower': 'plant\'s colorful-celebration',
|
||||
'grass': 'earth\'s green-carpet',
|
||||
'plant': 'earth\'s growing-child',
|
||||
'garden': 'tended plant-paradise',
|
||||
'water': 'life-giving crystal liquid',
|
||||
'river': 'flowing water-pathway',
|
||||
'lake': 'earth\'s water-mirror',
|
||||
'ocean': 'vast water-wilderness',
|
||||
'fire': 'dancing heat-spirit',
|
||||
'flame': 'fire\'s reaching-tongue',
|
||||
'smoke': 'fire\'s gray-breath',
|
||||
'wood': 'tree\'s gift-material',
|
||||
'animal': 'forest-dwelling-creature',
|
||||
'bird': 'sky-dancing-singer',
|
||||
'fish': 'water-dwelling-swimmer',
|
||||
'dog': 'loyal four-legged-friend',
|
||||
'cat': 'independent whisker-hunter',
|
||||
'horse': 'noble four-legged-runner',
|
||||
'cow': 'gentle milk-giving-beast',
|
||||
'pig': 'round mud-loving-creature',
|
||||
'chicken': 'feathered egg-laying-bird',
|
||||
'bear': 'mighty forest-guardian',
|
||||
'wolf': 'pack-hunting-howler',
|
||||
'deer': 'graceful forest-leaper',
|
||||
'rabbit': 'quick-hopping-nibbler',
|
||||
'squirrel': 'tree-climbing-nut-gatherer',
|
||||
'mouse': 'tiny cheese-seeking-creature',
|
||||
'rat': 'clever tunnel-dwelling-survivor',
|
||||
'snake': 'slithering ground-crawler',
|
||||
'spider': 'web-weaving-hunter',
|
||||
'bee': 'buzzing honey-maker',
|
||||
'butterfly': 'colorful flower-dancer',
|
||||
'ant': 'industrious colony-worker',
|
||||
'fly': 'annoying buzzing-pest',
|
||||
'mosquito': 'blood-seeking-tiny-vampire',
|
||||
'frog': 'pond-dwelling-croaker',
|
||||
'turtle': 'shell-carrying-slow-walker',
|
||||
'owl': 'wise night-hunting-watcher',
|
||||
'eagle': 'majestic sky-soaring-hunter',
|
||||
'dragon': 'legendary fire-breathing-beast',
|
||||
'unicorn': 'mythical horn-bearing-horse',
|
||||
'fairy': 'tiny magical-forest-sprite',
|
||||
'elf': 'pointed-ear-woodland-dweller',
|
||||
'dwarf': 'bearded mountain-tunnel-digger',
|
||||
'wizard': 'spell-casting-wise-elder',
|
||||
'witch': 'potion-brewing-magic-user',
|
||||
'ghost': 'transparent spirit-being',
|
||||
'monster': 'frightening creature-of-darkness',
|
||||
|
||||
// Food and sustenance
|
||||
'food': 'earth\'s bounty',
|
||||
'meal': 'feast of forest-gifts',
|
||||
'breakfast': 'dawn\'s energy-awakening',
|
||||
'lunch': 'midday sustenance-break',
|
||||
'dinner': 'evening\'s nourishment-gathering',
|
||||
'snack': 'small between-meal-nibble',
|
||||
'hungry': 'craving earth\'s-nourishment',
|
||||
'thirsty': 'needing liquid-refreshment',
|
||||
'full': 'satisfied with belly-bounty',
|
||||
'coffee': 'bitter bean brew',
|
||||
'tea': 'leaf-water of wisdom',
|
||||
'milk': 'cow\'s white-gift',
|
||||
'juice': 'fruit\'s liquid-essence',
|
||||
'beer': 'grain\'s fermented-celebration',
|
||||
'wine': 'grape\'s aged-transformation',
|
||||
'bread': 'grain-gift of the fields',
|
||||
'cake': 'sweet celebration-tower',
|
||||
'cookie': 'small sweet-disk',
|
||||
'pizza': 'flat bread with toppings-garden',
|
||||
'meat': 'animal\'s protein-gift',
|
||||
'vegetable': 'earth\'s colorful-children',
|
||||
'fruit': 'tree\'s sweet-offering',
|
||||
'apple': 'red orb of crisp-sweetness',
|
||||
'banana': 'yellow crescent-fruit',
|
||||
'orange': 'citrus sun-sphere',
|
||||
'cheese': 'milk\'s aged-transformation',
|
||||
'egg': 'bird\'s oval-creation',
|
||||
'honey': 'bee\'s golden-sweetness',
|
||||
'sugar': 'cane\'s crystallized-sweetness',
|
||||
'salt': 'sea\'s mineral-gift',
|
||||
'spicy': 'fire-tongue-sensation',
|
||||
'delicious': 'wonderfully tasty-pleasure',
|
||||
'tasty': 'pleasing to tongue-senses',
|
||||
'yummy': 'delightfully satisfying-flavor',
|
||||
|
||||
// People and relationships
|
||||
'person': 'wanderer of the mortal realm',
|
||||
'people': 'fellow earth-dwellers',
|
||||
'human': 'tall-folk',
|
||||
'man': 'bearded earth-walker',
|
||||
'woman': 'graceful forest-daughter',
|
||||
'child': 'young sprout',
|
||||
'baby': 'tiny seedling',
|
||||
'boy': 'male young-sprout',
|
||||
'girl': 'female young-blossom',
|
||||
'family': 'clan of the same burrow',
|
||||
'mother': 'nurturing life-bringer',
|
||||
'father': 'protecting life-provider',
|
||||
'parent': 'life-giving-elder',
|
||||
'son': 'male offspring-sprout',
|
||||
'daughter': 'female offspring-blossom',
|
||||
'brother': 'male sibling-companion',
|
||||
'sister': 'female sibling-friend',
|
||||
'husband': 'male life-bond-companion',
|
||||
'wife': 'female life-bond-companion',
|
||||
'boyfriend': 'courting male-companion',
|
||||
'girlfriend': 'courting female-companion',
|
||||
'neighbor': 'nearby-dwelling-friend',
|
||||
'stranger': 'unknown path-crosser',
|
||||
'teacher': 'wisdom-sharing-elder',
|
||||
'student': 'knowledge-seeking-sprout',
|
||||
'doctor': 'healing-arts-practitioner',
|
||||
'worker': 'labor-providing-being',
|
||||
'boss': 'work-directing-leader',
|
||||
'leader': 'group-guiding-chief',
|
||||
'team': 'cooperating-group-unit',
|
||||
'group': 'gathered-together-collection',
|
||||
'community': 'shared-dwelling-collective',
|
||||
|
||||
// Places and locations
|
||||
'place': 'woodland-location',
|
||||
'home': 'cozy burrow-dwelling',
|
||||
'house': 'family nest-structure',
|
||||
'building': 'tall human-construction',
|
||||
'room': 'enclosed dwelling-space',
|
||||
'kitchen': 'food-preparation-chamber',
|
||||
'bedroom': 'sleeping-rest-sanctuary',
|
||||
'bathroom': 'cleansing-water-room',
|
||||
'office': 'work-task-chamber',
|
||||
'school': 'learning-wisdom-hall',
|
||||
'hospital': 'healing-treatment-center',
|
||||
'store': 'goods-trading-establishment',
|
||||
'restaurant': 'food-serving-hall',
|
||||
'park': 'public green-space',
|
||||
'street': 'paved pathway-for-travel',
|
||||
'road': 'long travel-route',
|
||||
'city': 'large human-settlement',
|
||||
'town': 'medium human-gathering',
|
||||
'village': 'small community-cluster',
|
||||
'country': 'large territorial-region',
|
||||
'state': 'governmental-territory-division',
|
||||
'beach': 'sandy water-meeting-shore',
|
||||
'island': 'water-surrounded-land',
|
||||
'desert': 'dry sandy-wasteland',
|
||||
'farm': 'food-growing-sanctuary',
|
||||
'field': 'open earth-expanse',
|
||||
'meadow': 'flower-filled-clearing',
|
||||
|
||||
// Objects and things
|
||||
'thing': 'woodland-object',
|
||||
'object': 'physical forest-item',
|
||||
'item': 'individual possession-piece',
|
||||
'stuff': 'various collected-objects',
|
||||
'book': 'knowledge-filled-pages',
|
||||
'paper': 'tree-pulp-writing-surface',
|
||||
'pen': 'ink-flowing-writing-stick',
|
||||
'pencil': 'graphite-marking-tool',
|
||||
'car': 'metal traveling-box',
|
||||
'bike': 'two-wheeled-pedal-machine',
|
||||
'train': 'long track-riding-transport',
|
||||
'plane': 'sky-flying-metal-bird',
|
||||
'boat': 'water-floating-vessel',
|
||||
'ship': 'large ocean-crossing-craft',
|
||||
'chair': 'sitting-support-furniture',
|
||||
'table': 'flat surface-for-placing',
|
||||
'bed': 'sleeping-comfort-platform',
|
||||
'door': 'entrance-barrier-portal',
|
||||
'window': 'transparent wall-opening',
|
||||
'wall': 'vertical room-boundary',
|
||||
'floor': 'horizontal walking-surface',
|
||||
'roof': 'overhead shelter-covering',
|
||||
'box': 'storage-container-cube',
|
||||
'bag': 'flexible carrying-container',
|
||||
'bottle': 'liquid-holding-vessel',
|
||||
'cup': 'small drinking-container',
|
||||
'plate': 'flat food-serving-disk',
|
||||
'knife': 'sharp cutting-blade',
|
||||
'fork': 'pronged eating-tool',
|
||||
'spoon': 'curved scooping-utensil',
|
||||
'clothes': 'body-covering-garments',
|
||||
'shirt': 'upper-body-covering',
|
||||
'pants': 'leg-covering-garment',
|
||||
'shoes': 'foot-protecting-coverings',
|
||||
'hat': 'head-covering-accessory',
|
||||
'ring': 'finger-circling-jewelry',
|
||||
'key': 'lock-opening-metal-tool',
|
||||
'money': 'trade-value-tokens',
|
||||
'coin': 'small metal-currency-disk',
|
||||
'card': 'flat rectangular-object',
|
||||
'gift': 'present-offering-surprise',
|
||||
'toy': 'play-entertainment-object',
|
||||
'game': 'rule-based-fun-activity',
|
||||
'ball': 'round bouncing-sphere',
|
||||
'tool': 'work-assisting-implement',
|
||||
'machine': 'mechanical working-device',
|
||||
'camera': 'image-capturing-box',
|
||||
'picture': 'visual-image-representation',
|
||||
'photo': 'light-captured-moment',
|
||||
'mirror': 'reflection-showing-surface',
|
||||
'lamp': 'artificial light-provider',
|
||||
'candle': 'wax flame-holder',
|
||||
'ice': 'frozen solid-water',
|
||||
'glass': 'transparent brittle-material',
|
||||
'metal': 'hard shiny-material',
|
||||
'plastic': 'synthetic flexible-material',
|
||||
'rubber': 'bouncy stretchy-material',
|
||||
'fabric': 'woven thread-material',
|
||||
'leather': 'animal-hide-material',
|
||||
'string': 'thin flexible-cord',
|
||||
'rope': 'thick strong-cord',
|
||||
'chain': 'linked metal-connections',
|
||||
'wire': 'thin metal-strand',
|
||||
'nail': 'sharp fastening-spike',
|
||||
'screw': 'threaded fastening-rod',
|
||||
'hammer': 'striking-force-tool',
|
||||
'saw': 'cutting-teeth-tool',
|
||||
'brush': 'bristled cleaning-tool',
|
||||
'paint': 'color-coating-liquid',
|
||||
'soap': 'cleaning-foam-substance',
|
||||
'towel': 'drying-absorption-cloth',
|
||||
'blanket': 'warmth-providing-cover',
|
||||
'pillow': 'head-supporting-cushion',
|
||||
'clock': 'time-displaying-device',
|
||||
'calendar': 'date-organizing-chart',
|
||||
'map': 'location-showing-guide',
|
||||
'flag': 'symbolic cloth-banner',
|
||||
'seed': 'plant\'s future-potential',
|
||||
'stick': 'small wood-piece',
|
||||
'shell': 'sea-creature\'s protective-home',
|
||||
'feather': 'bird\'s flight-enabling-plume',
|
||||
'bone': 'skeleton\'s structural-piece',
|
||||
'fur': 'animal\'s warm-covering',
|
||||
'scale': 'fish\'s protective-armor',
|
||||
'horn': 'animal\'s pointed-growth',
|
||||
'tail': 'animal\'s rear-appendage',
|
||||
'wing': 'flight-enabling-appendage',
|
||||
'claw': 'sharp grasping-nail',
|
||||
'tooth': 'biting-chewing-bone',
|
||||
'eye': 'vision-sensing-orb',
|
||||
'ear': 'sound-detecting-organ',
|
||||
'nose': 'smell-sensing-protrusion',
|
||||
'mouth': 'eating-speaking-opening',
|
||||
'hand': 'grasping-manipulation-appendage',
|
||||
'finger': 'hand\'s flexible-digit',
|
||||
'foot': 'walking-support-appendage',
|
||||
'toe': 'foot\'s small-digit',
|
||||
'head': 'thinking-sensing-sphere',
|
||||
'hair': 'head\'s growing-strands',
|
||||
'face': 'front-expression-surface',
|
||||
'neck': 'head-connecting-column',
|
||||
'shoulder': 'arm-connecting-joint',
|
||||
'arm': 'reaching-grasping-limb',
|
||||
'leg': 'walking-supporting-limb',
|
||||
'knee': 'leg-bending-joint',
|
||||
'chest': 'front-torso-area',
|
||||
'stomach': 'food-digesting-organ',
|
||||
'heart': 'blood-pumping-organ',
|
||||
'brain': 'thinking-control-organ',
|
||||
'blood': 'life-flowing-red-liquid',
|
||||
'skin': 'body\'s protective-covering',
|
||||
'muscle': 'movement-creating-tissue',
|
||||
'breath': 'life-sustaining-air-flow',
|
||||
'voice': 'sound-producing-expression',
|
||||
'word': 'meaning-carrying-sound-unit',
|
||||
'language': 'communication-sound-system',
|
||||
'name': 'identity-labeling-word',
|
||||
'story': 'event-narrative-tale',
|
||||
'joke': 'humor-creating-tale',
|
||||
'song': 'melodic-sound-creation',
|
||||
'music': 'organized-sound-art',
|
||||
'sound': 'air-vibration-sensation',
|
||||
'noise': 'unwanted-sound-disturbance',
|
||||
'silence': 'absence-of-sound',
|
||||
'color': 'light-wavelength-perception',
|
||||
'red': 'blood-fire-color',
|
||||
'blue': 'sky-water-color',
|
||||
'green': 'grass-leaf-color',
|
||||
'yellow': 'sun-gold-color',
|
||||
'purple': 'royal-flower-color',
|
||||
'pink': 'blossom-blush-color',
|
||||
'brown': 'earth-wood-color',
|
||||
'black': 'night-shadow-color',
|
||||
'white': 'snow-cloud-color',
|
||||
'gray': 'storm-stone-color',
|
||||
'silver': 'moon-metal-color',
|
||||
'gold': 'treasure-sun-color',
|
||||
};
|
||||
|
||||
// Gnome exclamations and interjections
|
||||
const gnomeExclamations = [
|
||||
'By my pointy hat!',
|
||||
'Great toadstools!',
|
||||
'Mushroom caps!',
|
||||
'Sacred acorns!',
|
||||
'Blessed berries!',
|
||||
'Garden spirits preserve us!',
|
||||
'Forest magic!',
|
||||
'Woodland wonders!',
|
||||
'Gnome\'s beard!',
|
||||
'Tiny treasures!',
|
||||
'Earth\'s bounty!',
|
||||
'Root and branch!',
|
||||
'Moss and mushrooms!',
|
||||
'Fern and flower!',
|
||||
'Bark and berry!',
|
||||
'Stone and stream!'
|
||||
];
|
||||
|
||||
// Gnome sentence starters
|
||||
const gnomeSentenceStarters = [
|
||||
'In the sacred grove, ',
|
||||
'By the ancient oak, ',
|
||||
'Beneath the mushroom ring, ',
|
||||
'Among the forest folk, ',
|
||||
'In the woodland realm, ',
|
||||
'By the babbling brook, ',
|
||||
'Under the starlit sky, ',
|
||||
'Within the garden walls, ',
|
||||
'Beside the fairy ring, ',
|
||||
'Through the morning mist, ',
|
||||
'As the seasons turn, ',
|
||||
'When the moon is full, ',
|
||||
'In the depths of the forest, ',
|
||||
'Where the wild things grow, ',
|
||||
'Beyond the garden gate, '
|
||||
];
|
||||
|
||||
// Gnome sentence endings
|
||||
const gnomeSentenceEndings = [
|
||||
', as the ancients foretold.',
|
||||
', blessed by the forest spirits.',
|
||||
', in harmony with nature.',
|
||||
', according to gnome wisdom.',
|
||||
', as written in the bark of the elder tree.',
|
||||
', by the light of the fireflies.',
|
||||
', under the watchful eyes of the owls.',
|
||||
', with the blessing of the earth mother.',
|
||||
', as sure as mushrooms grow.',
|
||||
', in the way of our people.',
|
||||
', following the old traditions.',
|
||||
', as the seasons decree.',
|
||||
', by the sacred laws of the woodland.',
|
||||
', in the manner of the wise ones.',
|
||||
', as nature intended.'
|
||||
];
|
||||
|
||||
/**
|
||||
* Translates regular text into gnome speak
|
||||
*/
|
||||
export function translateToGnomeSpeak(text: string): string {
|
||||
if (!text || typeof text !== 'string') {
|
||||
return text;
|
||||
}
|
||||
|
||||
let gnomeText = text.toLowerCase();
|
||||
|
||||
// Preserve URLs
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
const urlMatches: string[] = [];
|
||||
gnomeText = gnomeText.replace(urlRegex, (match) => {
|
||||
urlMatches.push(match);
|
||||
return `__URL_${urlMatches.length - 1}__`;
|
||||
});
|
||||
|
||||
// Preserve nostr: references
|
||||
const nostrRegex = /(nostr:(npub1|note1|nprofile1|nevent1|naddr1)([023456789acdefghjklmnpqrstuvwxyz]+))/g;
|
||||
const nostrMatches: string[] = [];
|
||||
gnomeText = gnomeText.replace(nostrRegex, (match) => {
|
||||
nostrMatches.push(match);
|
||||
return `__NOSTR_${nostrMatches.length - 1}__`;
|
||||
});
|
||||
|
||||
// Preserve hashtags
|
||||
const hashtagRegex = /(#\w+)/g;
|
||||
const hashtagMatches: string[] = [];
|
||||
gnomeText = gnomeText.replace(hashtagRegex, (match) => {
|
||||
hashtagMatches.push(match);
|
||||
return `__HASHTAG_${hashtagMatches.length - 1}__`;
|
||||
});
|
||||
|
||||
// Replace words with gnome equivalents
|
||||
Object.entries(gnomeVocabulary).forEach(([english, gnome]) => {
|
||||
const escapedEnglish = english.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`\\b${escapedEnglish}\\b`, 'gi');
|
||||
gnomeText = gnomeText.replace(regex, gnome);
|
||||
});
|
||||
|
||||
// Add gnome exclamations for emphasis
|
||||
gnomeText = gnomeText.replace(/!/g, () => {
|
||||
const exclamation = gnomeExclamations[Math.floor(Math.random() * gnomeExclamations.length)];
|
||||
return `! ${exclamation}`;
|
||||
});
|
||||
|
||||
// Add gnome sentence starters occasionally
|
||||
const sentences = gnomeText.split(/[.!?]+/).filter(s => s.trim());
|
||||
const gnomeSentences = sentences.map((sentence, index) => {
|
||||
const trimmed = sentence.trim();
|
||||
if (!trimmed) return sentence;
|
||||
|
||||
// Add starter to some sentences (not all, to avoid repetition)
|
||||
if (Math.random() < 0.3 && index > 0) {
|
||||
const starter = gnomeSentenceStarters[Math.floor(Math.random() * gnomeSentenceStarters.length)];
|
||||
return starter + trimmed;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
});
|
||||
|
||||
// Rejoin sentences
|
||||
gnomeText = gnomeSentences.join('. ');
|
||||
|
||||
// Add gnome endings occasionally
|
||||
if (Math.random() < 0.4) {
|
||||
const ending = gnomeSentenceEndings[Math.floor(Math.random() * gnomeSentenceEndings.length)];
|
||||
gnomeText += ending;
|
||||
}
|
||||
|
||||
// Capitalize first letter of sentences
|
||||
gnomeText = gnomeText.replace(/(^|[.!?]\s+)([a-z])/g, (match, prefix, letter) => {
|
||||
return prefix + letter.toUpperCase();
|
||||
});
|
||||
|
||||
// Add some gnome-specific formatting
|
||||
gnomeText = gnomeText.replace(/\b(bitcoin|btc)\b/gi, '✨golden acorns of the digital realm✨');
|
||||
gnomeText = gnomeText.replace(/\b(gm|good morning)\b/gi, '🌅when dew kisses the earth🌅');
|
||||
gnomeText = gnomeText.replace(/\b(gn|good night)\b/gi, '🌙when the moon watches over🌙');
|
||||
|
||||
// Restore preserved patterns
|
||||
gnomeText = gnomeText.replace(/__URL_(\d+)__/g, (_, index) => urlMatches[parseInt(index)]);
|
||||
gnomeText = gnomeText.replace(/__NOSTR_(\d+)__/g, (_, index) => nostrMatches[parseInt(index)]);
|
||||
gnomeText = gnomeText.replace(/__HASHTAG_(\d+)__/g, (_, index) => hashtagMatches[parseInt(index)]);
|
||||
|
||||
return gnomeText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if text should be translated (only kind 1 events)
|
||||
*/
|
||||
export function shouldTranslateToGnomeSpeak(eventKind: number): boolean {
|
||||
return eventKind === 1;
|
||||
}
|
@ -6,7 +6,7 @@ import './lib/polyfills.ts';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
|
||||
// FIXME: a custom font should be used. Eg:
|
||||
// import '@fontsource-variable/<font-name>';
|
||||
// Import gnome-themed font
|
||||
import '@fontsource-variable/comfortaa';
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
@ -1,22 +1,36 @@
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
|
||||
// FIXME: Update this page (the content is just a fallback if you fail to update the page)
|
||||
import { GnomeHeader } from '@/components/GnomeHeader';
|
||||
import { GnomeComposer } from '@/components/GnomeComposer';
|
||||
import { GnomeFeed } from '@/components/GnomeFeed';
|
||||
|
||||
const Index = () => {
|
||||
useSeoMeta({
|
||||
title: 'Welcome to Your Blank App',
|
||||
description: 'A modern Nostr client application built with React, TailwindCSS, and Nostrify.',
|
||||
title: 'Gnome Nostr - The Sacred Network of Woodland Folk',
|
||||
description: 'A magical Nostr client where all messages are translated into the ancient gnome tongue. Connect with fellow earth-dwellers across the message-mushroom network!',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-gray-100">
|
||||
Welcome to Your Blank App
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-400">
|
||||
Start building your amazing project here!
|
||||
</p>
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="container max-w-2xl mx-auto px-4 py-6">
|
||||
<GnomeHeader />
|
||||
<GnomeComposer />
|
||||
<GnomeFeed />
|
||||
|
||||
{/* Footer with MKStack attribution */}
|
||||
<div className="mt-12 pt-6 border-t border-border/30 text-center">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Vibed with{' '}
|
||||
<a
|
||||
href="https://soapbox.pub/mkstack"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:text-accent transition-colors underline"
|
||||
>
|
||||
MKStack
|
||||
</a>
|
||||
{' '}• Blessed by the forest spirits 🍄✨
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -19,6 +19,9 @@ export default {
|
||||
}
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Comfortaa Variable', 'Comfortaa', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
|
Loading…
x
Reference in New Issue
Block a user