├── .cross_compile.sh ├── .github ├── FUNDING.yml └── workflows │ ├── docker.yaml │ └── release.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bot └── bot.go ├── compose.yaml ├── config.example.yaml ├── frontend ├── .gitignore ├── README.md ├── components.json ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── components │ │ ├── ui │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── input.tsx │ │ │ ├── toast.tsx │ │ │ └── toaster.tsx │ │ └── upload-zone.tsx │ ├── hooks │ │ └── use-toast.ts │ └── lib │ │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json ├── go.mod ├── go.sum ├── main.go ├── public ├── 404 │ └── index.html ├── 404.html ├── _next │ └── static │ │ ├── chunks │ │ ├── 455-a269af0bea64e1d5.js │ │ ├── 4bd1b696-8a3c458bdab8bf04.js │ │ ├── 684-9ff42712bb05fa22.js │ │ ├── 869-9c07f9cccedfb126.js │ │ ├── app │ │ │ ├── _not-found │ │ │ │ └── page-c4ccdce3b6a32a2a.js │ │ │ ├── layout-f4f75e10eba9ecd4.js │ │ │ └── page-ef23a9b1d76fb793.js │ │ ├── framework-f593a28cde54158e.js │ │ ├── main-6a454ceb54d37826.js │ │ ├── main-app-3701f8484f32bf65.js │ │ ├── pages │ │ │ ├── _app-da15c11dea942c36.js │ │ │ └── _error-cc3f077a18ea1793.js │ │ ├── polyfills-42372ed130431b0a.js │ │ └── webpack-f029a09104d09cbc.js │ │ ├── css │ │ └── afe69862c9b626f3.css │ │ ├── j6hdw6hDLpPcTkUaTeY7T │ │ ├── _buildManifest.js │ │ └── _ssgManifest.js │ │ └── media │ │ ├── 569ce4b8f30dc480-s.p.woff2 │ │ ├── 747892c23ea88013-s.woff2 │ │ ├── 8d697b304b401681-s.woff2 │ │ ├── 93f479601ee12b01-s.p.woff2 │ │ ├── 9610d9e46709d722-s.woff2 │ │ └── ba015fad6dcf6784-s.woff2 ├── favicon.ico ├── file.svg ├── globe.svg ├── index.html ├── index.txt ├── next.svg ├── vercel.svg └── window.svg ├── screenshot └── image.png └── static └── index.html /.cross_compile.sh: -------------------------------------------------------------------------------- 1 | ### 2 | # @Author: Vincent Yang 3 | # @Date: 2024-04-09 19:54:55 4 | # @LastEditors: Vincent Yang 5 | # @LastEditTime: 2024-04-09 19:55:09 6 | # @FilePath: /discord-image/.cross_compile.sh 7 | # @Telegram: https://t.me/missuo 8 | # @GitHub: https://github.com/missuo 9 | # 10 | # Copyright © 2024 by Vincent, All Rights Reserved. 11 | ### 12 | set -e 13 | 14 | DIST_PREFIX="discord-image" 15 | DEBUG_MODE=${2} 16 | TARGET_DIR="dist" 17 | PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64 windows/386 windows/amd64" 18 | 19 | rm -rf ${TARGET_DIR} 20 | mkdir ${TARGET_DIR} 21 | 22 | for pl in ${PLATFORMS}; do 23 | export GOOS=$(echo ${pl} | cut -d'/' -f1) 24 | export GOARCH=$(echo ${pl} | cut -d'/' -f2) 25 | export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH} 26 | if [ "${GOOS}" == "windows" ]; then 27 | export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH}.exe 28 | fi 29 | 30 | echo "build => ${TARGET}" 31 | if [ "${DEBUG_MODE}" == "debug" ]; then 32 | CGO_ENABLED=0 go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \ 33 | -ldflags "-w -s" . 34 | else 35 | CGO_ENABLED=0 go build -trimpath -o ${TARGET} \ 36 | -ldflags "-w -s" . 37 | fi 38 | done -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [missuo] 4 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | env: 9 | DOCKER_IMAGE_NAME: missuo/discord-image 10 | GHCR_IMAGE_NAME: ${{ github.repository }} 11 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 12 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 13 | GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | GHCR_USERNAME: ${{ github.repository_owner }} 15 | 16 | jobs: 17 | docker_build: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v2 28 | with: 29 | platforms: all 30 | 31 | - name: Set up docker buildx 32 | id: buildx 33 | uses: docker/setup-buildx-action@v2 34 | with: 35 | version: latest 36 | 37 | - name: Login to DockerHub 38 | uses: docker/login-action@v2 39 | with: 40 | registry: docker.io 41 | username: ${{ env.DOCKER_USERNAME }} 42 | password: ${{ env.DOCKER_PASSWORD }} 43 | 44 | - name: Login to GHCR 45 | uses: docker/login-action@v2 46 | with: 47 | registry: ghcr.io 48 | username: ${{ env.GHCR_USERNAME }} 49 | password: ${{ env.GHCR_TOKEN }} 50 | 51 | - name: Docker meta 52 | id: meta 53 | uses: docker/metadata-action@v4 54 | with: 55 | # list of Docker images to use as base name for tags 56 | images: | 57 | docker.io/${{ env.DOCKER_IMAGE_NAME }} 58 | ghcr.io/${{ env.GHCR_IMAGE_NAME }} 59 | # generate Docker tags based on the following events/attributes 60 | tags: | 61 | type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }} 62 | type=pep440,pattern={{raw}},enable=${{ startsWith(github.ref, 'refs/tags/') }} 63 | type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }} 64 | 65 | - name: Build and push 66 | uses: docker/build-push-action@v3 67 | with: 68 | context: . 69 | platforms: linux/amd64,linux/arm64 70 | push: true 71 | tags: ${{ steps.meta.outputs.tags }} 72 | labels: ${{ steps.meta.outputs.labels }} 73 | cache-from: type=gha 74 | cache-to: type=gha,mode=max -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | pull_request: 6 | 7 | name: Release 8 | jobs: 9 | 10 | Build: 11 | if: startsWith(github.ref, 'refs/tags/v') 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.24.0" 19 | 20 | - run: bash .cross_compile.sh 21 | 22 | - name: Release 23 | uses: softprops/action-gh-release@v1 24 | with: 25 | draft: false 26 | generate_release_notes: true 27 | files: | 28 | dist/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | uploads 23 | config.yaml 24 | 25 | # macOS files 26 | .DS_Store -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.0 AS builder 2 | WORKDIR /go/src/github.com/missuo/discord-image 3 | COPY main.go ./ 4 | COPY bot ./bot 5 | COPY public ./public 6 | COPY go.mod ./ 7 | COPY go.sum ./ 8 | RUN go get -d -v ./ 9 | RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o discord-image . 10 | 11 | FROM alpine:latest 12 | WORKDIR /app 13 | COPY --from=builder /go/src/github.com/missuo/discord-image/discord-image /app/ 14 | COPY --from=builder /go/src/github.com/missuo/discord-image/public /app/public 15 | CMD ["/app/discord-image"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub Workflow][1]](https://github.com/missuo/discord-image/actions) 2 | [![Go Version][2]](https://github.com/missuo/discord-image/blob/main/go.mod) 3 | [![Docker Pulls][3]](https://hub.docker.com/r/missuo/discord-image) 4 | 5 | [1]: https://img.shields.io/github/actions/workflow/status/missuo/discord-image/release.yaml?logo=github 6 | [2]: https://img.shields.io/github/go-mod/go-version/missuo/discord-image?logo=go 7 | [3]: https://img.shields.io/docker/pulls/missuo/discord-image?logo=docker 8 | 9 | # Discord Image 10 | 11 | A powerful image hosting and file sharing service built with Discord Bot technology. 12 | 13 | **Note: Deployment requires a Discord Bot Token. You'll need to create a Discord application and bot to obtain this token. Please refer to Discord's official documentation for setup instructions.** 14 | 15 | ## Features 16 | 17 | - **File size limits based on Discord tier:** 18 | - Free users: 10MB 19 | - Discord Nitro Basic: 50MB 20 | - Discord Nitro: 500MB 21 | - **Permanent storage** - Files never expire 22 | - **Upload management** - View upload history and delete files 23 | - **Multi-format support** - Images, videos, and other file types 24 | - **Custom proxy support** - Configure custom proxy URLs for better accessibility 25 | - **Server-friendly** - Automatic file cleanup option to save server disk space 26 | - **Self-hosted** - Private deployment for enhanced security and control 27 | 28 | ## Live Demo 29 | 30 | Try the live demo at these endpoints: 31 | - Cloudflare CDN: [https://dc.missuo.ru](https://dc.missuo.ru) 32 | - EdgeOne CDN: [https://dc.deeeee.de](https://dc.deeeee.de) 33 | 34 | *Note: The demo configuration includes `proxy_url` and `auto_delete` settings for optimal performance in mainland China.* 35 | 36 | ![Demo Screenshot](./screenshot/image.png) 37 | 38 | For technical details about the implementation, read the blog post: [https://missuo.me/posts/discord-file-sharing/](https://missuo.me/posts/discord-file-sharing/) 39 | 40 | ## Quick Start with Docker 41 | 42 | ```bash 43 | mkdir discord-image && cd discord-image 44 | wget -O compose.yaml https://raw.githubusercontent.com/missuo/discord-image/main/compose.yaml 45 | nano compose.yaml # Edit configuration 46 | docker compose up -d 47 | ``` 48 | 49 | ### Nginx Reverse Proxy Configuration 50 | 51 | ```nginx 52 | location / { 53 | proxy_pass http://localhost:8080; 54 | proxy_set_header Host $host; 55 | proxy_set_header X-Real-IP $remote_addr; 56 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 57 | proxy_set_header REMOTE-HOST $remote_addr; 58 | proxy_set_header Upgrade $http_upgrade; 59 | proxy_set_header Connection $connection_upgrade; 60 | proxy_http_version 1.1; 61 | } 62 | ``` 63 | 64 | ## Configuration 65 | 66 | ### Configuration File 67 | 68 | **Important:** Do not change the `bot_token` after initial setup, as this may invalidate existing file links. 69 | 70 | ```yaml 71 | bot: 72 | token: "" # Discord bot token 73 | channel_id: "" # Target channel ID 74 | 75 | upload: 76 | temp_dir: "uploads" # Temporary directory for file storage 77 | 78 | proxy_url: example.com # Custom proxy URL for cdn.discordapp.com (optional) 79 | auto_delete: true # Auto-delete files from server after upload 80 | ``` 81 | 82 | ### Docker Environment Variables 83 | 84 | For Docker deployments, use these environment variables instead of the configuration file: 85 | 86 | ```yaml 87 | services: 88 | discord-image: 89 | image: ghcr.io/missuo/discord-image 90 | ports: 91 | - "8080:8080" 92 | environment: 93 | - BOT_TOKEN=your_bot_token 94 | - CHANNEL_ID=your_channel_id 95 | - UPLOAD_DIR=/app/uploads 96 | - PROXY_URL=your_proxy_url # Optional 97 | - AUTO_DELETE=true 98 | volumes: 99 | - ./uploads:/app/uploads 100 | ``` 101 | 102 | ### Configuration Notes 103 | 104 | - **proxy_url**: Optional setting for accessing Discord CDN from mainland China. If not needed, leave unset. 105 | - **auto_delete**: Saves server disk space by removing files after successful upload to Discord. 106 | 107 | ## Setting Up Proxy URL (Optional) 108 | 109 | **Only required for mainland China access. Skip this section if not applicable.** 110 | 111 | ### Nginx Proxy Configuration 112 | 113 | ```nginx 114 | location / { 115 | proxy_pass https://cdn.discordapp.com; 116 | proxy_set_header Host cdn.discordapp.com; 117 | proxy_set_header X-Real-IP $remote_addr; 118 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 119 | proxy_set_header REMOTE-HOST $remote_addr; 120 | proxy_set_header Upgrade $http_upgrade; 121 | proxy_set_header Connection $connection_upgrade; 122 | proxy_http_version 1.1; 123 | } 124 | ``` 125 | 126 | ### Alternative: Cloudflare Workers 127 | 128 | You can also deploy the proxy using serverless solutions like Cloudflare Workers. Community contributions for Workers configurations are welcome via PR. 129 | 130 | ## Related Projects 131 | 132 | - [missuo/Telegraph-Image-Hosting](https://github.com/missuo/Telegraph-Image-Hosting) 133 | 134 | ## License 135 | 136 | AGPL-3.0 -------------------------------------------------------------------------------- /bot/bot.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Vincent Yang 3 | * @Date: 2024-04-09 03:36:13 4 | * @LastEditors: Vincent Yang 5 | * @LastEditTime: 2024-04-10 18:08:24 6 | * @FilePath: /discord-image/bot/bot.go 7 | * @Telegram: https://t.me/missuo 8 | * @GitHub: https://github.com/missuo 9 | * 10 | * Copyright © 2024 by Vincent, All Rights Reserved. 11 | */ 12 | package bot 13 | 14 | import ( 15 | "fmt" 16 | "log" 17 | "os" 18 | "os/signal" 19 | "syscall" 20 | 21 | "github.com/bwmarrin/discordgo" 22 | ) 23 | 24 | var ( 25 | BotToken string 26 | Discord *discordgo.Session 27 | ) 28 | 29 | func Run() { 30 | discord, err := discordgo.New("Bot " + BotToken) 31 | if err != nil { 32 | log.Fatalf("Failed to create Discord session: %v", err) 33 | } 34 | Discord = discord 35 | 36 | err = discord.Open() 37 | if err != nil { 38 | log.Fatalf("Failed to open the Discord session: %v", err) 39 | } 40 | defer discord.Close() 41 | 42 | fmt.Println("Bot running...") 43 | 44 | c := make(chan os.Signal, 1) 45 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 46 | <-c 47 | 48 | fmt.Println("Bot is shutting down...") 49 | os.Exit(0) 50 | } 51 | 52 | // SendImage sends an file to a Discord channel 53 | func SendImage(channelID, filename string) (*discordgo.Message, error) { 54 | file, err := os.Open(filename) 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer file.Close() 59 | 60 | message, err := Discord.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{ 61 | Files: []*discordgo.File{ 62 | { 63 | Name: filename, 64 | Reader: file, 65 | }, 66 | }, 67 | }) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return message, nil 73 | } 74 | 75 | // GetImageURL returns the latest URL of an file in a Discord message 76 | func GetImageURL(channelID, messageID string) (string, error) { 77 | message, err := Discord.ChannelMessage(channelID, messageID) 78 | if err != nil { 79 | return "", err 80 | } 81 | 82 | if len(message.Attachments) > 0 { 83 | return message.Attachments[0].URL, nil 84 | } 85 | 86 | return "", fmt.Errorf("image not found") 87 | } 88 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | discord-image: 3 | image: ghcr.io/missuo/discord-image 4 | ports: 5 | - "8080:8080" 6 | environment: 7 | - BOT_TOKEN=your_bot_token 8 | - CHANNEL_ID=your_channel_id 9 | - UPLOAD_DIR=/app/uploads 10 | #- PROXY_URL=your_proxy_url # If you want to access in mainland China, you must set this item. 11 | - AUTO_DELETE=true 12 | volumes: 13 | - ./uploads:/app/uploads 14 | -------------------------------------------------------------------------------- /config.example.yaml: -------------------------------------------------------------------------------- 1 | bot: 2 | token: "" 3 | channel_id: "" 4 | 5 | upload: 6 | temp_dir: "uploads" 7 | 8 | proxy_url: example.com 9 | auto_delete: true -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /frontend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | output: "export", 5 | trailingSlash: true, 6 | distDir: "../public", 7 | images: { 8 | unoptimized: true, 9 | }, 10 | }; 11 | 12 | export default nextConfig; 13 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "export": "next build" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-slot": "^1.2.3", 14 | "@radix-ui/react-toast": "^1.2.14", 15 | "autoprefixer": "^10.4.21", 16 | "class-variance-authority": "^0.7.1", 17 | "clsx": "^2.1.1", 18 | "lucide-react": "^0.525.0", 19 | "next": "15.3.5", 20 | "react": "^19.0.0", 21 | "react-dom": "^19.0.0", 22 | "tailwind-merge": "^3.3.1", 23 | "tailwindcss": "^3.4.17", 24 | "tailwindcss-animate": "^1.0.7" 25 | }, 26 | "devDependencies": { 27 | "@eslint/eslintrc": "^3", 28 | "@types/node": "^20", 29 | "@types/react": "^19", 30 | "@types/react-dom": "^19", 31 | "eslint": "^9", 32 | "eslint-config-next": "15.3.5", 33 | "typescript": "^5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /frontend/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 9% 9%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 10% 3.9%; 26 | --radius: 0.5rem; 27 | } 28 | 29 | .dark { 30 | --background: 240 10% 3.9%; 31 | --foreground: 0 0% 98%; 32 | --card: 240 10% 3.9%; 33 | --card-foreground: 0 0% 98%; 34 | --popover: 240 10% 3.9%; 35 | --popover-foreground: 0 0% 98%; 36 | --primary: 0 0% 98%; 37 | --primary-foreground: 240 5.9% 10%; 38 | --secondary: 240 3.7% 15.9%; 39 | --secondary-foreground: 0 0% 98%; 40 | --muted: 240 3.7% 15.9%; 41 | --muted-foreground: 240 5% 64.9%; 42 | --accent: 240 3.7% 15.9%; 43 | --accent-foreground: 0 0% 98%; 44 | --destructive: 0 62.8% 30.6%; 45 | --destructive-foreground: 0 0% 98%; 46 | --border: 240 3.7% 15.9%; 47 | --input: 240 3.7% 15.9%; 48 | --ring: 240 4.9% 83.9%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | body { 57 | @apply bg-background text-foreground; 58 | } 59 | } 60 | 61 | @layer utilities { 62 | .backdrop-blur-sm { 63 | backdrop-filter: blur(4px); 64 | } 65 | } -------------------------------------------------------------------------------- /frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | import { Toaster } from "@/components/ui/toaster"; 5 | 6 | const geistSans = Geist({ 7 | variable: "--font-geist-sans", 8 | subsets: ["latin"], 9 | }); 10 | 11 | const geistMono = Geist_Mono({ 12 | variable: "--font-geist-mono", 13 | subsets: ["latin"], 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Discord Image Upload", 18 | description: "Upload your images to Discord with drag & drop and clipboard support", 19 | }; 20 | 21 | export default function RootLayout({ 22 | children, 23 | }: Readonly<{ 24 | children: React.ReactNode; 25 | }>) { 26 | return ( 27 | 28 | 31 | {children} 32 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import UploadZone from "@/components/upload-zone"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 |
8 |

9 | Discord Image 10 |

11 |

12 | Fast, secure image hosting for Discord 13 |

14 | 20 | 21 | 22 | 23 | View on GitHub 24 | 25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 | ); 33 | } -------------------------------------------------------------------------------- /frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /frontend/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLDivElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |
64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /frontend/src/components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ToastPrimitives from "@radix-ui/react-toast" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | import { X } from "lucide-react" 7 | 8 | import { cn } from "@/lib/utils" 9 | 10 | const ToastProvider = ToastPrimitives.Provider 11 | 12 | const ToastViewport = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, ...props }, ref) => ( 16 | 24 | )) 25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 26 | 27 | const toastVariants = cva( 28 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 29 | { 30 | variants: { 31 | variant: { 32 | default: "border bg-background text-foreground", 33 | destructive: 34 | "destructive group border-destructive bg-destructive text-destructive-foreground", 35 | }, 36 | }, 37 | defaultVariants: { 38 | variant: "default", 39 | }, 40 | } 41 | ) 42 | 43 | const Toast = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef & 46 | VariantProps 47 | >(({ className, variant, ...props }, ref) => { 48 | return ( 49 | 54 | ) 55 | }) 56 | Toast.displayName = ToastPrimitives.Root.displayName 57 | 58 | const ToastAction = React.forwardRef< 59 | React.ElementRef, 60 | React.ComponentPropsWithoutRef 61 | >(({ className, ...props }, ref) => ( 62 | 70 | )) 71 | ToastAction.displayName = ToastPrimitives.Action.displayName 72 | 73 | const ToastClose = React.forwardRef< 74 | React.ElementRef, 75 | React.ComponentPropsWithoutRef 76 | >(({ className, ...props }, ref) => ( 77 | 86 | 87 | 88 | )) 89 | ToastClose.displayName = ToastPrimitives.Close.displayName 90 | 91 | const ToastTitle = React.forwardRef< 92 | React.ElementRef, 93 | React.ComponentPropsWithoutRef 94 | >(({ className, ...props }, ref) => ( 95 | 100 | )) 101 | ToastTitle.displayName = ToastPrimitives.Title.displayName 102 | 103 | const ToastDescription = React.forwardRef< 104 | React.ElementRef, 105 | React.ComponentPropsWithoutRef 106 | >(({ className, ...props }, ref) => ( 107 | 112 | )) 113 | ToastDescription.displayName = ToastPrimitives.Description.displayName 114 | 115 | type ToastProps = React.ComponentPropsWithoutRef 116 | 117 | type ToastActionElement = React.ReactElement 118 | 119 | export { 120 | type ToastProps, 121 | type ToastActionElement, 122 | ToastProvider, 123 | ToastViewport, 124 | Toast, 125 | ToastTitle, 126 | ToastDescription, 127 | ToastClose, 128 | ToastAction, 129 | } 130 | -------------------------------------------------------------------------------- /frontend/src/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useToast } from "@/hooks/use-toast" 4 | import { 5 | Toast, 6 | ToastClose, 7 | ToastDescription, 8 | ToastProvider, 9 | ToastTitle, 10 | ToastViewport, 11 | } from "@/components/ui/toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/components/upload-zone.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useCallback, useRef, useEffect } from "react"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 6 | import { Input } from "@/components/ui/input"; 7 | import { toast } from "@/hooks/use-toast"; 8 | import { Upload, Copy, Link } from "lucide-react"; 9 | 10 | interface UploadResult { 11 | url: string; 12 | } 13 | 14 | interface LinkFormats { 15 | direct: string; 16 | markdown: string; 17 | html: string; 18 | } 19 | 20 | export default function UploadZone() { 21 | const [isDragging, setIsDragging] = useState(false); 22 | const [uploading, setUploading] = useState(false); 23 | const [uploadResult, setUploadResult] = useState(null); 24 | const [linkFormats, setLinkFormats] = useState(null); 25 | const fileInputRef = useRef(null); 26 | 27 | const generateLinkFormats = useCallback((url: string): LinkFormats => { 28 | return { 29 | direct: url, 30 | markdown: `![Image](${url})`, 31 | html: `Image`, 32 | }; 33 | }, []); 34 | 35 | const handleFileUpload = useCallback(async (file: File) => { 36 | if (!file.type.startsWith("image/")) { 37 | toast({ 38 | title: "Invalid file type", 39 | description: "Please select an image file.", 40 | variant: "destructive", 41 | }); 42 | return; 43 | } 44 | 45 | if (file.size > 10 * 1024 * 1024) { 46 | toast({ 47 | title: "File too large", 48 | description: "Image size must be less than 10MB.", 49 | variant: "destructive", 50 | }); 51 | return; 52 | } 53 | 54 | setUploading(true); 55 | try { 56 | const formData = new FormData(); 57 | formData.append("image", file); 58 | 59 | const response = await fetch("/upload", { 60 | method: "POST", 61 | body: formData, 62 | }); 63 | 64 | if (!response.ok) { 65 | const errorData = await response.json(); 66 | throw new Error(errorData.error || "Upload failed"); 67 | } 68 | 69 | const result: UploadResult = await response.json(); 70 | setUploadResult(result); 71 | setLinkFormats(generateLinkFormats(result.url)); 72 | 73 | toast({ 74 | title: "Upload successful", 75 | description: "Your image has been uploaded successfully.", 76 | }); 77 | } catch (error) { 78 | console.error("Upload error:", error); 79 | toast({ 80 | title: "Upload failed", 81 | description: error instanceof Error ? error.message : "An unknown error occurred", 82 | variant: "destructive", 83 | }); 84 | } finally { 85 | setUploading(false); 86 | } 87 | }, [generateLinkFormats]); 88 | 89 | const handleDragOver = useCallback((e: React.DragEvent) => { 90 | e.preventDefault(); 91 | setIsDragging(true); 92 | }, []); 93 | 94 | const handleDragLeave = useCallback((e: React.DragEvent) => { 95 | e.preventDefault(); 96 | setIsDragging(false); 97 | }, []); 98 | 99 | const handleDrop = useCallback((e: React.DragEvent) => { 100 | e.preventDefault(); 101 | setIsDragging(false); 102 | 103 | const files = Array.from(e.dataTransfer.files); 104 | if (files.length > 0) { 105 | handleFileUpload(files[0]); 106 | } 107 | }, [handleFileUpload]); 108 | 109 | const handleFileSelect = useCallback((e: React.ChangeEvent) => { 110 | const files = e.target.files; 111 | if (files && files.length > 0) { 112 | handleFileUpload(files[0]); 113 | } 114 | }, [handleFileUpload]); 115 | 116 | const handlePaste = useCallback((e: ClipboardEvent) => { 117 | const items = e.clipboardData?.items; 118 | if (items) { 119 | for (let i = 0; i < items.length; i++) { 120 | const item = items[i]; 121 | if (item.type.indexOf("image") !== -1) { 122 | const file = item.getAsFile(); 123 | if (file) { 124 | handleFileUpload(file); 125 | } 126 | break; 127 | } 128 | } 129 | } 130 | }, [handleFileUpload]); 131 | 132 | const copyToClipboard = useCallback((text: string, format: string) => { 133 | navigator.clipboard.writeText(text).then(() => { 134 | toast({ 135 | title: "Copied to clipboard", 136 | description: `${format} link copied successfully.`, 137 | }); 138 | }); 139 | }, []); 140 | 141 | useEffect(() => { 142 | document.addEventListener("paste", handlePaste); 143 | return () => { 144 | document.removeEventListener("paste", handlePaste); 145 | }; 146 | }, [handlePaste]); 147 | 148 | return ( 149 |
150 | 151 | 152 |
162 |
163 |
168 | 173 |
174 | 175 |

176 | {isDragging ? "Drop your image here" : "Upload your image"} 177 |

178 | 179 |

180 | Drag & drop, browse files, or paste from clipboard 181 |

182 | 183 |
184 | 192 | 193 |
194 | or press Ctrl+V 195 |
196 |
197 | 198 |
199 | Maximum file size: 10MB • Supported formats: PNG, JPG, GIF, WebP 200 |
201 |
202 | 203 | 210 |
211 |
212 |
213 | 214 | {uploadResult && linkFormats && ( 215 | 216 | 217 | 218 |
219 | 220 |
221 | Upload Complete 222 |
223 | 224 | Your image has been uploaded successfully. Choose your preferred format: 225 | 226 |
227 | 228 |
229 |
230 | 231 |
232 | 237 | 245 |
246 |
247 | 248 |
249 | 250 |
251 | 256 | 264 |
265 |
266 | 267 |
268 | 269 |
270 | 275 | 283 |
284 |
285 |
286 | 287 |
288 |

Preview

289 |
290 | {/* eslint-disable-next-line @next/next/no-img-element */} 291 | Uploaded image preview 296 |
297 |
298 |
299 |
300 | )} 301 |
302 | ); 303 | } -------------------------------------------------------------------------------- /frontend/src/hooks/use-toast.ts: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | // Inspired by react-hot-toast library 4 | import * as React from "react" 5 | 6 | import type { 7 | ToastActionElement, 8 | ToastProps, 9 | } from "@/components/ui/toast" 10 | 11 | const TOAST_LIMIT = 1 12 | const TOAST_REMOVE_DELAY = 1000000 13 | 14 | type ToasterToast = ToastProps & { 15 | id: string 16 | title?: React.ReactNode 17 | description?: React.ReactNode 18 | action?: ToastActionElement 19 | } 20 | 21 | let count = 0 22 | 23 | function genId() { 24 | count = (count + 1) % Number.MAX_SAFE_INTEGER 25 | return count.toString() 26 | } 27 | 28 | type Action = 29 | | { 30 | type: "ADD_TOAST" 31 | toast: ToasterToast 32 | } 33 | | { 34 | type: "UPDATE_TOAST" 35 | toast: Partial 36 | } 37 | | { 38 | type: "DISMISS_TOAST" 39 | toastId?: ToasterToast["id"] 40 | } 41 | | { 42 | type: "REMOVE_TOAST" 43 | toastId?: ToasterToast["id"] 44 | } 45 | 46 | interface State { 47 | toasts: ToasterToast[] 48 | } 49 | 50 | const toastTimeouts = new Map>() 51 | 52 | const addToRemoveQueue = (toastId: string) => { 53 | if (toastTimeouts.has(toastId)) { 54 | return 55 | } 56 | 57 | const timeout = setTimeout(() => { 58 | toastTimeouts.delete(toastId) 59 | dispatch({ 60 | type: "REMOVE_TOAST", 61 | toastId: toastId, 62 | }) 63 | }, TOAST_REMOVE_DELAY) 64 | 65 | toastTimeouts.set(toastId, timeout) 66 | } 67 | 68 | export const reducer = (state: State, action: Action): State => { 69 | switch (action.type) { 70 | case "ADD_TOAST": 71 | return { 72 | ...state, 73 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 74 | } 75 | 76 | case "UPDATE_TOAST": 77 | return { 78 | ...state, 79 | toasts: state.toasts.map((t) => 80 | t.id === action.toast.id ? { ...t, ...action.toast } : t 81 | ), 82 | } 83 | 84 | case "DISMISS_TOAST": { 85 | const { toastId } = action 86 | 87 | // ! Side effects ! - This could be extracted into a dismissToast() action, 88 | // but I'll keep it here for simplicity 89 | if (toastId) { 90 | addToRemoveQueue(toastId) 91 | } else { 92 | state.toasts.forEach((toast) => { 93 | addToRemoveQueue(toast.id) 94 | }) 95 | } 96 | 97 | return { 98 | ...state, 99 | toasts: state.toasts.map((t) => 100 | t.id === toastId || toastId === undefined 101 | ? { 102 | ...t, 103 | open: false, 104 | } 105 | : t 106 | ), 107 | } 108 | } 109 | case "REMOVE_TOAST": 110 | if (action.toastId === undefined) { 111 | return { 112 | ...state, 113 | toasts: [], 114 | } 115 | } 116 | return { 117 | ...state, 118 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 119 | } 120 | } 121 | } 122 | 123 | const listeners: Array<(state: State) => void> = [] 124 | 125 | let memoryState: State = { toasts: [] } 126 | 127 | function dispatch(action: Action) { 128 | memoryState = reducer(memoryState, action) 129 | listeners.forEach((listener) => { 130 | listener(memoryState) 131 | }) 132 | } 133 | 134 | type Toast = Omit 135 | 136 | function toast({ ...props }: Toast) { 137 | const id = genId() 138 | 139 | const update = (props: ToasterToast) => 140 | dispatch({ 141 | type: "UPDATE_TOAST", 142 | toast: { ...props, id }, 143 | }) 144 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) 145 | 146 | dispatch({ 147 | type: "ADD_TOAST", 148 | toast: { 149 | ...props, 150 | id, 151 | open: true, 152 | onOpenChange: (open) => { 153 | if (!open) dismiss() 154 | }, 155 | }, 156 | }) 157 | 158 | return { 159 | id: id, 160 | dismiss, 161 | update, 162 | } 163 | } 164 | 165 | function useToast() { 166 | const [state, setState] = React.useState(memoryState) 167 | 168 | React.useEffect(() => { 169 | listeners.push(setState) 170 | return () => { 171 | const index = listeners.indexOf(setState) 172 | if (index > -1) { 173 | listeners.splice(index, 1) 174 | } 175 | } 176 | }, [state]) 177 | 178 | return { 179 | ...state, 180 | toast, 181 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 182 | } 183 | } 184 | 185 | export { useToast, toast } 186 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | colors: { 21 | border: "hsl(var(--border))", 22 | input: "hsl(var(--input))", 23 | ring: "hsl(var(--ring))", 24 | background: "hsl(var(--background))", 25 | foreground: "hsl(var(--foreground))", 26 | primary: { 27 | DEFAULT: "hsl(var(--primary))", 28 | foreground: "hsl(var(--primary-foreground))", 29 | }, 30 | secondary: { 31 | DEFAULT: "hsl(var(--secondary))", 32 | foreground: "hsl(var(--secondary-foreground))", 33 | }, 34 | destructive: { 35 | DEFAULT: "hsl(var(--destructive))", 36 | foreground: "hsl(var(--destructive-foreground))", 37 | }, 38 | muted: { 39 | DEFAULT: "hsl(var(--muted))", 40 | foreground: "hsl(var(--muted-foreground))", 41 | }, 42 | accent: { 43 | DEFAULT: "hsl(var(--accent))", 44 | foreground: "hsl(var(--accent-foreground))", 45 | }, 46 | popover: { 47 | DEFAULT: "hsl(var(--popover))", 48 | foreground: "hsl(var(--popover-foreground))", 49 | }, 50 | card: { 51 | DEFAULT: "hsl(var(--card))", 52 | foreground: "hsl(var(--card-foreground))", 53 | }, 54 | }, 55 | borderRadius: { 56 | lg: "var(--radius)", 57 | md: "calc(var(--radius) - 2px)", 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | "accordion-down": { 62 | from: { height: "0" }, 63 | to: { height: "var(--radix-accordion-content-height)" }, 64 | }, 65 | "accordion-up": { 66 | from: { height: "var(--radix-accordion-content-height)" }, 67 | to: { height: "0" }, 68 | }, 69 | }, 70 | animation: { 71 | "accordion-down": "accordion-down 0.2s ease-out", 72 | "accordion-up": "accordion-up 0.2s ease-out", 73 | }, 74 | }, 75 | }, 76 | plugins: [require("tailwindcss-animate")], 77 | }; 78 | 79 | export default config; -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/missuo/discord-image 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/bwmarrin/discordgo v0.28.1 7 | github.com/gin-contrib/cors v1.7.1 8 | github.com/gin-gonic/gin v1.9.1 9 | github.com/google/uuid v1.6.0 10 | github.com/spf13/viper v1.18.2 11 | ) 12 | 13 | require ( 14 | github.com/bytedance/sonic v1.11.3 // indirect 15 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 16 | github.com/chenzhuoyu/iasm v0.9.1 // indirect 17 | github.com/fsnotify/fsnotify v1.7.0 // indirect 18 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 19 | github.com/gin-contrib/sse v0.1.0 // indirect 20 | github.com/go-playground/locales v0.14.1 // indirect 21 | github.com/go-playground/universal-translator v0.18.1 // indirect 22 | github.com/go-playground/validator/v10 v10.19.0 // indirect 23 | github.com/goccy/go-json v0.10.2 // indirect 24 | github.com/gorilla/websocket v1.4.2 // indirect 25 | github.com/hashicorp/hcl v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 28 | github.com/leodido/go-urn v1.4.0 // indirect 29 | github.com/magiconair/properties v1.8.7 // indirect 30 | github.com/mattn/go-isatty v0.0.20 // indirect 31 | github.com/mitchellh/mapstructure v1.5.0 // indirect 32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 33 | github.com/modern-go/reflect2 v1.0.2 // indirect 34 | github.com/pelletier/go-toml/v2 v2.2.0 // indirect 35 | github.com/sagikazarmark/locafero v0.4.0 // indirect 36 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 37 | github.com/sourcegraph/conc v0.3.0 // indirect 38 | github.com/spf13/afero v1.11.0 // indirect 39 | github.com/spf13/cast v1.6.0 // indirect 40 | github.com/spf13/pflag v1.0.5 // indirect 41 | github.com/subosito/gotenv v1.6.0 // indirect 42 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 43 | github.com/ugorji/go/codec v1.2.12 // indirect 44 | go.uber.org/atomic v1.9.0 // indirect 45 | go.uber.org/multierr v1.9.0 // indirect 46 | golang.org/x/arch v0.7.0 // indirect 47 | golang.org/x/crypto v0.36.0 // indirect 48 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 49 | golang.org/x/net v0.38.0 // indirect 50 | golang.org/x/sys v0.31.0 // indirect 51 | golang.org/x/text v0.23.0 // indirect 52 | google.golang.org/protobuf v1.33.0 // indirect 53 | gopkg.in/ini.v1 v1.67.0 // indirect 54 | gopkg.in/yaml.v3 v3.0.1 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= 2 | github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= 3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 5 | github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= 6 | github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 7 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 9 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= 10 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= 11 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 12 | github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= 13 | github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 17 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 19 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 20 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 21 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 22 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 23 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 24 | github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs= 25 | github.com/gin-contrib/cors v1.7.1/go.mod h1:n/Zj7B4xyrgk/cX1WCX2dkzFfaNm/xJb6oIUk7WTtps= 26 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 27 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 28 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 29 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 30 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 31 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 32 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 33 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 34 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 35 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 36 | github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= 37 | github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 38 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 39 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 40 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 41 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 42 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 43 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 44 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 45 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 46 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 47 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 48 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 49 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 50 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 51 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 52 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 53 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 54 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 55 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 56 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 57 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 58 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 59 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 60 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 61 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 62 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 63 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 64 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 65 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 66 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 67 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 68 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 69 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 70 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 71 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 72 | github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= 73 | github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 74 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 76 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 77 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 78 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 79 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 80 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 81 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 82 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 83 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 84 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 85 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 86 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 87 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 88 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 89 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 90 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 91 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= 92 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= 93 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 94 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 95 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 96 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 97 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 98 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 99 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 100 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 101 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 102 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 103 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 104 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 105 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 106 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 107 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 108 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 109 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 110 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 111 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 112 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 113 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 114 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 115 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 116 | golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= 117 | golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 118 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 119 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 120 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 121 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 122 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 123 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 124 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 125 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 126 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 127 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 128 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 129 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 130 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 131 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 132 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 133 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 134 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 135 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 136 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 137 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 138 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 139 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 140 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 141 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 142 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 143 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 144 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 145 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 146 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 147 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 148 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Vincent Yang 3 | * @Date: 2024-04-09 03:35:57 4 | * @LastEditors: Vincent Yang 5 | * @LastEditTime: 2025-07-12 04:55:18 6 | * @FilePath: /discord-image/main.go 7 | * @Telegram: https://t.me/missuo 8 | * @GitHub: https://github.com/missuo 9 | * 10 | * Copyright © 2024 by Vincent, All Rights Reserved. 11 | */ 12 | 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | "log" 18 | "net/http" 19 | "os" 20 | "path/filepath" 21 | "regexp" 22 | "strings" 23 | "time" 24 | 25 | "github.com/gin-contrib/cors" 26 | "github.com/gin-gonic/gin" 27 | "github.com/google/uuid" 28 | "github.com/missuo/discord-image/bot" 29 | "github.com/spf13/viper" 30 | ) 31 | 32 | func main() { 33 | // Set default values for configuration 34 | viper.SetDefault("bot.token", "") 35 | viper.SetDefault("bot.channel_id", "") 36 | viper.SetDefault("upload.temp_dir", "uploads") 37 | viper.SetDefault("proxy_url", "") 38 | viper.SetDefault("auto_delete", true) 39 | 40 | // Read configuration from environment variables 41 | viper.AutomaticEnv() 42 | 43 | viper.BindEnv("bot.token", "BOT_TOKEN") 44 | viper.BindEnv("bot.channel_id", "CHANNEL_ID") 45 | viper.BindEnv("upload.temp_dir", "UPLOAD_DIR") 46 | viper.BindEnv("proxy_url", "PROXY_URL") 47 | viper.BindEnv("auto_delete", "AUTO_DELETE") 48 | 49 | // Read configuration from config.yaml if it exists 50 | viper.SetConfigFile("config.yaml") 51 | if err := viper.ReadInConfig(); err == nil { 52 | log.Println("Using config file:", viper.ConfigFileUsed()) 53 | } 54 | 55 | botToken := viper.GetString("bot.token") 56 | channelID := viper.GetString("bot.channel_id") 57 | uploadDir := viper.GetString("upload.temp_dir") 58 | proxyUrl := viper.GetString("proxy_url") 59 | autoDelete := viper.GetBool("auto_delete") 60 | 61 | // Make sure the required configuration values are set 62 | if botToken == "" { 63 | log.Fatal("BOT_TOKEN environment variable or bot.token in config.yaml is not set") 64 | } 65 | if channelID == "" { 66 | log.Fatal("CHANNEL_ID environment variable or bot.channel_id in config.yaml is not set") 67 | } 68 | 69 | bot.BotToken = botToken 70 | 71 | // Make sure the upload directory exists 72 | if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil { 73 | log.Fatalf("Failed to create upload directory: %v", err) 74 | } 75 | 76 | // Start the bot 77 | go bot.Run() 78 | 79 | // Create Gin server 80 | gin.SetMode(gin.ReleaseMode) 81 | r := gin.Default() 82 | r.Use(cors.Default()) 83 | 84 | // Upload image API 85 | r.POST("/upload", func(c *gin.Context) { 86 | host := c.Request.Host 87 | ipPortRegex := regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{1,5})?$`) 88 | var linkPrefix string 89 | if ipPortRegex.MatchString(host) { 90 | linkPrefix = "http://" + host 91 | } else { 92 | linkPrefix = "https://" + host 93 | } 94 | 95 | file, err := c.FormFile("image") 96 | if err != nil { 97 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 98 | return 99 | } 100 | 101 | // Check if the image size exceeds 25MB 102 | if file.Size > 25*1024*1024 { 103 | c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds the maximum limit of 25MB"}) 104 | return 105 | } 106 | 107 | // Generate a unique filename 108 | ext := filepath.Ext(file.Filename) 109 | filename := fmt.Sprintf("%d_%s%s", time.Now().UnixNano(), uuid.New().String(), ext) 110 | 111 | // Save the file to the specified directory 112 | filePath := filepath.Join(uploadDir, filename) 113 | if err := c.SaveUploadedFile(file, filePath); err != nil { 114 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 115 | return 116 | } 117 | 118 | // Trigger the bot to send the image to the group 119 | message, err := bot.SendImage(channelID, filePath) 120 | if err != nil { 121 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 122 | return 123 | } 124 | 125 | // Delete the uploaded image if auto_delete is true 126 | if autoDelete { 127 | os.Remove(filePath) 128 | } 129 | 130 | // Return the URL to access the image 131 | c.JSON(http.StatusOK, gin.H{"url": fmt.Sprintf("%s/file/%s", linkPrefix, message.ID)}) 132 | }) 133 | 134 | // API for accessing images 135 | r.GET("/file/:id", func(c *gin.Context) { 136 | messageID := c.Param("id") 137 | 138 | // Query the bot to get the image URL 139 | url, err := bot.GetImageURL(channelID, messageID) 140 | if err != nil { 141 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 142 | return 143 | } 144 | 145 | if proxyUrl != "" { 146 | url = strings.Replace(url, "https://cdn.discordapp.com", "https://"+proxyUrl, 1) 147 | } 148 | 149 | // Redirect to the image URL 150 | c.Redirect(http.StatusFound, url) 151 | }) 152 | 153 | // Serve Next.js static assets 154 | r.Static("/_next", "./public/_next") 155 | r.StaticFile("/favicon.ico", "./public/favicon.ico") 156 | r.StaticFile("/next.svg", "./public/next.svg") 157 | r.StaticFile("/vercel.svg", "./public/vercel.svg") 158 | r.StaticFile("/file.svg", "./public/file.svg") 159 | r.StaticFile("/globe.svg", "./public/globe.svg") 160 | r.StaticFile("/window.svg", "./public/window.svg") 161 | 162 | // Serve main page and handle SPA routing 163 | r.GET("/", func(c *gin.Context) { 164 | c.File("./public/index.html") 165 | }) 166 | 167 | // Fallback for SPA routing - serve index.html for any unmatched routes 168 | r.NoRoute(func(c *gin.Context) { 169 | c.File("./public/index.html") 170 | }) 171 | 172 | // Start Gin server 173 | if err := r.Run(":8080"); err != nil { 174 | log.Fatal(err) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 404: This page could not be found.Discord Image Upload

404

This page could not be found.

    -------------------------------------------------------------------------------- /public/404/index.html: -------------------------------------------------------------------------------- 1 | 404: This page could not be found.Discord Image Upload

    404

    This page could not be found.

      -------------------------------------------------------------------------------- /public/_next/static/chunks/455-a269af0bea64e1d5.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[455],{2085:(e,r,o)=>{o.d(r,{F:()=>l});var t=o(2596);let n=e=>"boolean"==typeof e?`${e}`:0===e?"0":e,a=t.$,l=(e,r)=>o=>{var t;if((null==r?void 0:r.variants)==null)return a(e,null==o?void 0:o.class,null==o?void 0:o.className);let{variants:l,defaultVariants:s}=r,i=Object.keys(l).map(e=>{let r=null==o?void 0:o[e],t=null==s?void 0:s[e];if(null===r)return null;let a=n(r)||n(t);return l[e][a]}),d=o&&Object.entries(o).reduce((e,r)=>{let[o,t]=r;return void 0===t||(e[o]=t),e},{});return a(e,i,null==r||null==(t=r.compoundVariants)?void 0:t.reduce((e,r)=>{let{class:o,className:t,...n}=r;return Object.entries(n).every(e=>{let[r,o]=e;return Array.isArray(o)?o.includes({...s,...d}[r]):({...s,...d})[r]===o})?[...e,o,t]:e},[]),null==o?void 0:o.class,null==o?void 0:o.className)}},2596:(e,r,o)=>{o.d(r,{$:()=>t});function t(){for(var e,r,o=0,t="",n=arguments.length;o{o.d(r,{s:()=>l,t:()=>a});var t=o(2115);function n(e,r){if("function"==typeof e)return e(r);null!=e&&(e.current=r)}function a(...e){return r=>{let o=!1,t=e.map(e=>{let t=n(e,r);return o||"function"!=typeof t||(o=!0),t});if(o)return()=>{for(let r=0;r{o.d(r,{QP:()=>ed});let t=e=>{let r=s(e),{conflictingClassGroups:o,conflictingClassGroupModifiers:t}=e;return{getClassGroupId:e=>{let o=e.split("-");return""===o[0]&&1!==o.length&&o.shift(),n(o,r)||l(e)},getConflictingClassGroupIds:(e,r)=>{let n=o[e]||[];return r&&t[e]?[...n,...t[e]]:n}}},n=(e,r)=>{if(0===e.length)return r.classGroupId;let o=e[0],t=r.nextPart.get(o),a=t?n(e.slice(1),t):void 0;if(a)return a;if(0===r.validators.length)return;let l=e.join("-");return r.validators.find(({validator:e})=>e(l))?.classGroupId},a=/^\[(.+)\]$/,l=e=>{if(a.test(e)){let r=a.exec(e)[1],o=r?.substring(0,r.indexOf(":"));if(o)return"arbitrary.."+o}},s=e=>{let{theme:r,classGroups:o}=e,t={nextPart:new Map,validators:[]};for(let e in o)i(o[e],t,e,r);return t},i=(e,r,o,t)=>{e.forEach(e=>{if("string"==typeof e){(""===e?r:d(r,e)).classGroupId=o;return}if("function"==typeof e)return c(e)?void i(e(t),r,o,t):void r.validators.push({validator:e,classGroupId:o});Object.entries(e).forEach(([e,n])=>{i(n,d(r,e),o,t)})})},d=(e,r)=>{let o=e;return r.split("-").forEach(e=>{o.nextPart.has(e)||o.nextPart.set(e,{nextPart:new Map,validators:[]}),o=o.nextPart.get(e)}),o},c=e=>e.isThemeGetter,m=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let r=0,o=new Map,t=new Map,n=(n,a)=>{o.set(n,a),++r>e&&(r=0,t=o,o=new Map)};return{get(e){let r=o.get(e);return void 0!==r?r:void 0!==(r=t.get(e))?(n(e,r),r):void 0},set(e,r){o.has(e)?o.set(e,r):n(e,r)}}},p=e=>{let{prefix:r,experimentalParseClassName:o}=e,t=e=>{let r,o=[],t=0,n=0,a=0;for(let l=0;la?r-a:void 0}};if(r){let e=r+":",o=t;t=r=>r.startsWith(e)?o(r.substring(e.length)):{isExternal:!0,modifiers:[],hasImportantModifier:!1,baseClassName:r,maybePostfixModifierPosition:void 0}}if(o){let e=t;t=r=>o({className:r,parseClassName:e})}return t},u=e=>e.endsWith("!")?e.substring(0,e.length-1):e.startsWith("!")?e.substring(1):e,f=e=>{let r=Object.fromEntries(e.orderSensitiveModifiers.map(e=>[e,!0]));return e=>{if(e.length<=1)return e;let o=[],t=[];return e.forEach(e=>{"["===e[0]||r[e]?(o.push(...t.sort(),e),t=[]):t.push(e)}),o.push(...t.sort()),o}},b=e=>({cache:m(e.cacheSize),parseClassName:p(e),sortModifiers:f(e),...t(e)}),g=/\s+/,h=(e,r)=>{let{parseClassName:o,getClassGroupId:t,getConflictingClassGroupIds:n,sortModifiers:a}=r,l=[],s=e.trim().split(g),i="";for(let e=s.length-1;e>=0;e-=1){let r=s[e],{isExternal:d,modifiers:c,hasImportantModifier:m,baseClassName:p,maybePostfixModifierPosition:u}=o(r);if(d){i=r+(i.length>0?" "+i:i);continue}let f=!!u,b=t(f?p.substring(0,u):p);if(!b){if(!f||!(b=t(p))){i=r+(i.length>0?" "+i:i);continue}f=!1}let g=a(c).join(":"),h=m?g+"!":g,k=h+b;if(l.includes(k))continue;l.push(k);let w=n(b,f);for(let e=0;e0?" "+i:i)}return i};function k(){let e,r,o=0,t="";for(;o{let r;if("string"==typeof e)return e;let o="";for(let t=0;t{let r=r=>r[e]||[];return r.isThemeGetter=!0,r},y=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,v=/^\((?:(\w[\w-]*):)?(.+)\)$/i,z=/^\d+\/\d+$/,j=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,C=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,N=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,E=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,$=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,A=e=>z.test(e),M=e=>!!e&&!Number.isNaN(Number(e)),P=e=>!!e&&Number.isInteger(Number(e)),O=e=>e.endsWith("%")&&M(e.slice(0,-1)),W=e=>j.test(e),_=()=>!0,G=e=>C.test(e)&&!N.test(e),I=()=>!1,S=e=>E.test(e),R=e=>$.test(e),L=e=>!T(e)&&!U(e),V=e=>ee(e,en,I),T=e=>y.test(e),D=e=>ee(e,ea,G),Z=e=>ee(e,el,M),q=e=>ee(e,eo,I),B=e=>ee(e,et,R),F=e=>ee(e,ei,S),U=e=>v.test(e),Q=e=>er(e,ea),X=e=>er(e,es),H=e=>er(e,eo),J=e=>er(e,en),K=e=>er(e,et),Y=e=>er(e,ei,!0),ee=(e,r,o)=>{let t=y.exec(e);return!!t&&(t[1]?r(t[1]):o(t[2]))},er=(e,r,o=!1)=>{let t=v.exec(e);return!!t&&(t[1]?r(t[1]):o)},eo=e=>"position"===e||"percentage"===e,et=e=>"image"===e||"url"===e,en=e=>"length"===e||"size"===e||"bg-size"===e,ea=e=>"length"===e,el=e=>"number"===e,es=e=>"family-name"===e,ei=e=>"shadow"===e;Symbol.toStringTag;let ed=function(e,...r){let o,t,n,a=function(s){return t=(o=b(r.reduce((e,r)=>r(e),e()))).cache.get,n=o.cache.set,a=l,l(s)};function l(e){let r=t(e);if(r)return r;let a=h(e,o);return n(e,a),a}return function(){return a(k.apply(null,arguments))}}(()=>{let e=x("color"),r=x("font"),o=x("text"),t=x("font-weight"),n=x("tracking"),a=x("leading"),l=x("breakpoint"),s=x("container"),i=x("spacing"),d=x("radius"),c=x("shadow"),m=x("inset-shadow"),p=x("text-shadow"),u=x("drop-shadow"),f=x("blur"),b=x("perspective"),g=x("aspect"),h=x("ease"),k=x("animate"),w=()=>["auto","avoid","all","avoid-page","page","left","right","column"],y=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],v=()=>[...y(),U,T],z=()=>["auto","hidden","clip","visible","scroll"],j=()=>["auto","contain","none"],C=()=>[U,T,i],N=()=>[A,"full","auto",...C()],E=()=>[P,"none","subgrid",U,T],$=()=>["auto",{span:["full",P,U,T]},P,U,T],G=()=>[P,"auto",U,T],I=()=>["auto","min","max","fr",U,T],S=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],R=()=>["start","end","center","stretch","center-safe","end-safe"],ee=()=>["auto",...C()],er=()=>[A,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...C()],eo=()=>[e,U,T],et=()=>[...y(),H,q,{position:[U,T]}],en=()=>["no-repeat",{repeat:["","x","y","space","round"]}],ea=()=>["auto","cover","contain",J,V,{size:[U,T]}],el=()=>[O,Q,D],es=()=>["","none","full",d,U,T],ei=()=>["",M,Q,D],ed=()=>["solid","dashed","dotted","double"],ec=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],em=()=>[M,O,H,q],ep=()=>["","none",f,U,T],eu=()=>["none",M,U,T],ef=()=>["none",M,U,T],eb=()=>[M,U,T],eg=()=>[A,"full",...C()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[W],breakpoint:[W],color:[_],container:[W],"drop-shadow":[W],ease:["in","out","in-out"],font:[L],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[W],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[W],shadow:[W],spacing:["px",M],text:[W],"text-shadow":[W],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",A,T,U,g]}],container:["container"],columns:[{columns:[M,T,U,s]}],"break-after":[{"break-after":w()}],"break-before":[{"break-before":w()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:v()}],overflow:[{overflow:z()}],"overflow-x":[{"overflow-x":z()}],"overflow-y":[{"overflow-y":z()}],overscroll:[{overscroll:j()}],"overscroll-x":[{"overscroll-x":j()}],"overscroll-y":[{"overscroll-y":j()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:N()}],"inset-x":[{"inset-x":N()}],"inset-y":[{"inset-y":N()}],start:[{start:N()}],end:[{end:N()}],top:[{top:N()}],right:[{right:N()}],bottom:[{bottom:N()}],left:[{left:N()}],visibility:["visible","invisible","collapse"],z:[{z:[P,"auto",U,T]}],basis:[{basis:[A,"full","auto",s,...C()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[M,A,"auto","initial","none",T]}],grow:[{grow:["",M,U,T]}],shrink:[{shrink:["",M,U,T]}],order:[{order:[P,"first","last","none",U,T]}],"grid-cols":[{"grid-cols":E()}],"col-start-end":[{col:$()}],"col-start":[{"col-start":G()}],"col-end":[{"col-end":G()}],"grid-rows":[{"grid-rows":E()}],"row-start-end":[{row:$()}],"row-start":[{"row-start":G()}],"row-end":[{"row-end":G()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":I()}],"auto-rows":[{"auto-rows":I()}],gap:[{gap:C()}],"gap-x":[{"gap-x":C()}],"gap-y":[{"gap-y":C()}],"justify-content":[{justify:[...S(),"normal"]}],"justify-items":[{"justify-items":[...R(),"normal"]}],"justify-self":[{"justify-self":["auto",...R()]}],"align-content":[{content:["normal",...S()]}],"align-items":[{items:[...R(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...R(),{baseline:["","last"]}]}],"place-content":[{"place-content":S()}],"place-items":[{"place-items":[...R(),"baseline"]}],"place-self":[{"place-self":["auto",...R()]}],p:[{p:C()}],px:[{px:C()}],py:[{py:C()}],ps:[{ps:C()}],pe:[{pe:C()}],pt:[{pt:C()}],pr:[{pr:C()}],pb:[{pb:C()}],pl:[{pl:C()}],m:[{m:ee()}],mx:[{mx:ee()}],my:[{my:ee()}],ms:[{ms:ee()}],me:[{me:ee()}],mt:[{mt:ee()}],mr:[{mr:ee()}],mb:[{mb:ee()}],ml:[{ml:ee()}],"space-x":[{"space-x":C()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":C()}],"space-y-reverse":["space-y-reverse"],size:[{size:er()}],w:[{w:[s,"screen",...er()]}],"min-w":[{"min-w":[s,"screen","none",...er()]}],"max-w":[{"max-w":[s,"screen","none","prose",{screen:[l]},...er()]}],h:[{h:["screen","lh",...er()]}],"min-h":[{"min-h":["screen","lh","none",...er()]}],"max-h":[{"max-h":["screen","lh",...er()]}],"font-size":[{text:["base",o,Q,D]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[t,U,Z]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",O,T]}],"font-family":[{font:[X,T,r]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[n,U,T]}],"line-clamp":[{"line-clamp":[M,"none",U,Z]}],leading:[{leading:[a,...C()]}],"list-image":[{"list-image":["none",U,T]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",U,T]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:eo()}],"text-color":[{text:eo()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...ed(),"wavy"]}],"text-decoration-thickness":[{decoration:[M,"from-font","auto",U,D]}],"text-decoration-color":[{decoration:eo()}],"underline-offset":[{"underline-offset":[M,"auto",U,T]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:C()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",U,T]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",U,T]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:et()}],"bg-repeat":[{bg:en()}],"bg-size":[{bg:ea()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},P,U,T],radial:["",U,T],conic:[P,U,T]},K,B]}],"bg-color":[{bg:eo()}],"gradient-from-pos":[{from:el()}],"gradient-via-pos":[{via:el()}],"gradient-to-pos":[{to:el()}],"gradient-from":[{from:eo()}],"gradient-via":[{via:eo()}],"gradient-to":[{to:eo()}],rounded:[{rounded:es()}],"rounded-s":[{"rounded-s":es()}],"rounded-e":[{"rounded-e":es()}],"rounded-t":[{"rounded-t":es()}],"rounded-r":[{"rounded-r":es()}],"rounded-b":[{"rounded-b":es()}],"rounded-l":[{"rounded-l":es()}],"rounded-ss":[{"rounded-ss":es()}],"rounded-se":[{"rounded-se":es()}],"rounded-ee":[{"rounded-ee":es()}],"rounded-es":[{"rounded-es":es()}],"rounded-tl":[{"rounded-tl":es()}],"rounded-tr":[{"rounded-tr":es()}],"rounded-br":[{"rounded-br":es()}],"rounded-bl":[{"rounded-bl":es()}],"border-w":[{border:ei()}],"border-w-x":[{"border-x":ei()}],"border-w-y":[{"border-y":ei()}],"border-w-s":[{"border-s":ei()}],"border-w-e":[{"border-e":ei()}],"border-w-t":[{"border-t":ei()}],"border-w-r":[{"border-r":ei()}],"border-w-b":[{"border-b":ei()}],"border-w-l":[{"border-l":ei()}],"divide-x":[{"divide-x":ei()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":ei()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...ed(),"hidden","none"]}],"divide-style":[{divide:[...ed(),"hidden","none"]}],"border-color":[{border:eo()}],"border-color-x":[{"border-x":eo()}],"border-color-y":[{"border-y":eo()}],"border-color-s":[{"border-s":eo()}],"border-color-e":[{"border-e":eo()}],"border-color-t":[{"border-t":eo()}],"border-color-r":[{"border-r":eo()}],"border-color-b":[{"border-b":eo()}],"border-color-l":[{"border-l":eo()}],"divide-color":[{divide:eo()}],"outline-style":[{outline:[...ed(),"none","hidden"]}],"outline-offset":[{"outline-offset":[M,U,T]}],"outline-w":[{outline:["",M,Q,D]}],"outline-color":[{outline:eo()}],shadow:[{shadow:["","none",c,Y,F]}],"shadow-color":[{shadow:eo()}],"inset-shadow":[{"inset-shadow":["none",m,Y,F]}],"inset-shadow-color":[{"inset-shadow":eo()}],"ring-w":[{ring:ei()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:eo()}],"ring-offset-w":[{"ring-offset":[M,D]}],"ring-offset-color":[{"ring-offset":eo()}],"inset-ring-w":[{"inset-ring":ei()}],"inset-ring-color":[{"inset-ring":eo()}],"text-shadow":[{"text-shadow":["none",p,Y,F]}],"text-shadow-color":[{"text-shadow":eo()}],opacity:[{opacity:[M,U,T]}],"mix-blend":[{"mix-blend":[...ec(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":ec()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[M]}],"mask-image-linear-from-pos":[{"mask-linear-from":em()}],"mask-image-linear-to-pos":[{"mask-linear-to":em()}],"mask-image-linear-from-color":[{"mask-linear-from":eo()}],"mask-image-linear-to-color":[{"mask-linear-to":eo()}],"mask-image-t-from-pos":[{"mask-t-from":em()}],"mask-image-t-to-pos":[{"mask-t-to":em()}],"mask-image-t-from-color":[{"mask-t-from":eo()}],"mask-image-t-to-color":[{"mask-t-to":eo()}],"mask-image-r-from-pos":[{"mask-r-from":em()}],"mask-image-r-to-pos":[{"mask-r-to":em()}],"mask-image-r-from-color":[{"mask-r-from":eo()}],"mask-image-r-to-color":[{"mask-r-to":eo()}],"mask-image-b-from-pos":[{"mask-b-from":em()}],"mask-image-b-to-pos":[{"mask-b-to":em()}],"mask-image-b-from-color":[{"mask-b-from":eo()}],"mask-image-b-to-color":[{"mask-b-to":eo()}],"mask-image-l-from-pos":[{"mask-l-from":em()}],"mask-image-l-to-pos":[{"mask-l-to":em()}],"mask-image-l-from-color":[{"mask-l-from":eo()}],"mask-image-l-to-color":[{"mask-l-to":eo()}],"mask-image-x-from-pos":[{"mask-x-from":em()}],"mask-image-x-to-pos":[{"mask-x-to":em()}],"mask-image-x-from-color":[{"mask-x-from":eo()}],"mask-image-x-to-color":[{"mask-x-to":eo()}],"mask-image-y-from-pos":[{"mask-y-from":em()}],"mask-image-y-to-pos":[{"mask-y-to":em()}],"mask-image-y-from-color":[{"mask-y-from":eo()}],"mask-image-y-to-color":[{"mask-y-to":eo()}],"mask-image-radial":[{"mask-radial":[U,T]}],"mask-image-radial-from-pos":[{"mask-radial-from":em()}],"mask-image-radial-to-pos":[{"mask-radial-to":em()}],"mask-image-radial-from-color":[{"mask-radial-from":eo()}],"mask-image-radial-to-color":[{"mask-radial-to":eo()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":y()}],"mask-image-conic-pos":[{"mask-conic":[M]}],"mask-image-conic-from-pos":[{"mask-conic-from":em()}],"mask-image-conic-to-pos":[{"mask-conic-to":em()}],"mask-image-conic-from-color":[{"mask-conic-from":eo()}],"mask-image-conic-to-color":[{"mask-conic-to":eo()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:et()}],"mask-repeat":[{mask:en()}],"mask-size":[{mask:ea()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",U,T]}],filter:[{filter:["","none",U,T]}],blur:[{blur:ep()}],brightness:[{brightness:[M,U,T]}],contrast:[{contrast:[M,U,T]}],"drop-shadow":[{"drop-shadow":["","none",u,Y,F]}],"drop-shadow-color":[{"drop-shadow":eo()}],grayscale:[{grayscale:["",M,U,T]}],"hue-rotate":[{"hue-rotate":[M,U,T]}],invert:[{invert:["",M,U,T]}],saturate:[{saturate:[M,U,T]}],sepia:[{sepia:["",M,U,T]}],"backdrop-filter":[{"backdrop-filter":["","none",U,T]}],"backdrop-blur":[{"backdrop-blur":ep()}],"backdrop-brightness":[{"backdrop-brightness":[M,U,T]}],"backdrop-contrast":[{"backdrop-contrast":[M,U,T]}],"backdrop-grayscale":[{"backdrop-grayscale":["",M,U,T]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[M,U,T]}],"backdrop-invert":[{"backdrop-invert":["",M,U,T]}],"backdrop-opacity":[{"backdrop-opacity":[M,U,T]}],"backdrop-saturate":[{"backdrop-saturate":[M,U,T]}],"backdrop-sepia":[{"backdrop-sepia":["",M,U,T]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":C()}],"border-spacing-x":[{"border-spacing-x":C()}],"border-spacing-y":[{"border-spacing-y":C()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",U,T]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[M,"initial",U,T]}],ease:[{ease:["linear","initial",h,U,T]}],delay:[{delay:[M,U,T]}],animate:[{animate:["none",k,U,T]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[b,U,T]}],"perspective-origin":[{"perspective-origin":v()}],rotate:[{rotate:eu()}],"rotate-x":[{"rotate-x":eu()}],"rotate-y":[{"rotate-y":eu()}],"rotate-z":[{"rotate-z":eu()}],scale:[{scale:ef()}],"scale-x":[{"scale-x":ef()}],"scale-y":[{"scale-y":ef()}],"scale-z":[{"scale-z":ef()}],"scale-3d":["scale-3d"],skew:[{skew:eb()}],"skew-x":[{"skew-x":eb()}],"skew-y":[{"skew-y":eb()}],transform:[{transform:[U,T,"","none","gpu","cpu"]}],"transform-origin":[{origin:v()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:eg()}],"translate-x":[{"translate-x":eg()}],"translate-y":[{"translate-y":eg()}],"translate-z":[{"translate-z":eg()}],"translate-none":["translate-none"],accent:[{accent:eo()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:eo()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",U,T]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":C()}],"scroll-mx":[{"scroll-mx":C()}],"scroll-my":[{"scroll-my":C()}],"scroll-ms":[{"scroll-ms":C()}],"scroll-me":[{"scroll-me":C()}],"scroll-mt":[{"scroll-mt":C()}],"scroll-mr":[{"scroll-mr":C()}],"scroll-mb":[{"scroll-mb":C()}],"scroll-ml":[{"scroll-ml":C()}],"scroll-p":[{"scroll-p":C()}],"scroll-px":[{"scroll-px":C()}],"scroll-py":[{"scroll-py":C()}],"scroll-ps":[{"scroll-ps":C()}],"scroll-pe":[{"scroll-pe":C()}],"scroll-pt":[{"scroll-pt":C()}],"scroll-pr":[{"scroll-pr":C()}],"scroll-pb":[{"scroll-pb":C()}],"scroll-pl":[{"scroll-pl":C()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",U,T]}],fill:[{fill:["none",...eo()]}],"stroke-w":[{stroke:[M,Q,D,Z]}],stroke:[{stroke:["none",...eo()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}})},9708:(e,r,o)=>{o.d(r,{DX:()=>s,TL:()=>l});var t=o(2115),n=o(6101),a=o(5155);function l(e){let r=function(e){let r=t.forwardRef((e,r)=>{let{children:o,...a}=e;if(t.isValidElement(o)){var l;let e,s,i=(l=o,(s=(e=Object.getOwnPropertyDescriptor(l.props,"ref")?.get)&&"isReactWarning"in e&&e.isReactWarning)?l.ref:(s=(e=Object.getOwnPropertyDescriptor(l,"ref")?.get)&&"isReactWarning"in e&&e.isReactWarning)?l.props.ref:l.props.ref||l.ref),d=function(e,r){let o={...r};for(let t in r){let n=e[t],a=r[t];/^on[A-Z]/.test(t)?n&&a?o[t]=(...e)=>{let r=a(...e);return n(...e),r}:n&&(o[t]=n):"style"===t?o[t]={...n,...a}:"className"===t&&(o[t]=[n,a].filter(Boolean).join(" "))}return{...e,...o}}(a,o.props);return o.type!==t.Fragment&&(d.ref=r?(0,n.t)(r,i):i),t.cloneElement(o,d)}return t.Children.count(o)>1?t.Children.only(null):null});return r.displayName=`${e}.SlotClone`,r}(e),o=t.forwardRef((e,o)=>{let{children:n,...l}=e,s=t.Children.toArray(n),i=s.find(d);if(i){let e=i.props.children,n=s.map(r=>r!==i?r:t.Children.count(e)>1?t.Children.only(null):t.isValidElement(e)?e.props.children:null);return(0,a.jsx)(r,{...l,ref:o,children:t.isValidElement(e)?t.cloneElement(e,void 0,n):null})}return(0,a.jsx)(r,{...l,ref:o,children:n})});return o.displayName=`${e}.Slot`,o}var s=l("Slot"),i=Symbol("radix.slottable");function d(e){return t.isValidElement(e)&&"function"==typeof e.type&&"__radixId"in e.type&&e.type.__radixId===i}},9946:(e,r,o)=>{o.d(r,{A:()=>m});var t=o(2115);let n=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),a=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,r,o)=>o?o.toUpperCase():r.toLowerCase()),l=e=>{let r=a(e);return r.charAt(0).toUpperCase()+r.slice(1)},s=function(){for(var e=arguments.length,r=Array(e),o=0;o!!e&&""!==e.trim()&&o.indexOf(e)===r).join(" ").trim()},i=e=>{for(let r in e)if(r.startsWith("aria-")||"role"===r||"title"===r)return!0};var d={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let c=(0,t.forwardRef)((e,r)=>{let{color:o="currentColor",size:n=24,strokeWidth:a=2,absoluteStrokeWidth:l,className:c="",children:m,iconNode:p,...u}=e;return(0,t.createElement)("svg",{ref:r,...d,width:n,height:n,stroke:o,strokeWidth:l?24*Number(a)/Number(n):a,className:s("lucide",c),...!m&&!i(u)&&{"aria-hidden":"true"},...u},[...p.map(e=>{let[r,o]=e;return(0,t.createElement)(r,o)}),...Array.isArray(m)?m:[m]])}),m=(e,r)=>{let o=(0,t.forwardRef)((o,a)=>{let{className:i,...d}=o;return(0,t.createElement)(c,{ref:a,iconNode:r,className:s("lucide-".concat(n(l(e))),"lucide-".concat(e),i),...d})});return o.displayName=l(e),o}}}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/869-9c07f9cccedfb126.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[869],{4147:e=>{e.exports={style:{fontFamily:"'Geist', 'Geist Fallback'",fontStyle:"normal"},className:"__className_5cfdac",variable:"__variable_5cfdac"}},4416:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});let r=(0,n(9946).A)("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]])},6205:(e,t,n)=>{"use strict";n.d(t,{rc:()=>eE,bm:()=>eh,VY:()=>ey,Kq:()=>ep,bL:()=>em,hE:()=>ew,LM:()=>ev});var r,o,i=n(2115),a=n.t(i,2),s=n(7650);function l(e,t,{checkForDefaultPrevented:n=!0}={}){return function(r){if(e?.(r),!1===n||!r.defaultPrevented)return t?.(r)}}var u=n(6101);function c(e,t,n){if(!t.has(e))throw TypeError("attempted to "+n+" private field on non-instance");return t.get(e)}function d(e,t){var n=c(e,t,"get");return n.get?n.get.call(e):n.value}function f(e,t,n){var r=c(e,t,"set");if(r.set)r.set.call(e,n);else{if(!r.writable)throw TypeError("attempted to set read only private field");r.value=n}return n}var p=n(5155);function v(e,t=[]){let n=[],r=()=>{let t=n.map(e=>i.createContext(e));return function(n){let r=n?.[e]||t;return i.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return r.scopeName=e,[function(t,r){let o=i.createContext(r),a=n.length;n=[...n,r];let s=t=>{let{scope:n,children:r,...s}=t,l=n?.[e]?.[a]||o,u=i.useMemo(()=>s,Object.values(s));return(0,p.jsx)(l.Provider,{value:u,children:r})};return s.displayName=t+"Provider",[s,function(n,s){let l=s?.[e]?.[a]||o,u=i.useContext(l);if(u)return u;if(void 0!==r)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}]},function(...e){let t=e[0];if(1===e.length)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let o=n(e)[`__scope${r}`];return{...t,...o}},{});return i.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}(r,...t)]}var m=n(9708),w=new WeakMap;function y(e,t){if("at"in Array.prototype)return Array.prototype.at.call(e,t);let n=function(e,t){let n=e.length,r=E(t),o=r>=0?r:n+r;return o<0||o>=n?-1:o}(e,t);return -1===n?void 0:e[n]}function E(e){return e!=e||0===e?0:Math.trunc(e)}r=new WeakMap;var h=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"].reduce((e,t)=>{let n=(0,m.TL)(`Primitive.${t}`),r=i.forwardRef((e,r)=>{let{asChild:o,...i}=e;return"undefined"!=typeof window&&(window[Symbol.for("radix-ui")]=!0),(0,p.jsx)(o?n:t,{...i,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function b(e,t){e&&s.flushSync(()=>e.dispatchEvent(t))}function x(e){let t=i.useRef(e);return i.useEffect(()=>{t.current=e}),i.useMemo(()=>(...e)=>t.current?.(...e),[])}var g="dismissableLayer.update",T=i.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),C=i.forwardRef((e,t)=>{var n,r;let{disableOutsidePointerEvents:a=!1,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:d,onInteractOutside:f,onDismiss:v,...m}=e,w=i.useContext(T),[y,E]=i.useState(null),b=null!=(r=null==y?void 0:y.ownerDocument)?r:null==(n=globalThis)?void 0:n.document,[,C]=i.useState({}),N=(0,u.s)(t,e=>E(e)),L=Array.from(w.layers),[S]=[...w.layersWithOutsidePointerEventsDisabled].slice(-1),D=L.indexOf(S),M=y?L.indexOf(y):-1,j=w.layersWithOutsidePointerEventsDisabled.size>0,k=M>=D,O=function(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null==(t=globalThis)?void 0:t.document,r=x(e),o=i.useRef(!1),a=i.useRef(()=>{});return i.useEffect(()=>{let e=e=>{if(e.target&&!o.current){let t=function(){P("dismissableLayer.pointerDownOutside",r,o,{discrete:!0})},o={originalEvent:e};"touch"===e.pointerType?(n.removeEventListener("click",a.current),a.current=t,n.addEventListener("click",a.current,{once:!0})):t()}else n.removeEventListener("click",a.current);o.current=!1},t=window.setTimeout(()=>{n.addEventListener("pointerdown",e)},0);return()=>{window.clearTimeout(t),n.removeEventListener("pointerdown",e),n.removeEventListener("click",a.current)}},[n,r]),{onPointerDownCapture:()=>o.current=!0}}(e=>{let t=e.target,n=[...w.branches].some(e=>e.contains(t));k&&!n&&(null==c||c(e),null==f||f(e),e.defaultPrevented||null==v||v())},b),A=function(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null==(t=globalThis)?void 0:t.document,r=x(e),o=i.useRef(!1);return i.useEffect(()=>{let e=e=>{e.target&&!o.current&&P("dismissableLayer.focusOutside",r,{originalEvent:e},{discrete:!1})};return n.addEventListener("focusin",e),()=>n.removeEventListener("focusin",e)},[n,r]),{onFocusCapture:()=>o.current=!0,onBlurCapture:()=>o.current=!1}}(e=>{let t=e.target;![...w.branches].some(e=>e.contains(t))&&(null==d||d(e),null==f||f(e),e.defaultPrevented||null==v||v())},b);return!function(e,t=globalThis?.document){let n=x(e);i.useEffect(()=>{let e=e=>{"Escape"===e.key&&n(e)};return t.addEventListener("keydown",e,{capture:!0}),()=>t.removeEventListener("keydown",e,{capture:!0})},[n,t])}(e=>{M===w.layers.size-1&&(null==s||s(e),!e.defaultPrevented&&v&&(e.preventDefault(),v()))},b),i.useEffect(()=>{if(y)return a&&(0===w.layersWithOutsidePointerEventsDisabled.size&&(o=b.body.style.pointerEvents,b.body.style.pointerEvents="none"),w.layersWithOutsidePointerEventsDisabled.add(y)),w.layers.add(y),R(),()=>{a&&1===w.layersWithOutsidePointerEventsDisabled.size&&(b.body.style.pointerEvents=o)}},[y,b,a,w]),i.useEffect(()=>()=>{y&&(w.layers.delete(y),w.layersWithOutsidePointerEventsDisabled.delete(y),R())},[y,w]),i.useEffect(()=>{let e=()=>C({});return document.addEventListener(g,e),()=>document.removeEventListener(g,e)},[]),(0,p.jsx)(h.div,{...m,ref:N,style:{pointerEvents:j?k?"auto":"none":void 0,...e.style},onFocusCapture:l(e.onFocusCapture,A.onFocusCapture),onBlurCapture:l(e.onBlurCapture,A.onBlurCapture),onPointerDownCapture:l(e.onPointerDownCapture,O.onPointerDownCapture)})});C.displayName="DismissableLayer";var N=i.forwardRef((e,t)=>{let n=i.useContext(T),r=i.useRef(null),o=(0,u.s)(t,r);return i.useEffect(()=>{let e=r.current;if(e)return n.branches.add(e),()=>{n.branches.delete(e)}},[n.branches]),(0,p.jsx)(h.div,{...e,ref:o})});function R(){let e=new CustomEvent(g);document.dispatchEvent(e)}function P(e,t,n,r){let{discrete:o}=r,i=n.originalEvent.target,a=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),o?b(i,a):i.dispatchEvent(a)}N.displayName="DismissableLayerBranch";var L=globalThis?.document?i.useLayoutEffect:()=>{},S=i.forwardRef((e,t)=>{var n,r;let{container:o,...a}=e,[l,u]=i.useState(!1);L(()=>u(!0),[]);let c=o||l&&(null==(r=globalThis)||null==(n=r.document)?void 0:n.body);return c?s.createPortal((0,p.jsx)(h.div,{...a,ref:t}),c):null});S.displayName="Portal";var D=e=>{let{present:t,children:n}=e,r=function(e){var t,n;let[r,o]=i.useState(),a=i.useRef(null),s=i.useRef(e),l=i.useRef("none"),[u,c]=(t=e?"mounted":"unmounted",n={mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}},i.useReducer((e,t)=>{let r=n[e][t];return null!=r?r:e},t));return i.useEffect(()=>{let e=M(a.current);l.current="mounted"===u?e:"none"},[u]),L(()=>{let t=a.current,n=s.current;if(n!==e){let r=l.current,o=M(t);e?c("MOUNT"):"none"===o||(null==t?void 0:t.display)==="none"?c("UNMOUNT"):n&&r!==o?c("ANIMATION_OUT"):c("UNMOUNT"),s.current=e}},[e,c]),L(()=>{if(r){var e;let t,n=null!=(e=r.ownerDocument.defaultView)?e:window,o=e=>{let o=M(a.current).includes(e.animationName);if(e.target===r&&o&&(c("ANIMATION_END"),!s.current)){let e=r.style.animationFillMode;r.style.animationFillMode="forwards",t=n.setTimeout(()=>{"forwards"===r.style.animationFillMode&&(r.style.animationFillMode=e)})}},i=e=>{e.target===r&&(l.current=M(a.current))};return r.addEventListener("animationstart",i),r.addEventListener("animationcancel",o),r.addEventListener("animationend",o),()=>{n.clearTimeout(t),r.removeEventListener("animationstart",i),r.removeEventListener("animationcancel",o),r.removeEventListener("animationend",o)}}c("ANIMATION_END")},[r,c]),{isPresent:["mounted","unmountSuspended"].includes(u),ref:i.useCallback(e=>{a.current=e?getComputedStyle(e):null,o(e)},[])}}(t),o="function"==typeof n?n({present:r.isPresent}):i.Children.only(n),a=(0,u.s)(r.ref,function(e){var t,n;let r=null==(t=Object.getOwnPropertyDescriptor(e.props,"ref"))?void 0:t.get,o=r&&"isReactWarning"in r&&r.isReactWarning;return o?e.ref:(o=(r=null==(n=Object.getOwnPropertyDescriptor(e,"ref"))?void 0:n.get)&&"isReactWarning"in r&&r.isReactWarning)?e.props.ref:e.props.ref||e.ref}(o));return"function"==typeof n||r.isPresent?i.cloneElement(o,{ref:a}):null};function M(e){return(null==e?void 0:e.animationName)||"none"}D.displayName="Presence";var j=a[" useInsertionEffect ".trim().toString()]||L,k=(Symbol("RADIX:SYNC_STATE"),Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"})),O=i.forwardRef((e,t)=>(0,p.jsx)(h.span,{...e,ref:t,style:{...k,...e.style}}));O.displayName="VisuallyHidden";var A="ToastProvider",[F,_,I]=function(e){let t=e+"CollectionProvider",[n,r]=v(t),[o,a]=n(t,{collectionRef:{current:null},itemMap:new Map}),s=e=>{let{scope:t,children:n}=e,r=i.useRef(null),a=i.useRef(new Map).current;return(0,p.jsx)(o,{scope:t,itemMap:a,collectionRef:r,children:n})};s.displayName=t;let l=e+"CollectionSlot",c=(0,m.TL)(l),d=i.forwardRef((e,t)=>{let{scope:n,children:r}=e,o=a(l,n),i=(0,u.s)(t,o.collectionRef);return(0,p.jsx)(c,{ref:i,children:r})});d.displayName=l;let f=e+"CollectionItemSlot",w="data-radix-collection-item",y=(0,m.TL)(f),E=i.forwardRef((e,t)=>{let{scope:n,children:r,...o}=e,s=i.useRef(null),l=(0,u.s)(t,s),c=a(f,n);return i.useEffect(()=>(c.itemMap.set(s,{ref:s,...o}),()=>void c.itemMap.delete(s))),(0,p.jsx)(y,{...{[w]:""},ref:l,children:r})});return E.displayName=f,[{Provider:s,Slot:d,ItemSlot:E},function(t){let n=a(e+"CollectionConsumer",t);return i.useCallback(()=>{let e=n.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll("[".concat(w,"]")));return Array.from(n.itemMap.values()).sort((e,n)=>t.indexOf(e.ref.current)-t.indexOf(n.ref.current))},[n.collectionRef,n.itemMap])},r]}("Toast"),[W,K]=v("Toast",[I]),[U,$]=W(A),V=e=>{let{__scopeToast:t,label:n="Notification",duration:r=5e3,swipeDirection:o="right",swipeThreshold:a=50,children:s}=e,[l,u]=i.useState(null),[c,d]=i.useState(0),f=i.useRef(!1),v=i.useRef(!1);return n.trim()||console.error("Invalid prop `label` supplied to `".concat(A,"`. Expected non-empty `string`.")),(0,p.jsx)(F.Provider,{scope:t,children:(0,p.jsx)(U,{scope:t,label:n,duration:r,swipeDirection:o,swipeThreshold:a,toastCount:c,viewport:l,onViewportChange:u,onToastAdd:i.useCallback(()=>d(e=>e+1),[]),onToastRemove:i.useCallback(()=>d(e=>e-1),[]),isFocusedToastEscapeKeyDownRef:f,isClosePausedRef:v,children:s})})};V.displayName=A;var z="ToastViewport",B=["F8"],q="toast.viewportPause",G="toast.viewportResume",X=i.forwardRef((e,t)=>{let{__scopeToast:n,hotkey:r=B,label:o="Notifications ({hotkey})",...a}=e,s=$(z,n),l=_(n),c=i.useRef(null),d=i.useRef(null),f=i.useRef(null),v=i.useRef(null),m=(0,u.s)(t,v,s.onViewportChange),w=r.join("+").replace(/Key/g,"").replace(/Digit/g,""),y=s.toastCount>0;i.useEffect(()=>{let e=e=>{var t;0!==r.length&&r.every(t=>e[t]||e.code===t)&&(null==(t=v.current)||t.focus())};return document.addEventListener("keydown",e),()=>document.removeEventListener("keydown",e)},[r]),i.useEffect(()=>{let e=c.current,t=v.current;if(y&&e&&t){let n=()=>{if(!s.isClosePausedRef.current){let e=new CustomEvent(q);t.dispatchEvent(e),s.isClosePausedRef.current=!0}},r=()=>{if(s.isClosePausedRef.current){let e=new CustomEvent(G);t.dispatchEvent(e),s.isClosePausedRef.current=!1}},o=t=>{e.contains(t.relatedTarget)||r()},i=()=>{e.contains(document.activeElement)||r()};return e.addEventListener("focusin",n),e.addEventListener("focusout",o),e.addEventListener("pointermove",n),e.addEventListener("pointerleave",i),window.addEventListener("blur",n),window.addEventListener("focus",r),()=>{e.removeEventListener("focusin",n),e.removeEventListener("focusout",o),e.removeEventListener("pointermove",n),e.removeEventListener("pointerleave",i),window.removeEventListener("blur",n),window.removeEventListener("focus",r)}}},[y,s.isClosePausedRef]);let E=i.useCallback(e=>{let{tabbingDirection:t}=e,n=l().map(e=>{let n=e.ref.current,r=[n,...function(e){let t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{let t="INPUT"===e.tagName&&"hidden"===e.type;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}(n)];return"forwards"===t?r:r.reverse()});return("forwards"===t?n.reverse():n).flat()},[l]);return i.useEffect(()=>{let e=v.current;if(e){let t=t=>{let n=t.altKey||t.ctrlKey||t.metaKey;if("Tab"===t.key&&!n){var r,o,i;let n=document.activeElement,a=t.shiftKey;if(t.target===e&&a){null==(r=d.current)||r.focus();return}let s=E({tabbingDirection:a?"backwards":"forwards"}),l=s.findIndex(e=>e===n);ef(s.slice(l+1))?t.preventDefault():a?null==(o=d.current)||o.focus():null==(i=f.current)||i.focus()}};return e.addEventListener("keydown",t),()=>e.removeEventListener("keydown",t)}},[l,E]),(0,p.jsxs)(N,{ref:c,role:"region","aria-label":o.replace("{hotkey}",w),tabIndex:-1,style:{pointerEvents:y?void 0:"none"},children:[y&&(0,p.jsx)(H,{ref:d,onFocusFromOutsideViewport:()=>{ef(E({tabbingDirection:"forwards"}))}}),(0,p.jsx)(F.Slot,{scope:n,children:(0,p.jsx)(h.ol,{tabIndex:-1,...a,ref:m})}),y&&(0,p.jsx)(H,{ref:f,onFocusFromOutsideViewport:()=>{ef(E({tabbingDirection:"backwards"}))}})]})});X.displayName=z;var Y="ToastFocusProxy",H=i.forwardRef((e,t)=>{let{__scopeToast:n,onFocusFromOutsideViewport:r,...o}=e,i=$(Y,n);return(0,p.jsx)(O,{"aria-hidden":!0,tabIndex:0,...o,ref:t,style:{position:"fixed"},onFocus:e=>{var t;let n=e.relatedTarget;(null==(t=i.viewport)?void 0:t.contains(n))||r()}})});H.displayName=Y;var J="Toast",Q=i.forwardRef((e,t)=>{let{forceMount:n,open:r,defaultOpen:o,onOpenChange:a,...s}=e,[u,c]=function({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){let[o,a,s]=function({defaultProp:e,onChange:t}){let[n,r]=i.useState(e),o=i.useRef(n),a=i.useRef(t);return j(()=>{a.current=t},[t]),i.useEffect(()=>{o.current!==n&&(a.current?.(n),o.current=n)},[n,o]),[n,r,a]}({defaultProp:t,onChange:n}),l=void 0!==e,u=l?e:o;{let t=i.useRef(void 0!==e);i.useEffect(()=>{let e=t.current;if(e!==l){let t=l?"controlled":"uncontrolled";console.warn(`${r} is changing from ${e?"controlled":"uncontrolled"} to ${t}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`)}t.current=l},[l,r])}return[u,i.useCallback(t=>{if(l){let n="function"==typeof t?t(e):t;n!==e&&s.current?.(n)}else a(t)},[l,e,a,s])]}({prop:r,defaultProp:null==o||o,onChange:a,caller:J});return(0,p.jsx)(D,{present:n||u,children:(0,p.jsx)(et,{open:u,...s,ref:t,onClose:()=>c(!1),onPause:x(e.onPause),onResume:x(e.onResume),onSwipeStart:l(e.onSwipeStart,e=>{e.currentTarget.setAttribute("data-swipe","start")}),onSwipeMove:l(e.onSwipeMove,e=>{let{x:t,y:n}=e.detail.delta;e.currentTarget.setAttribute("data-swipe","move"),e.currentTarget.style.setProperty("--radix-toast-swipe-move-x","".concat(t,"px")),e.currentTarget.style.setProperty("--radix-toast-swipe-move-y","".concat(n,"px"))}),onSwipeCancel:l(e.onSwipeCancel,e=>{e.currentTarget.setAttribute("data-swipe","cancel"),e.currentTarget.style.removeProperty("--radix-toast-swipe-move-x"),e.currentTarget.style.removeProperty("--radix-toast-swipe-move-y"),e.currentTarget.style.removeProperty("--radix-toast-swipe-end-x"),e.currentTarget.style.removeProperty("--radix-toast-swipe-end-y")}),onSwipeEnd:l(e.onSwipeEnd,e=>{let{x:t,y:n}=e.detail.delta;e.currentTarget.setAttribute("data-swipe","end"),e.currentTarget.style.removeProperty("--radix-toast-swipe-move-x"),e.currentTarget.style.removeProperty("--radix-toast-swipe-move-y"),e.currentTarget.style.setProperty("--radix-toast-swipe-end-x","".concat(t,"px")),e.currentTarget.style.setProperty("--radix-toast-swipe-end-y","".concat(n,"px")),c(!1)})})})});Q.displayName=J;var[Z,ee]=W(J,{onClose(){}}),et=i.forwardRef((e,t)=>{let{__scopeToast:n,type:r="foreground",duration:o,open:a,onClose:c,onEscapeKeyDown:d,onPause:f,onResume:v,onSwipeStart:m,onSwipeMove:w,onSwipeCancel:y,onSwipeEnd:E,...b}=e,g=$(J,n),[T,N]=i.useState(null),R=(0,u.s)(t,e=>N(e)),P=i.useRef(null),L=i.useRef(null),S=o||g.duration,D=i.useRef(0),M=i.useRef(S),j=i.useRef(0),{onToastAdd:k,onToastRemove:O}=g,A=x(()=>{var e;(null==T?void 0:T.contains(document.activeElement))&&(null==(e=g.viewport)||e.focus()),c()}),_=i.useCallback(e=>{e&&e!==1/0&&(window.clearTimeout(j.current),D.current=new Date().getTime(),j.current=window.setTimeout(A,e))},[A]);i.useEffect(()=>{let e=g.viewport;if(e){let t=()=>{_(M.current),null==v||v()},n=()=>{let e=new Date().getTime()-D.current;M.current=M.current-e,window.clearTimeout(j.current),null==f||f()};return e.addEventListener(q,n),e.addEventListener(G,t),()=>{e.removeEventListener(q,n),e.removeEventListener(G,t)}}},[g.viewport,S,f,v,_]),i.useEffect(()=>{a&&!g.isClosePausedRef.current&&_(S)},[a,S,g.isClosePausedRef,_]),i.useEffect(()=>(k(),()=>O()),[k,O]);let I=i.useMemo(()=>T?function e(t){let n=[];return Array.from(t.childNodes).forEach(t=>{var r;if(t.nodeType===t.TEXT_NODE&&t.textContent&&n.push(t.textContent),(r=t).nodeType===r.ELEMENT_NODE){let r=t.ariaHidden||t.hidden||"none"===t.style.display,o=""===t.dataset.radixToastAnnounceExclude;if(!r)if(o){let e=t.dataset.radixToastAnnounceAlt;e&&n.push(e)}else n.push(...e(t))}}),n}(T):null,[T]);return g.viewport?(0,p.jsxs)(p.Fragment,{children:[I&&(0,p.jsx)(en,{__scopeToast:n,role:"status","aria-live":"foreground"===r?"assertive":"polite","aria-atomic":!0,children:I}),(0,p.jsx)(Z,{scope:n,onClose:A,children:s.createPortal((0,p.jsx)(F.ItemSlot,{scope:n,children:(0,p.jsx)(C,{asChild:!0,onEscapeKeyDown:l(d,()=>{g.isFocusedToastEscapeKeyDownRef.current||A(),g.isFocusedToastEscapeKeyDownRef.current=!1}),children:(0,p.jsx)(h.li,{role:"status","aria-live":"off","aria-atomic":!0,tabIndex:0,"data-state":a?"open":"closed","data-swipe-direction":g.swipeDirection,...b,ref:R,style:{userSelect:"none",touchAction:"none",...e.style},onKeyDown:l(e.onKeyDown,e=>{"Escape"===e.key&&(null==d||d(e.nativeEvent),e.nativeEvent.defaultPrevented||(g.isFocusedToastEscapeKeyDownRef.current=!0,A()))}),onPointerDown:l(e.onPointerDown,e=>{0===e.button&&(P.current={x:e.clientX,y:e.clientY})}),onPointerMove:l(e.onPointerMove,e=>{if(!P.current)return;let t=e.clientX-P.current.x,n=e.clientY-P.current.y,r=!!L.current,o=["left","right"].includes(g.swipeDirection),i=["left","up"].includes(g.swipeDirection)?Math.min:Math.max,a=o?i(0,t):0,s=o?0:i(0,n),l="touch"===e.pointerType?10:2,u={x:a,y:s},c={originalEvent:e,delta:u};r?(L.current=u,ec("toast.swipeMove",w,c,{discrete:!1})):ed(u,g.swipeDirection,l)?(L.current=u,ec("toast.swipeStart",m,c,{discrete:!1}),e.target.setPointerCapture(e.pointerId)):(Math.abs(t)>l||Math.abs(n)>l)&&(P.current=null)}),onPointerUp:l(e.onPointerUp,e=>{let t=L.current,n=e.target;if(n.hasPointerCapture(e.pointerId)&&n.releasePointerCapture(e.pointerId),L.current=null,P.current=null,t){let n=e.currentTarget,r={originalEvent:e,delta:t};ed(t,g.swipeDirection,g.swipeThreshold)?ec("toast.swipeEnd",E,r,{discrete:!0}):ec("toast.swipeCancel",y,r,{discrete:!0}),n.addEventListener("click",e=>e.preventDefault(),{once:!0})}})})})}),g.viewport)})]}):null}),en=e=>{let{__scopeToast:t,children:n,...r}=e,o=$(J,t),[a,s]=i.useState(!1),[l,u]=i.useState(!1);return function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:()=>{},t=x(e);L(()=>{let e=0,n=0;return e=window.requestAnimationFrame(()=>n=window.requestAnimationFrame(t)),()=>{window.cancelAnimationFrame(e),window.cancelAnimationFrame(n)}},[t])}(()=>s(!0)),i.useEffect(()=>{let e=window.setTimeout(()=>u(!0),1e3);return()=>window.clearTimeout(e)},[]),l?null:(0,p.jsx)(S,{asChild:!0,children:(0,p.jsx)(O,{...r,children:a&&(0,p.jsxs)(p.Fragment,{children:[o.label," ",n]})})})},er=i.forwardRef((e,t)=>{let{__scopeToast:n,...r}=e;return(0,p.jsx)(h.div,{...r,ref:t})});er.displayName="ToastTitle";var eo=i.forwardRef((e,t)=>{let{__scopeToast:n,...r}=e;return(0,p.jsx)(h.div,{...r,ref:t})});eo.displayName="ToastDescription";var ei="ToastAction",ea=i.forwardRef((e,t)=>{let{altText:n,...r}=e;return n.trim()?(0,p.jsx)(eu,{altText:n,asChild:!0,children:(0,p.jsx)(el,{...r,ref:t})}):(console.error("Invalid prop `altText` supplied to `".concat(ei,"`. Expected non-empty `string`.")),null)});ea.displayName=ei;var es="ToastClose",el=i.forwardRef((e,t)=>{let{__scopeToast:n,...r}=e,o=ee(es,n);return(0,p.jsx)(eu,{asChild:!0,children:(0,p.jsx)(h.button,{type:"button",...r,ref:t,onClick:l(e.onClick,o.onClose)})})});el.displayName=es;var eu=i.forwardRef((e,t)=>{let{__scopeToast:n,altText:r,...o}=e;return(0,p.jsx)(h.div,{"data-radix-toast-announce-exclude":"","data-radix-toast-announce-alt":r||void 0,...o,ref:t})});function ec(e,t,n,r){let{discrete:o}=r,i=n.originalEvent.currentTarget,a=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),o?b(i,a):i.dispatchEvent(a)}var ed=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,r=Math.abs(e.x),o=Math.abs(e.y),i=r>o;return"left"===t||"right"===t?i&&r>n:!i&&o>n};function ef(e){let t=document.activeElement;return e.some(e=>e===t||(e.focus(),document.activeElement!==t))}var ep=V,ev=X,em=Q,ew=er,ey=eo,eE=ea,eh=el},8489:e=>{e.exports={style:{fontFamily:"'Geist Mono', 'Geist Mono Fallback'",fontStyle:"normal"},className:"__className_9a8899",variable:"__variable_9a8899"}}}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/app/_not-found/page-c4ccdce3b6a32a2a.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{3632:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let l=r(5155),n=r(6395);function o(){return(0,l.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3868:(e,t,r)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return r(3632)}])},6395:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}}),r(8229);let l=r(5155);r(2115);let n={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};function o(e){let{status:t,message:r}=e;return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("title",{children:t+": "+r}),(0,l.jsx)("div",{style:n.error,children:(0,l.jsxs)("div",{children:[(0,l.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,l.jsx)("h1",{className:"next-error-h1",style:n.h1,children:t}),(0,l.jsx)("div",{style:n.desc,children:(0,l.jsx)("h2",{style:n.h2,children:r})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{var t=t=>e(e.s=t);e.O(0,[441,684,358],()=>t(3868)),_N_E=e.O()}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/app/layout-f4f75e10eba9ecd4.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{347:()=>{},2558:(e,t,s)=>{"use strict";s.d(t,{Toaster:()=>g});var r=s(5155),a=s(7481),o=s(2115),i=s(6205),n=s(2085),d=s(4416),u=s(9434);let l=i.Kq,c=o.forwardRef((e,t)=>{let{className:s,...a}=e;return(0,r.jsx)(i.LM,{ref:t,className:(0,u.cn)("fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",s),...a})});c.displayName=i.LM.displayName;let f=(0,n.F)("group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",{variants:{variant:{default:"border bg-background text-foreground",destructive:"destructive group border-destructive bg-destructive text-destructive-foreground"}},defaultVariants:{variant:"default"}}),p=o.forwardRef((e,t)=>{let{className:s,variant:a,...o}=e;return(0,r.jsx)(i.bL,{ref:t,className:(0,u.cn)(f({variant:a}),s),...o})});p.displayName=i.bL.displayName,o.forwardRef((e,t)=>{let{className:s,...a}=e;return(0,r.jsx)(i.rc,{ref:t,className:(0,u.cn)("inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",s),...a})}).displayName=i.rc.displayName;let m=o.forwardRef((e,t)=>{let{className:s,...a}=e;return(0,r.jsx)(i.bm,{ref:t,className:(0,u.cn)("absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",s),"toast-close":"",...a,children:(0,r.jsx)(d.A,{className:"h-4 w-4"})})});m.displayName=i.bm.displayName;let v=o.forwardRef((e,t)=>{let{className:s,...a}=e;return(0,r.jsx)(i.hE,{ref:t,className:(0,u.cn)("text-sm font-semibold",s),...a})});v.displayName=i.hE.displayName;let x=o.forwardRef((e,t)=>{let{className:s,...a}=e;return(0,r.jsx)(i.VY,{ref:t,className:(0,u.cn)("text-sm opacity-90",s),...a})});function g(){let{toasts:e}=(0,a.dj)();return(0,r.jsxs)(l,{children:[e.map(function(e){let{id:t,title:s,description:a,action:o,...i}=e;return(0,r.jsxs)(p,{...i,children:[(0,r.jsxs)("div",{className:"grid gap-1",children:[s&&(0,r.jsx)(v,{children:s}),a&&(0,r.jsx)(x,{children:a})]}),o,(0,r.jsx)(m,{})]},t)}),(0,r.jsx)(c,{})]})}x.displayName=i.VY.displayName},3211:(e,t,s)=>{Promise.resolve().then(s.t.bind(s,4147,23)),Promise.resolve().then(s.t.bind(s,8489,23)),Promise.resolve().then(s.t.bind(s,347,23)),Promise.resolve().then(s.bind(s,2558))},7481:(e,t,s)=>{"use strict";s.d(t,{dj:()=>f,oR:()=>c});var r=s(2115);let a=0,o=new Map,i=e=>{if(o.has(e))return;let t=setTimeout(()=>{o.delete(e),l({type:"REMOVE_TOAST",toastId:e})},1e6);o.set(e,t)},n=(e,t)=>{switch(t.type){case"ADD_TOAST":return{...e,toasts:[t.toast,...e.toasts].slice(0,1)};case"UPDATE_TOAST":return{...e,toasts:e.toasts.map(e=>e.id===t.toast.id?{...e,...t.toast}:e)};case"DISMISS_TOAST":{let{toastId:s}=t;return s?i(s):e.toasts.forEach(e=>{i(e.id)}),{...e,toasts:e.toasts.map(e=>e.id===s||void 0===s?{...e,open:!1}:e)}}case"REMOVE_TOAST":if(void 0===t.toastId)return{...e,toasts:[]};return{...e,toasts:e.toasts.filter(e=>e.id!==t.toastId)}}},d=[],u={toasts:[]};function l(e){u=n(u,e),d.forEach(e=>{e(u)})}function c(e){let{...t}=e,s=(a=(a+1)%Number.MAX_SAFE_INTEGER).toString(),r=()=>l({type:"DISMISS_TOAST",toastId:s});return l({type:"ADD_TOAST",toast:{...t,id:s,open:!0,onOpenChange:e=>{e||r()}}}),{id:s,dismiss:r,update:e=>l({type:"UPDATE_TOAST",toast:{...e,id:s}})}}function f(){let[e,t]=r.useState(u);return r.useEffect(()=>(d.push(t),()=>{let e=d.indexOf(t);e>-1&&d.splice(e,1)}),[e]),{...e,toast:c,dismiss:e=>l({type:"DISMISS_TOAST",toastId:e})}}},9434:(e,t,s)=>{"use strict";s.d(t,{cn:()=>o});var r=s(2596),a=s(9688);function o(){for(var e=arguments.length,t=Array(e),s=0;s{var t=t=>e(e.s=t);e.O(0,[896,455,869,441,684,358],()=>t(3211)),_N_E=e.O()}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/app/page-ef23a9b1d76fb793.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{829:(e,r,a)=>{"use strict";a.d(r,{default:()=>k});var t=a(5155),s=a(2115),l=a(9708),d=a(2085),o=a(9434);let i=(0,d.F)("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border border-input bg-background hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3",lg:"h-11 rounded-md px-8",icon:"h-10 w-10"}},defaultVariants:{variant:"default",size:"default"}}),n=s.forwardRef((e,r)=>{let{className:a,variant:s,size:d,asChild:n=!1,...c}=e,u=n?l.DX:"button";return(0,t.jsx)(u,{className:(0,o.cn)(i({variant:s,size:d,className:a})),ref:r,...c})});n.displayName="Button";let c=s.forwardRef((e,r)=>{let{className:a,...s}=e;return(0,t.jsx)("div",{ref:r,className:(0,o.cn)("rounded-lg border bg-card text-card-foreground shadow-sm",a),...s})});c.displayName="Card";let u=s.forwardRef((e,r)=>{let{className:a,...s}=e;return(0,t.jsx)("div",{ref:r,className:(0,o.cn)("flex flex-col space-y-1.5 p-6",a),...s})});u.displayName="CardHeader";let g=s.forwardRef((e,r)=>{let{className:a,...s}=e;return(0,t.jsx)("div",{ref:r,className:(0,o.cn)("text-2xl font-semibold leading-none tracking-tight",a),...s})});g.displayName="CardTitle";let m=s.forwardRef((e,r)=>{let{className:a,...s}=e;return(0,t.jsx)("div",{ref:r,className:(0,o.cn)("text-sm text-muted-foreground",a),...s})});m.displayName="CardDescription";let x=s.forwardRef((e,r)=>{let{className:a,...s}=e;return(0,t.jsx)("div",{ref:r,className:(0,o.cn)("p-6 pt-0",a),...s})});x.displayName="CardContent",s.forwardRef((e,r)=>{let{className:a,...s}=e;return(0,t.jsx)("div",{ref:r,className:(0,o.cn)("flex items-center p-6 pt-0",a),...s})}).displayName="CardFooter";let b=s.forwardRef((e,r)=>{let{className:a,type:s,...l}=e;return(0,t.jsx)("input",{type:s,className:(0,o.cn)("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",a),ref:r,...l})});b.displayName="Input";var f=a(7481),p=a(9946);let h=(0,p.A)("upload",[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]]),y=(0,p.A)("link",[["path",{d:"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",key:"1cjeqo"}],["path",{d:"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71",key:"19qd67"}]]),v=(0,p.A)("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);function k(){let[e,r]=(0,s.useState)(!1),[a,l]=(0,s.useState)(!1),[d,o]=(0,s.useState)(null),[i,p]=(0,s.useState)(null),k=(0,s.useRef)(null),N=(0,s.useCallback)(e=>({direct:e,markdown:"![Image](".concat(e,")"),html:'Image')}),[]),j=(0,s.useCallback)(async e=>{if(!e.type.startsWith("image/"))return void(0,f.oR)({title:"Invalid file type",description:"Please select an image file.",variant:"destructive"});if(e.size>0xa00000)return void(0,f.oR)({title:"File too large",description:"Image size must be less than 10MB.",variant:"destructive"});l(!0);try{let r=new FormData;r.append("image",e);let a=await fetch("/upload",{method:"POST",body:r});if(!a.ok){let e=await a.json();throw Error(e.error||"Upload failed")}let t=await a.json();o(t),p(N(t.url)),(0,f.oR)({title:"Upload successful",description:"Your image has been uploaded successfully."})}catch(e){console.error("Upload error:",e),(0,f.oR)({title:"Upload failed",description:e instanceof Error?e.message:"An unknown error occurred",variant:"destructive"})}finally{l(!1)}},[N]),w=(0,s.useCallback)(e=>{e.preventDefault(),r(!0)},[]),T=(0,s.useCallback)(e=>{e.preventDefault(),r(!1)},[]),C=(0,s.useCallback)(e=>{e.preventDefault(),r(!1);let a=Array.from(e.dataTransfer.files);a.length>0&&j(a[0])},[j]),S=(0,s.useCallback)(e=>{let r=e.target.files;r&&r.length>0&&j(r[0])},[j]),D=(0,s.useCallback)(e=>{var r;let a=null==(r=e.clipboardData)?void 0:r.items;if(a)for(let e=0;e{navigator.clipboard.writeText(e).then(()=>{(0,f.oR)({title:"Copied to clipboard",description:"".concat(r," link copied successfully.")})})},[]);return(0,s.useEffect)(()=>(document.addEventListener("paste",D),()=>{document.removeEventListener("paste",D)}),[D]),(0,t.jsxs)("div",{className:"w-full max-w-4xl mx-auto",children:[(0,t.jsx)(c,{className:"mb-6 shadow-lg border-0 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm",children:(0,t.jsx)(x,{className:"p-8",children:(0,t.jsxs)("div",{className:"border-2 border-dashed rounded-xl p-12 text-center transition-all duration-300 ".concat(e?"border-blue-500 bg-blue-50/50 dark:bg-blue-900/20 scale-105":"border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:bg-gray-50/50 dark:hover:bg-gray-700/50"),onDragOver:w,onDragLeave:T,onDrop:C,children:[(0,t.jsxs)("div",{className:"flex flex-col items-center",children:[(0,t.jsx)("div",{className:"p-4 rounded-full mb-6 transition-all duration-300 ".concat(e?"bg-blue-100 dark:bg-blue-900/40":"bg-gray-100 dark:bg-gray-700"),children:(0,t.jsx)(h,{className:"w-12 h-12 transition-colors ".concat(e?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500")})}),(0,t.jsx)("h3",{className:"text-xl font-semibold mb-2 text-gray-900 dark:text-white",children:e?"Drop your image here":"Upload your image"}),(0,t.jsx)("p",{className:"text-gray-600 dark:text-gray-300 mb-6 max-w-md",children:"Drag & drop, browse files, or paste from clipboard"}),(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row gap-4 items-center",children:[(0,t.jsx)(n,{onClick:()=>{var e;return null==(e=k.current)?void 0:e.click()},disabled:a,size:"lg",className:"bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 text-lg font-medium transition-all duration-200",children:a?"Uploading...":"Browse Files"}),(0,t.jsxs)("div",{className:"text-sm text-gray-500 dark:text-gray-400",children:["or press ",(0,t.jsx)("kbd",{className:"px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded text-xs font-mono",children:"Ctrl+V"})]})]}),(0,t.jsx)("div",{className:"mt-6 text-xs text-gray-500 dark:text-gray-400",children:"Maximum file size: 10MB • Supported formats: PNG, JPG, GIF, WebP"})]}),(0,t.jsx)(b,{ref:k,type:"file",accept:"image/*",onChange:S,className:"hidden"})]})})}),d&&i&&(0,t.jsxs)(c,{className:"shadow-lg border-0 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm",children:[(0,t.jsxs)(u,{className:"pb-4",children:[(0,t.jsxs)(g,{className:"flex items-center gap-3 text-xl",children:[(0,t.jsx)("div",{className:"p-2 bg-green-100 dark:bg-green-900/40 rounded-lg",children:(0,t.jsx)(y,{className:"w-5 h-5 text-green-600 dark:text-green-400"})}),"Upload Complete"]}),(0,t.jsx)(m,{className:"text-base",children:"Your image has been uploaded successfully. Choose your preferred format:"})]}),(0,t.jsxs)(x,{className:"space-y-6",children:[(0,t.jsxs)("div",{className:"grid gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("label",{className:"text-sm font-semibold text-gray-700 dark:text-gray-300",children:"Direct URL"}),(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(b,{readOnly:!0,value:i.direct,className:"flex-1 font-mono text-sm bg-gray-50 dark:bg-gray-900 border-gray-200 dark:border-gray-600"}),(0,t.jsx)(n,{variant:"outline",size:"sm",onClick:()=>A(i.direct,"Direct URL"),className:"shrink-0 hover:bg-blue-50 dark:hover:bg-blue-900/20",children:(0,t.jsx)(v,{className:"w-4 h-4"})})]})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("label",{className:"text-sm font-semibold text-gray-700 dark:text-gray-300",children:"Markdown"}),(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(b,{readOnly:!0,value:i.markdown,className:"flex-1 font-mono text-sm bg-gray-50 dark:bg-gray-900 border-gray-200 dark:border-gray-600"}),(0,t.jsx)(n,{variant:"outline",size:"sm",onClick:()=>A(i.markdown,"Markdown"),className:"shrink-0 hover:bg-blue-50 dark:hover:bg-blue-900/20",children:(0,t.jsx)(v,{className:"w-4 h-4"})})]})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("label",{className:"text-sm font-semibold text-gray-700 dark:text-gray-300",children:"HTML"}),(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(b,{readOnly:!0,value:i.html,className:"flex-1 font-mono text-sm bg-gray-50 dark:bg-gray-900 border-gray-200 dark:border-gray-600"}),(0,t.jsx)(n,{variant:"outline",size:"sm",onClick:()=>A(i.html,"HTML"),className:"shrink-0 hover:bg-blue-50 dark:hover:bg-blue-900/20",children:(0,t.jsx)(v,{className:"w-4 h-4"})})]})]})]}),(0,t.jsxs)("div",{className:"pt-4 border-t border-gray-200 dark:border-gray-600",children:[(0,t.jsx)("p",{className:"text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3",children:"Preview"}),(0,t.jsx)("div",{className:"bg-gray-50 dark:bg-gray-900 p-4 rounded-xl border border-gray-200 dark:border-gray-600",children:(0,t.jsx)("img",{src:d.url,alt:"Uploaded image preview",className:"max-w-full max-h-80 object-contain rounded-lg mx-auto shadow-sm"})})]})]})]})]})}},1183:(e,r,a)=>{Promise.resolve().then(a.bind(a,829))},7481:(e,r,a)=>{"use strict";a.d(r,{dj:()=>g,oR:()=>u});var t=a(2115);let s=0,l=new Map,d=e=>{if(l.has(e))return;let r=setTimeout(()=>{l.delete(e),c({type:"REMOVE_TOAST",toastId:e})},1e6);l.set(e,r)},o=(e,r)=>{switch(r.type){case"ADD_TOAST":return{...e,toasts:[r.toast,...e.toasts].slice(0,1)};case"UPDATE_TOAST":return{...e,toasts:e.toasts.map(e=>e.id===r.toast.id?{...e,...r.toast}:e)};case"DISMISS_TOAST":{let{toastId:a}=r;return a?d(a):e.toasts.forEach(e=>{d(e.id)}),{...e,toasts:e.toasts.map(e=>e.id===a||void 0===a?{...e,open:!1}:e)}}case"REMOVE_TOAST":if(void 0===r.toastId)return{...e,toasts:[]};return{...e,toasts:e.toasts.filter(e=>e.id!==r.toastId)}}},i=[],n={toasts:[]};function c(e){n=o(n,e),i.forEach(e=>{e(n)})}function u(e){let{...r}=e,a=(s=(s+1)%Number.MAX_SAFE_INTEGER).toString(),t=()=>c({type:"DISMISS_TOAST",toastId:a});return c({type:"ADD_TOAST",toast:{...r,id:a,open:!0,onOpenChange:e=>{e||t()}}}),{id:a,dismiss:t,update:e=>c({type:"UPDATE_TOAST",toast:{...e,id:a}})}}function g(){let[e,r]=t.useState(n);return t.useEffect(()=>(i.push(r),()=>{let e=i.indexOf(r);e>-1&&i.splice(e,1)}),[e]),{...e,toast:u,dismiss:e=>c({type:"DISMISS_TOAST",toastId:e})}}},9434:(e,r,a)=>{"use strict";a.d(r,{cn:()=>l});var t=a(2596),s=a(9688);function l(){for(var e=arguments.length,r=Array(e),a=0;a{var r=r=>e(e.s=r);e.O(0,[455,441,684,358],()=>r(1183)),_N_E=e.O()}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/main-app-3701f8484f32bf65.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{1112:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,894,23)),Promise.resolve().then(n.t.bind(n,4970,23)),Promise.resolve().then(n.t.bind(n,6614,23)),Promise.resolve().then(n.t.bind(n,6975,23)),Promise.resolve().then(n.t.bind(n,7555,23)),Promise.resolve().then(n.t.bind(n,4911,23)),Promise.resolve().then(n.t.bind(n,9665,23)),Promise.resolve().then(n.t.bind(n,1295,23))},9393:()=>{}},e=>{var s=s=>e(e.s=s);e.O(0,[441,684],()=>(s(5415),s(1112))),_N_E=e.O()}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/pages/_app-da15c11dea942c36.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[636],{326:(_,n,p)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return p(472)}])}},_=>{var n=n=>_(_.s=n);_.O(0,[593,792],()=>(n(326),n(4294))),_N_E=_.O()}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/pages/_error-cc3f077a18ea1793.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[731],{2164:(_,n,e)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return e(9341)}])}},_=>{var n=n=>_(_.s=n);_.O(0,[636,593,792],()=>n(2164)),_N_E=_.O()}]); -------------------------------------------------------------------------------- /public/_next/static/chunks/webpack-f029a09104d09cbc.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var i=t[o]={exports:{}},a=!0;try{e[o](i,i.exports,r),a=!1}finally{a&&delete t[o]}return i.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,i)=>{if(o){i=i||0;for(var a=e.length;a>0&&e[a-1][2]>i;a--)e[a]=e[a-1];e[a]=[o,n,i];return}for(var u=1/0,a=0;a=i)&&Object.keys(r.O).every(e=>r.O[e](o[c]))?o.splice(c--,1):(l=!1,i{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n||"object"==typeof o&&o&&(4&n&&o.__esModule||16&n&&"function"==typeof o.then))return o;var i=Object.create(null);r.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var u=2&n&&o;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach(e=>a[e]=()=>o[e]);return a.default=()=>o,r.d(i,a),i}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>{},r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="_N_E:";r.l=(o,n,i,a)=>{if(e[o])return void e[o].push(n);if(void 0!==i)for(var u,l,c=document.getElementsByTagName("script"),f=0;f{u.onerror=u.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=d.bind(null,u.onerror),u.onload=d.bind(null,u.onload),l&&document.head.appendChild(u)}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:e=>e},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("nextjs#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="/_next/",(()=>{var e={68:0,896:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n)if(n)o.push(n[2]);else if(/^(68|896)$/.test(t))e[t]=0;else{var i=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=i);var a=r.p+r.u(t),u=Error();r.l(a,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var i=o&&("load"===o.type?"missing":o.type),a=o&&o.target&&o.target.src;u.message="Loading chunk "+t+" failed.\n("+i+": "+a+")",u.name="ChunkLoadError",u.type=i,u.request=a,n[1](u)}},"chunk-"+t,t)}},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,i,[a,u,l]=o,c=0;if(a.some(t=>0!==e[t])){for(n in u)r.o(u,n)&&(r.m[n]=u[n]);if(l)var f=l(r)}for(t&&t(o);c:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-0{border-width:0}.border-2{border-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-destructive{border-color:hsl(var(--destructive))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-input{border-color:hsl(var(--input))}.bg-background{background-color:hsl(var(--background))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-blue-50\/50{background-color:rgb(239 246 255/.5)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity,1))}.bg-primary{background-color:hsl(var(--primary))}.bg-secondary{background-color:hsl(var(--secondary))}.bg-transparent{background-color:transparent}.bg-white\/80{background-color:rgb(255 255 255/.8)}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.from-slate-50{--tw-gradient-from:#f8fafc var(--tw-gradient-from-position);--tw-gradient-to:rgb(248 250 252/0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-gray-100{--tw-gradient-to:#f3f4f6 var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-4{padding-bottom:1rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-4{padding-top:1rem}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-foreground{color:hsl(var(--foreground))}.text-foreground\/50{color:hsl(var(--foreground)/.5)}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-90{opacity:.9}.shadow-lg{--tw-shadow:0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgb(0 0 0/0.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline{outline-style:solid}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.duration-200{animation-duration:.2s}.duration-300{animation-duration:.3s}.backdrop-blur-sm{-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.file\:border-0::file-selector-button{border-width:0}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.file\:text-foreground::file-selector-button{color:hsl(var(--foreground))}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.hover\:border-blue-400:hover{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-blue-50:hover{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive)/.9)}.hover\:bg-gray-50\/50:hover{background-color:rgb(249 250 251/.5)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary)/.9)}.hover\:bg-secondary:hover{background-color:hsl(var(--secondary))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary)/.8)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-blue-800:hover{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:underline:hover{text-decoration-line:underline}.focus\:opacity-100:focus{opacity:1}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color:hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}.group.destructive .group-\[\.destructive\]\:border-muted\/40{border-color:hsl(var(--muted)/.4)}.group.destructive .group-\[\.destructive\]\:text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.group.destructive .group-\[\.destructive\]\:hover\:border-destructive\/30:hover{border-color:hsl(var(--destructive)/.3)}.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover{background-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover{color:hsl(var(--destructive-foreground))}.group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover{--tw-text-opacity:1;color:rgb(254 242 242/var(--tw-text-opacity,1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus{--tw-ring-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(248 113 113/var(--tw-ring-opacity,1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus{--tw-ring-offset-color:#dc2626}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x:0px}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel],.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{--tw-translate-x:var(--radix-toast-swipe-end-x)}.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{--tw-translate-x:var(--radix-toast-swipe-move-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}.data-\[state\=open\]\:animate-in[data-state=open]{animation-name:enter;animation-duration:.15s;--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial}.data-\[state\=closed\]\:animate-out[data-state=closed],.data-\[swipe\=end\]\:animate-out[data-swipe=end]{animation-name:exit;animation-duration:.15s;--tw-exit-opacity:initial;--tw-exit-scale:initial;--tw-exit-rotate:initial;--tw-exit-translate-x:initial;--tw-exit-translate-y:initial}.data-\[state\=closed\]\:fade-out-80[data-state=closed]{--tw-exit-opacity:0.8}.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=open\]\:slide-in-from-top-full[data-state=open]{--tw-enter-translate-y:-100%}.dark\:border-gray-600:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity,1))}.dark\:bg-blue-900\/20:is(.dark *){background-color:rgb(30 58 138/.2)}.dark\:bg-blue-900\/40:is(.dark *){background-color:rgb(30 58 138/.4)}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.dark\:bg-gray-800\/80:is(.dark *){background-color:rgb(31 41 55/.8)}.dark\:bg-gray-900:is(.dark *){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity,1))}.dark\:bg-green-900\/40:is(.dark *){background-color:rgb(20 83 45/.4)}.dark\:from-gray-900:is(.dark *){--tw-gradient-from:#111827 var(--tw-gradient-from-position);--tw-gradient-to:rgb(17 24 39/0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.dark\:to-slate-800:is(.dark *){--tw-gradient-to:#1e293b var(--tw-gradient-to-position)}.dark\:text-blue-400:is(.dark *){--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.dark\:text-gray-500:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity,1))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:hover\:border-blue-500:hover:is(.dark *){--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.dark\:hover\:bg-blue-900\/20:hover:is(.dark *){background-color:rgb(30 58 138/.2)}.dark\:hover\:bg-gray-700\/50:hover:is(.dark *){background-color:rgb(55 65 81/.5)}.dark\:hover\:text-blue-300:hover:is(.dark *){--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}@media (min-width:640px){.sm\:bottom-0{bottom:0}.sm\:right-0{right:0}.sm\:top-auto{top:auto}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state=open]{--tw-enter-translate-y:100%}}@media (min-width:768px){.md\:max-w-\[420px\]{max-width:420px}.md\:text-5xl{font-size:3rem;line-height:1}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:1rem;height:1rem}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0} -------------------------------------------------------------------------------- /public/_next/static/j6hdw6hDLpPcTkUaTeY7T/_buildManifest.js: -------------------------------------------------------------------------------- 1 | self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:3,errorRate:1e-4,numBits:58,numHashes:14,bitArray:[1,1,0,e,0,e,e,r,e,e,r,e,e,e,r,e,r,r,e,r,r,r,e,r,r,r,r,r,e,r,e,e,e,e,r,e,e,r,e,e,e,r,e,r,e,r,r,e,e,e,r,r,e,e,e,r,e,e]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-cc3f077a18ea1793.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); -------------------------------------------------------------------------------- /public/_next/static/j6hdw6hDLpPcTkUaTeY7T/_ssgManifest.js: -------------------------------------------------------------------------------- 1 | self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB() -------------------------------------------------------------------------------- /public/_next/static/media/569ce4b8f30dc480-s.p.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/_next/static/media/569ce4b8f30dc480-s.p.woff2 -------------------------------------------------------------------------------- /public/_next/static/media/747892c23ea88013-s.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/_next/static/media/747892c23ea88013-s.woff2 -------------------------------------------------------------------------------- /public/_next/static/media/8d697b304b401681-s.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/_next/static/media/8d697b304b401681-s.woff2 -------------------------------------------------------------------------------- /public/_next/static/media/93f479601ee12b01-s.p.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/_next/static/media/93f479601ee12b01-s.p.woff2 -------------------------------------------------------------------------------- /public/_next/static/media/9610d9e46709d722-s.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/_next/static/media/9610d9e46709d722-s.woff2 -------------------------------------------------------------------------------- /public/_next/static/media/ba015fad6dcf6784-s.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/_next/static/media/ba015fad6dcf6784-s.woff2 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/public/favicon.ico -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | Discord Image Upload

      Discord Image

      Fast, secure image hosting for Discord

      View on GitHub

      Upload your image

      Drag & drop, browse files, or paste from clipboard

      or press Ctrl+V
      Maximum file size: 10MB • Supported formats: PNG, JPG, GIF, WebP
        -------------------------------------------------------------------------------- /public/index.txt: -------------------------------------------------------------------------------- 1 | 1:"$Sreact.fragment" 2 | 2:I[7555,[],""] 3 | 3:I[1295,[],""] 4 | 4:I[2558,["455","static/chunks/455-a269af0bea64e1d5.js","869","static/chunks/869-9c07f9cccedfb126.js","177","static/chunks/app/layout-f4f75e10eba9ecd4.js"],"Toaster"] 5 | 5:I[829,["455","static/chunks/455-a269af0bea64e1d5.js","974","static/chunks/app/page-ef23a9b1d76fb793.js"],"default"] 6 | 6:I[9665,[],"OutletBoundary"] 7 | 9:I[4911,[],"AsyncMetadataOutlet"] 8 | b:I[9665,[],"ViewportBoundary"] 9 | d:I[9665,[],"MetadataBoundary"] 10 | f:I[6614,[],""] 11 | :HL["/_next/static/media/569ce4b8f30dc480-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] 12 | :HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] 13 | :HL["/_next/static/css/afe69862c9b626f3.css","style"] 14 | 0:{"P":null,"b":"j6hdw6hDLpPcTkUaTeY7T","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/afe69862c9b626f3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","div",null,{"className":"min-h-screen bg-gradient-to-br from-slate-50 to-gray-100 dark:from-gray-900 dark:to-slate-800","children":["$","div",null,{"className":"container mx-auto px-4 py-8 min-h-screen flex flex-col","children":[["$","header",null,{"className":"text-center mb-8","children":[["$","h1",null,{"className":"text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-2","children":"Discord Image"}],["$","p",null,{"className":"text-lg text-gray-600 dark:text-gray-300 mb-4","children":"Fast, secure image hosting for Discord"}],["$","a",null,{"href":"https://github.com/missuo/discord-image","target":"_blank","rel":"noopener noreferrer","className":"inline-flex items-center gap-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 transition-colors","children":[["$","svg",null,{"className":"w-4 h-4","fill":"currentColor","viewBox":"0 0 20 20","children":["$","path",null,{"fillRule":"evenodd","d":"M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z","clipRule":"evenodd"}]}],"View on GitHub"]}]]}],["$","main",null,{"className":"flex-1 flex items-center justify-center","children":["$","$L5",null,{}]}]]}]}],null,["$","$L6",null,{"children":["$L7","$L8",["$","$L9",null,{"promise":"$@a"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,["$","$1","Nr05oIQyJ6CPV-v_2-EE_v",{"children":[["$","$Lb",null,{"children":"$Lc"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$Ld",null,{"children":"$Le"}]]}],false]],"m":"$undefined","G":["$f","$undefined"],"s":false,"S":true} 15 | 10:"$Sreact.suspense" 16 | 11:I[4911,[],"AsyncMetadata"] 17 | e:["$","div",null,{"hidden":true,"children":["$","$10",null,{"fallback":null,"children":["$","$L11",null,{"promise":"$@12"}]}]}] 18 | 8:null 19 | c:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] 20 | 7:null 21 | a:{"metadata":[["$","title","0",{"children":"Discord Image Upload"}],["$","meta","1",{"name":"description","content":"Upload your images to Discord with drag & drop and clipboard support"}],["$","link","2",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"512x512"}]],"error":null,"digest":"$undefined"} 22 | 12:{"metadata":"$a:metadata","error":null,"digest":"$undefined"} 23 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshot/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/missuo/discord-image/99989eee03f23c53fcd558eedc7cd78996d8a06e/screenshot/image.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Discord Image Upload 8 | 12 | 36 | 37 | 38 |
        39 |

        Discord Image

        40 |

        Upload your image to Discord

        41 |
        42 |
        43 | 44 | 45 |
        46 | 49 | 50 | 56 | 60 |
        61 | 62 | 63 | 64 | 65 | 124 | 125 | 126 | 127 | --------------------------------------------------------------------------------