From bf1d29e70481498853b8ff6a994a4f4beb3901a8 Mon Sep 17 00:00:00 2001 From: Paul Butler Date: Sun, 26 Jan 2025 08:16:00 -0500 Subject: [PATCH] initial commit --- .gitignore | 27 + app/emoji.ts | 24 + app/encoding.test.ts | 29 + app/encoding.ts | 31 + app/globals.css | 94 + app/layout.tsx | 18 + app/page.tsx | 88 + components.json | 21 + components/emoji-selector.tsx | 29 + components/theme-provider.tsx | 11 + components/ui/accordion.tsx | 58 + components/ui/alert-dialog.tsx | 141 + components/ui/alert.tsx | 59 + components/ui/aspect-ratio.tsx | 7 + components/ui/avatar.tsx | 50 + components/ui/badge.tsx | 36 + components/ui/breadcrumb.tsx | 115 + components/ui/button.tsx | 56 + components/ui/calendar.tsx | 66 + components/ui/card.tsx | 79 + components/ui/carousel.tsx | 262 ++ components/ui/chart.tsx | 365 ++ components/ui/checkbox.tsx | 30 + components/ui/collapsible.tsx | 11 + components/ui/command.tsx | 153 + components/ui/context-menu.tsx | 200 ++ components/ui/dialog.tsx | 122 + components/ui/drawer.tsx | 118 + components/ui/dropdown-menu.tsx | 200 ++ components/ui/form.tsx | 178 + components/ui/hover-card.tsx | 29 + components/ui/input-otp.tsx | 71 + components/ui/input.tsx | 22 + components/ui/label.tsx | 26 + components/ui/menubar.tsx | 236 ++ components/ui/navigation-menu.tsx | 128 + components/ui/pagination.tsx | 117 + components/ui/popover.tsx | 31 + components/ui/progress.tsx | 28 + components/ui/radio-group.tsx | 44 + components/ui/resizable.tsx | 45 + components/ui/scroll-area.tsx | 48 + components/ui/select.tsx | 160 + components/ui/separator.tsx | 31 + components/ui/sheet.tsx | 140 + components/ui/sidebar.tsx | 763 ++++ components/ui/skeleton.tsx | 15 + components/ui/slider.tsx | 28 + components/ui/sonner.tsx | 31 + components/ui/switch.tsx | 29 + components/ui/table.tsx | 117 + components/ui/tabs.tsx | 55 + components/ui/textarea.tsx | 22 + components/ui/toast.tsx | 129 + components/ui/toaster.tsx | 35 + components/ui/toggle-group.tsx | 61 + components/ui/toggle.tsx | 45 + components/ui/tooltip.tsx | 30 + components/ui/use-mobile.tsx | 19 + components/ui/use-toast.ts | 194 ++ hooks/use-mobile.tsx | 19 + hooks/use-toast.ts | 194 ++ lib/utils.ts | 6 + next.config.mjs | 48 + package-lock.json | 5372 +++++++++++++++++++++++++++++ package.json | 71 + postcss.config.mjs | 8 + public/placeholder-logo.png | Bin 0 -> 958 bytes public/placeholder-logo.svg | 1 + public/placeholder-user.jpg | Bin 0 -> 2615 bytes public/placeholder.jpg | Bin 0 -> 1596 bytes public/placeholder.svg | 1 + styles/globals.css | 94 + tailwind.config.ts | 96 + tsconfig.json | 27 + 75 files changed, 11344 insertions(+) create mode 100644 .gitignore create mode 100644 app/emoji.ts create mode 100644 app/encoding.test.ts create mode 100644 app/encoding.ts create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 components.json create mode 100644 components/emoji-selector.tsx create mode 100644 components/theme-provider.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/aspect-ratio.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/chart.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/context-menu.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/hover-card.tsx create mode 100644 components/ui/input-otp.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/menubar.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/resizable.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/toggle-group.tsx create mode 100644 components/ui/toggle.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/ui/use-mobile.tsx create mode 100644 components/ui/use-toast.ts create mode 100644 hooks/use-mobile.tsx create mode 100644 hooks/use-toast.ts create mode 100644 lib/utils.ts create mode 100644 next.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 public/placeholder-logo.png create mode 100644 public/placeholder-logo.svg create mode 100644 public/placeholder-user.jpg create mode 100644 public/placeholder.jpg create mode 100644 public/placeholder.svg create mode 100644 styles/globals.css create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f650315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/app/emoji.ts b/app/emoji.ts new file mode 100644 index 0000000..7be6ee8 --- /dev/null +++ b/app/emoji.ts @@ -0,0 +1,24 @@ +export const EMOJI_LIST = [ + "πŸ˜€", + "πŸ˜‚", + "πŸ₯°", + "😎", + "πŸ€”", + "πŸ‘", + "πŸ‘Ž", + "πŸ‘", + "πŸ™Œ", + "🀝", + "πŸŽ‰", + "πŸŽ‚", + "πŸ•", + "🌈", + "🌞", + "πŸŒ™", + "πŸ”₯", + "πŸ’―", + "πŸš€", + "πŸ‘€", + "πŸ’€", + "πŸ₯Ή", +] diff --git a/app/encoding.test.ts b/app/encoding.test.ts new file mode 100644 index 0000000..66ac0fa --- /dev/null +++ b/app/encoding.test.ts @@ -0,0 +1,29 @@ +import { expect, test, describe } from 'vitest' +import { encode, decode } from './encoding' +import { EMOJI_LIST } from './emoji' + +describe('emoji encoder/decoder', () => { + test('should correctly encode and decode strings', () => { + const testStrings = [ + 'Hello, World!', + 'Testing 123', + 'Special chars: !@#$%^&*()', + 'Unicode: δ½ ε₯½οΌŒδΈ–η•Œ', + '', // empty string + ' ' // space only + ] + + for (const emoji of EMOJI_LIST) { + for (const str of testStrings) { + const encoded = encode(emoji, str) + const decoded = decode(encoded) + + // Ensure decoding returns the original string + expect(decoded).toBe(str) + + // Ensure encoded string only contains emojis (optional test) + // expect(encoded).toMatch(/^[\p{Emoji}]+$/u) + } + } + }) +}) diff --git a/app/encoding.ts b/app/encoding.ts new file mode 100644 index 0000000..23f9792 --- /dev/null +++ b/app/encoding.ts @@ -0,0 +1,31 @@ +export function encode(emoji: string, text: string): string { + // convert the string to utf-8 bytes + const bytes = new TextEncoder().encode(text) + let encoded = emoji + + for (const char of bytes) { + const upper = char >> 4 + const lower = char & 0b1111 + encoded += String.fromCodePoint(upper + 0xfe00, lower + 0xfe00) + } + + return encoded +} + +export function decode(text: string): string { + let decoded = [] + + for (let i = 2; i < text.length; i += 2) { + const upper = text.charCodeAt(i) - 0xfe00 + const lower = text.charCodeAt(i + 1) - 0xfe00 + + if (upper < 0 || upper > 15 || lower < 0 || lower > 15) { + break; + } + + decoded.push((upper << 4) | lower) + } + + let decodedArray = new Uint8Array(decoded) + return new TextDecoder().decode(decodedArray) +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..ac68442 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --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%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --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%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..2489707 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'Encode anything as an emoji', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..470c6da --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,88 @@ +"use client" + +import { useState } from "react" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Switch } from "@/components/ui/switch" +import { Label } from "@/components/ui/label" +import { decode, encode } from "./encoding" +import { EmojiSelector } from "@/components/emoji-selector" +import { EMOJI_LIST } from "./emoji" + +export default function Base64EncoderDecoder() { + const [input, setInput] = useState("") + const [emoji, setEmoji] = useState("πŸ˜€") + const [output, setOutput] = useState("") + const [isEncoding, setIsEncoding] = useState(true) + const [error, setError] = useState("") + + const handleConvert = () => { + try { + if (isEncoding) { + setOutput(encode(emoji, input)) + } else { + setOutput(decode(input)) + } + setError("") + } catch (e) { + setError(`Error ${isEncoding ? "encoding" : "decoding"}: Invalid input`) + } + } + + const handleModeToggle = (checked: boolean) => { + setIsEncoding(checked) + setInput("") + setOutput("") + setError("") + } + + const handleEmojiSelect = (emoji: string) => { + setEmoji(emoji) + } + + return ( +
+ + + Encode Anything as an Emoji + + +

This tool allows you to encode a hidden message into an emoji. You can copy and paste an emoji with a hidden message in it to decode the message.

+ +
+ + + +
+ +