diff --git a/public/.well-known/nostr.json b/public/.well-known/nostr.json new file mode 100644 index 0000000..9067601 --- /dev/null +++ b/public/.well-known/nostr.json @@ -0,0 +1,6 @@ +{ + "names": { + "_": "c8fb0d3aa788b9ace4f6cb92dd97d3f292db25b5c9f92462ef6c64926129fbaf", + "verbiricha": "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194" + } +} diff --git a/src/components/nostr/UserName.tsx b/src/components/nostr/UserName.tsx index 551e86c..270055a 100644 --- a/src/components/nostr/UserName.tsx +++ b/src/components/nostr/UserName.tsx @@ -2,6 +2,7 @@ import { useProfile } from "@/hooks/useProfile"; import { getDisplayName } from "@/lib/nostr-utils"; import { cn } from "@/lib/utils"; import { useGrimoire } from "@/core/state"; +import { isGrimoireMember } from "@/lib/grimoire-members"; interface UserNameProps { pubkey: string; @@ -14,10 +15,12 @@ interface UserNameProps { * Shows placeholder derived from pubkey while loading or if no profile exists * Clicking opens the user's profile * Uses highlight color for the logged-in user (themeable amber) + * Shows Grimoire members with yellow-orange gradient styling */ export function UserName({ pubkey, isMention, className }: UserNameProps) { const { addWindow, state } = useGrimoire(); const profile = useProfile(pubkey); + const isGrimoire = isGrimoireMember(pubkey); const displayName = getDisplayName(pubkey, profile); // Check if this is the logged-in user @@ -33,7 +36,11 @@ export function UserName({ pubkey, isMention, className }: UserNameProps) { dir="auto" className={cn( "font-semibold cursor-crosshair hover:underline hover:decoration-dotted", - isActiveAccount ? "text-highlight" : "text-accent", + isGrimoire + ? "bg-gradient-to-br from-yellow-500 via-orange-500 to-orange-600 bg-clip-text text-transparent" + : isActiveAccount + ? "text-highlight" + : "text-accent", className, )} onClick={handleClick} diff --git a/src/lib/grimoire-members.ts b/src/lib/grimoire-members.ts new file mode 100644 index 0000000..41e7c34 --- /dev/null +++ b/src/lib/grimoire-members.ts @@ -0,0 +1,96 @@ +/** + * Grimoire Member System + * + * Defines special usernames for Grimoire project members with custom NIP-05 style identifiers. + * Members get special gradient styling to distinguish them visually. + */ + +/** + * Grimoire member definition + */ +export interface GrimoireMember { + /** Username for display (e.g., "_", "verbiricha") */ + username: string; + /** Hex pubkey */ + pubkey: string; + /** NIP-05 style identifier (e.g., "_@grimoire.rocks") */ + nip05: string; +} + +/** + * Official Grimoire project members + * These users get special gradient styling applied to their usernames + */ +export const GRIMOIRE_MEMBERS: readonly GrimoireMember[] = [ + { + username: "_", + pubkey: "c8fb0d3aa788b9ace4f6cb92dd97d3f292db25b5c9f92462ef6c64926129fbaf", + nip05: "_@grimoire.rocks", + }, + { + username: "verbiricha", + pubkey: "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", + nip05: "verbiricha@grimoire.rocks", + }, +] as const; + +/** + * Map of pubkey -> member for O(1) lookups + */ +const membersByPubkey = new Map( + GRIMOIRE_MEMBERS.map((member) => [member.pubkey, member]), +); + +/** + * Map of NIP-05 identifier -> member for O(1) lookups + */ +const membersByNip05 = new Map( + GRIMOIRE_MEMBERS.map((member) => [member.nip05, member]), +); + +/** + * Check if a pubkey belongs to a Grimoire member + * @param pubkey - Hex public key + * @returns true if user is a Grimoire member + */ +export function isGrimoireMember(pubkey: string): boolean { + return membersByPubkey.has(pubkey.toLowerCase()); +} + +/** + * Get Grimoire member info by pubkey + * @param pubkey - Hex public key + * @returns Member info or undefined + */ +export function getGrimoireMember(pubkey: string): GrimoireMember | undefined { + return membersByPubkey.get(pubkey.toLowerCase()); +} + +/** + * Get Grimoire member info by NIP-05 identifier + * @param nip05 - NIP-05 identifier (e.g., "_@grimoire.rocks") + * @returns Member info or undefined + */ +export function getGrimoireMemberByNip05( + nip05: string, +): GrimoireMember | undefined { + return membersByNip05.get(nip05.toLowerCase()); +} + +/** + * Get Grimoire username for a pubkey + * @param pubkey - Hex public key + * @returns Username or undefined if not a member + */ +export function getGrimoireUsername(pubkey: string): string | undefined { + return membersByPubkey.get(pubkey.toLowerCase())?.username; +} + +/** + * Get Grimoire NIP-05 identifier for a pubkey + * @param pubkey - Hex public key + * @returns NIP-05 identifier or undefined if not a member + */ +export function getGrimoireNip05(pubkey: string): string | undefined { + return membersByPubkey.get(pubkey.toLowerCase())?.nip05; +} diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo index 75ea001..5e39d3d 100644 --- a/tsconfig.node.tsbuildinfo +++ b/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"root":["./vite.config.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./vite.config.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file diff --git a/vercel.json b/vercel.json index 3a48e56..843a1da 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,31 @@ { - "rewrites": [{ "source": "/(.*)", "destination": "/" }] + "headers": [ + { + "source": "/.well-known/nostr.json", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "GET, OPTIONS" + }, + { + "key": "Cache-Control", + "value": "public, max-age=3600" + } + ] + } + ], + "rewrites": [ + { + "source": "/((?!.well-known).*)", + "destination": "/" + } + ] }