Qr code working
Browse files- .claude/settings.local.json +15 -0
- app/api/huggingface/route.js +91 -0
- app/api/proxy-image/route.js +44 -0
- app/globals.css +117 -12
- app/layout.tsx +10 -4
- app/page.tsx +3 -63
- components.json +22 -0
- components/HuggingFaceQRGenerator.js +281 -0
- components/HuggingFaceQRGenerator.tsx +250 -0
- components/QRCodeWithLogo.js +70 -0
- components/QRCodeWithLogo.tsx +79 -0
- components/ui/avatar.tsx +53 -0
- components/ui/badge.tsx +46 -0
- components/ui/button.tsx +60 -0
- components/ui/card.tsx +92 -0
- components/ui/input.tsx +21 -0
- components/ui/label.tsx +20 -0
- lib/huggingface.js +132 -0
- lib/huggingface.ts +141 -0
- lib/utils.ts +6 -0
- package-lock.json +386 -23
- package.json +20 -6
- styles/qr-generator.css +584 -0
- types/html-to-image.d.ts +17 -0
- types/qr-code-styling.d.ts +46 -0
.claude/settings.local.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"permissions": {
|
| 3 |
+
"allow": [
|
| 4 |
+
"Bash(npm install:*)",
|
| 5 |
+
"Bash(curl:*)",
|
| 6 |
+
"Bash(node -e:*)",
|
| 7 |
+
"Bash(npx shadcn@latest init -y)",
|
| 8 |
+
"Bash(npx shadcn@latest init:*)",
|
| 9 |
+
"Bash(npx shadcn@latest add:*)",
|
| 10 |
+
"Bash(npm run build:*)"
|
| 11 |
+
],
|
| 12 |
+
"deny": [],
|
| 13 |
+
"ask": []
|
| 14 |
+
}
|
| 15 |
+
}
|
app/api/huggingface/route.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextResponse } from 'next/server';
|
| 2 |
+
import axios from 'axios';
|
| 3 |
+
|
| 4 |
+
export async function POST(request) {
|
| 5 |
+
try {
|
| 6 |
+
const { username, resourceType, resourceName } = await request.json();
|
| 7 |
+
|
| 8 |
+
if (!username) {
|
| 9 |
+
return NextResponse.json(
|
| 10 |
+
{ error: 'Username is required' },
|
| 11 |
+
{ status: 400 }
|
| 12 |
+
);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
try {
|
| 16 |
+
// Fetch the profile page HTML to extract the real avatar URL
|
| 17 |
+
const response = await axios.get(
|
| 18 |
+
`https://huggingface.co/${username}`,
|
| 19 |
+
{
|
| 20 |
+
headers: {
|
| 21 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
);
|
| 25 |
+
|
| 26 |
+
const html = response.data;
|
| 27 |
+
|
| 28 |
+
// Extract the CDN avatar URL from the HTML
|
| 29 |
+
// Look for pattern: https://cdn-avatars.huggingface.co/...
|
| 30 |
+
const avatarMatch = html.match(/https:\/\/cdn-avatars\.huggingface\.co\/[^"'\s]+\.(png|jpg|jpeg|webp)/);
|
| 31 |
+
|
| 32 |
+
let avatarUrl = null;
|
| 33 |
+
if (avatarMatch && avatarMatch[0]) {
|
| 34 |
+
avatarUrl = avatarMatch[0];
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
// If no CDN avatar found, try to find any avatar image
|
| 38 |
+
if (!avatarUrl) {
|
| 39 |
+
// Try to find organization/user avatar in meta tags or other places
|
| 40 |
+
const metaAvatarMatch = html.match(/<meta[^>]+property="og:image"[^>]+content="([^"]+)"/);
|
| 41 |
+
if (metaAvatarMatch && metaAvatarMatch[1]) {
|
| 42 |
+
avatarUrl = metaAvatarMatch[1];
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// Fallback to a default avatar if none found
|
| 47 |
+
if (!avatarUrl) {
|
| 48 |
+
// Use Hugging Face's default avatar placeholder
|
| 49 |
+
avatarUrl = `https://huggingface.co/front/assets/huggingface_logo-noborder.svg`;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// Extract full name if available from the page
|
| 53 |
+
let fullName = username;
|
| 54 |
+
const nameMatch = html.match(/<h1[^>]*>([^<]+)<\/h1>/);
|
| 55 |
+
if (nameMatch && nameMatch[1]) {
|
| 56 |
+
fullName = nameMatch[1].trim();
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
return NextResponse.json({
|
| 60 |
+
username: username,
|
| 61 |
+
fullName: fullName,
|
| 62 |
+
avatarUrl: avatarUrl,
|
| 63 |
+
profileUrl: `https://huggingface.co/${username}`,
|
| 64 |
+
type: 'user',
|
| 65 |
+
resourceType: resourceType || null,
|
| 66 |
+
resourceName: resourceName || null
|
| 67 |
+
});
|
| 68 |
+
|
| 69 |
+
} catch (fetchError) {
|
| 70 |
+
console.log('Error fetching profile page:', fetchError.message);
|
| 71 |
+
|
| 72 |
+
// Fallback response with default avatar
|
| 73 |
+
return NextResponse.json({
|
| 74 |
+
username: username,
|
| 75 |
+
fullName: username,
|
| 76 |
+
avatarUrl: `https://huggingface.co/front/assets/huggingface_logo-noborder.svg`,
|
| 77 |
+
profileUrl: `https://huggingface.co/${username}`,
|
| 78 |
+
type: 'user',
|
| 79 |
+
resourceType: resourceType || null,
|
| 80 |
+
resourceName: resourceName || null
|
| 81 |
+
});
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
} catch (error) {
|
| 85 |
+
console.error('Error in HuggingFace API route:', error);
|
| 86 |
+
return NextResponse.json(
|
| 87 |
+
{ error: 'Failed to fetch profile data' },
|
| 88 |
+
{ status: 500 }
|
| 89 |
+
);
|
| 90 |
+
}
|
| 91 |
+
}
|
app/api/proxy-image/route.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextResponse } from 'next/server';
|
| 2 |
+
import axios from 'axios';
|
| 3 |
+
|
| 4 |
+
export async function GET(request) {
|
| 5 |
+
try {
|
| 6 |
+
const { searchParams } = new URL(request.url);
|
| 7 |
+
const imageUrl = searchParams.get('url');
|
| 8 |
+
|
| 9 |
+
if (!imageUrl) {
|
| 10 |
+
return NextResponse.json(
|
| 11 |
+
{ error: 'Image URL is required' },
|
| 12 |
+
{ status: 400 }
|
| 13 |
+
);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// Fetch the image
|
| 17 |
+
const response = await axios.get(imageUrl, {
|
| 18 |
+
responseType: 'arraybuffer',
|
| 19 |
+
headers: {
|
| 20 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
| 21 |
+
}
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
// Get content type from response
|
| 25 |
+
const contentType = response.headers['content-type'] || 'image/png';
|
| 26 |
+
|
| 27 |
+
// Return the image with proper headers
|
| 28 |
+
return new NextResponse(response.data, {
|
| 29 |
+
status: 200,
|
| 30 |
+
headers: {
|
| 31 |
+
'Content-Type': contentType,
|
| 32 |
+
'Cache-Control': 'public, max-age=3600',
|
| 33 |
+
'Access-Control-Allow-Origin': '*'
|
| 34 |
+
}
|
| 35 |
+
});
|
| 36 |
+
|
| 37 |
+
} catch (error) {
|
| 38 |
+
console.error('Error proxying image:', error);
|
| 39 |
+
return NextResponse.json(
|
| 40 |
+
{ error: 'Failed to fetch image' },
|
| 41 |
+
{ status: 500 }
|
| 42 |
+
);
|
| 43 |
+
}
|
| 44 |
+
}
|
app/globals.css
CHANGED
|
@@ -1,26 +1,131 @@
|
|
| 1 |
@import "tailwindcss";
|
| 2 |
|
| 3 |
-
|
| 4 |
-
--background: #ffffff;
|
| 5 |
-
--foreground: #171717;
|
| 6 |
-
}
|
| 7 |
|
| 8 |
@theme inline {
|
| 9 |
--color-background: var(--background);
|
| 10 |
--color-foreground: var(--foreground);
|
| 11 |
--font-sans: var(--font-geist-sans);
|
| 12 |
--font-mono: var(--font-geist-mono);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
-
|
| 16 |
-
:
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
}
|
| 20 |
}
|
| 21 |
|
| 22 |
body {
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
|
|
|
| 1 |
@import "tailwindcss";
|
| 2 |
|
| 3 |
+
@custom-variant dark (&:is(.dark *));
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
@theme inline {
|
| 6 |
--color-background: var(--background);
|
| 7 |
--color-foreground: var(--foreground);
|
| 8 |
--font-sans: var(--font-geist-sans);
|
| 9 |
--font-mono: var(--font-geist-mono);
|
| 10 |
+
--color-sidebar-ring: var(--sidebar-ring);
|
| 11 |
+
--color-sidebar-border: var(--sidebar-border);
|
| 12 |
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
| 13 |
+
--color-sidebar-accent: var(--sidebar-accent);
|
| 14 |
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
| 15 |
+
--color-sidebar-primary: var(--sidebar-primary);
|
| 16 |
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
| 17 |
+
--color-sidebar: var(--sidebar);
|
| 18 |
+
--color-chart-5: var(--chart-5);
|
| 19 |
+
--color-chart-4: var(--chart-4);
|
| 20 |
+
--color-chart-3: var(--chart-3);
|
| 21 |
+
--color-chart-2: var(--chart-2);
|
| 22 |
+
--color-chart-1: var(--chart-1);
|
| 23 |
+
--color-ring: var(--ring);
|
| 24 |
+
--color-input: var(--input);
|
| 25 |
+
--color-border: var(--border);
|
| 26 |
+
--color-destructive: var(--destructive);
|
| 27 |
+
--color-accent-foreground: var(--accent-foreground);
|
| 28 |
+
--color-accent: var(--accent);
|
| 29 |
+
--color-muted-foreground: var(--muted-foreground);
|
| 30 |
+
--color-muted: var(--muted);
|
| 31 |
+
--color-secondary-foreground: var(--secondary-foreground);
|
| 32 |
+
--color-secondary: var(--secondary);
|
| 33 |
+
--color-primary-foreground: var(--primary-foreground);
|
| 34 |
+
--color-primary: var(--primary);
|
| 35 |
+
--color-popover-foreground: var(--popover-foreground);
|
| 36 |
+
--color-popover: var(--popover);
|
| 37 |
+
--color-card-foreground: var(--card-foreground);
|
| 38 |
+
--color-card: var(--card);
|
| 39 |
+
--radius-sm: calc(var(--radius) - 4px);
|
| 40 |
+
--radius-md: calc(var(--radius) - 2px);
|
| 41 |
+
--radius-lg: var(--radius);
|
| 42 |
+
--radius-xl: calc(var(--radius) + 4px);
|
| 43 |
}
|
| 44 |
|
| 45 |
+
* {
|
| 46 |
+
margin: 0;
|
| 47 |
+
padding: 0;
|
| 48 |
+
box-sizing: border-box;
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
body {
|
| 52 |
+
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
:root {
|
| 56 |
+
--radius: 0.625rem;
|
| 57 |
+
--background: oklch(1 0 0);
|
| 58 |
+
--foreground: oklch(0.145 0 0);
|
| 59 |
+
--card: oklch(1 0 0);
|
| 60 |
+
--card-foreground: oklch(0.145 0 0);
|
| 61 |
+
--popover: oklch(1 0 0);
|
| 62 |
+
--popover-foreground: oklch(0.145 0 0);
|
| 63 |
+
--primary: oklch(0.205 0 0);
|
| 64 |
+
--primary-foreground: oklch(0.985 0 0);
|
| 65 |
+
--secondary: oklch(0.97 0 0);
|
| 66 |
+
--secondary-foreground: oklch(0.205 0 0);
|
| 67 |
+
--muted: oklch(0.97 0 0);
|
| 68 |
+
--muted-foreground: oklch(0.556 0 0);
|
| 69 |
+
--accent: oklch(0.97 0 0);
|
| 70 |
+
--accent-foreground: oklch(0.205 0 0);
|
| 71 |
+
--destructive: oklch(0.577 0.245 27.325);
|
| 72 |
+
--border: oklch(0.922 0 0);
|
| 73 |
+
--input: oklch(0.922 0 0);
|
| 74 |
+
--ring: oklch(0.708 0 0);
|
| 75 |
+
--chart-1: oklch(0.646 0.222 41.116);
|
| 76 |
+
--chart-2: oklch(0.6 0.118 184.704);
|
| 77 |
+
--chart-3: oklch(0.398 0.07 227.392);
|
| 78 |
+
--chart-4: oklch(0.828 0.189 84.429);
|
| 79 |
+
--chart-5: oklch(0.769 0.188 70.08);
|
| 80 |
+
--sidebar: oklch(0.985 0 0);
|
| 81 |
+
--sidebar-foreground: oklch(0.145 0 0);
|
| 82 |
+
--sidebar-primary: oklch(0.205 0 0);
|
| 83 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 84 |
+
--sidebar-accent: oklch(0.97 0 0);
|
| 85 |
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
| 86 |
+
--sidebar-border: oklch(0.922 0 0);
|
| 87 |
+
--sidebar-ring: oklch(0.708 0 0);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.dark {
|
| 91 |
+
--background: oklch(0.145 0 0);
|
| 92 |
+
--foreground: oklch(0.985 0 0);
|
| 93 |
+
--card: oklch(0.205 0 0);
|
| 94 |
+
--card-foreground: oklch(0.985 0 0);
|
| 95 |
+
--popover: oklch(0.205 0 0);
|
| 96 |
+
--popover-foreground: oklch(0.985 0 0);
|
| 97 |
+
--primary: oklch(0.922 0 0);
|
| 98 |
+
--primary-foreground: oklch(0.205 0 0);
|
| 99 |
+
--secondary: oklch(0.269 0 0);
|
| 100 |
+
--secondary-foreground: oklch(0.985 0 0);
|
| 101 |
+
--muted: oklch(0.269 0 0);
|
| 102 |
+
--muted-foreground: oklch(0.708 0 0);
|
| 103 |
+
--accent: oklch(0.269 0 0);
|
| 104 |
+
--accent-foreground: oklch(0.985 0 0);
|
| 105 |
+
--destructive: oklch(0.704 0.191 22.216);
|
| 106 |
+
--border: oklch(1 0 0 / 10%);
|
| 107 |
+
--input: oklch(1 0 0 / 15%);
|
| 108 |
+
--ring: oklch(0.556 0 0);
|
| 109 |
+
--chart-1: oklch(0.488 0.243 264.376);
|
| 110 |
+
--chart-2: oklch(0.696 0.17 162.48);
|
| 111 |
+
--chart-3: oklch(0.769 0.188 70.08);
|
| 112 |
+
--chart-4: oklch(0.627 0.265 303.9);
|
| 113 |
+
--chart-5: oklch(0.645 0.246 16.439);
|
| 114 |
+
--sidebar: oklch(0.205 0 0);
|
| 115 |
+
--sidebar-foreground: oklch(0.985 0 0);
|
| 116 |
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
| 117 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 118 |
+
--sidebar-accent: oklch(0.269 0 0);
|
| 119 |
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
| 120 |
+
--sidebar-border: oklch(1 0 0 / 10%);
|
| 121 |
+
--sidebar-ring: oklch(0.556 0 0);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
@layer base {
|
| 125 |
+
* {
|
| 126 |
+
@apply border-border outline-ring/50;
|
| 127 |
+
}
|
| 128 |
+
body {
|
| 129 |
+
@apply bg-background text-foreground;
|
| 130 |
+
}
|
| 131 |
}
|
app/layout.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import type { Metadata } from "next";
|
| 2 |
-
import { Geist, Geist_Mono } from "next/font/google";
|
| 3 |
import "./globals.css";
|
|
|
|
| 4 |
|
| 5 |
const geistSans = Geist({
|
| 6 |
variable: "--font-geist-sans",
|
|
@@ -12,9 +13,14 @@ const geistMono = Geist_Mono({
|
|
| 12 |
subsets: ["latin"],
|
| 13 |
});
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
export const metadata: Metadata = {
|
| 16 |
-
title: "
|
| 17 |
-
description: "
|
| 18 |
};
|
| 19 |
|
| 20 |
export default function RootLayout({
|
|
@@ -25,7 +31,7 @@ export default function RootLayout({
|
|
| 25 |
return (
|
| 26 |
<html lang="en">
|
| 27 |
<body
|
| 28 |
-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
| 29 |
>
|
| 30 |
{children}
|
| 31 |
</body>
|
|
|
|
| 1 |
import type { Metadata } from "next";
|
| 2 |
+
import { Geist, Geist_Mono, Inter } from "next/font/google";
|
| 3 |
import "./globals.css";
|
| 4 |
+
import "../styles/qr-generator.css";
|
| 5 |
|
| 6 |
const geistSans = Geist({
|
| 7 |
variable: "--font-geist-sans",
|
|
|
|
| 13 |
subsets: ["latin"],
|
| 14 |
});
|
| 15 |
|
| 16 |
+
const inter = Inter({
|
| 17 |
+
variable: "--font-inter",
|
| 18 |
+
subsets: ["latin"],
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
export const metadata: Metadata = {
|
| 22 |
+
title: "Hugging Face QR Code Generator",
|
| 23 |
+
description: "Generate beautiful QR codes for Hugging Face profiles with embedded avatars",
|
| 24 |
};
|
| 25 |
|
| 26 |
export default function RootLayout({
|
|
|
|
| 31 |
return (
|
| 32 |
<html lang="en">
|
| 33 |
<body
|
| 34 |
+
className={`${geistSans.variable} ${geistMono.variable} ${inter.variable} antialiased`}
|
| 35 |
>
|
| 36 |
{children}
|
| 37 |
</body>
|
app/page.tsx
CHANGED
|
@@ -1,65 +1,5 @@
|
|
| 1 |
-
import
|
| 2 |
|
| 3 |
export default function Home() {
|
| 4 |
-
return
|
| 5 |
-
|
| 6 |
-
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
| 7 |
-
<Image
|
| 8 |
-
className="dark:invert"
|
| 9 |
-
src="/next.svg"
|
| 10 |
-
alt="Next.js logo"
|
| 11 |
-
width={100}
|
| 12 |
-
height={20}
|
| 13 |
-
priority
|
| 14 |
-
/>
|
| 15 |
-
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
| 16 |
-
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
| 17 |
-
To get started, edit the page.tsx file.
|
| 18 |
-
</h1>
|
| 19 |
-
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
| 20 |
-
Looking for a starting point or more instructions? Head over to{" "}
|
| 21 |
-
<a
|
| 22 |
-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 23 |
-
className="font-medium text-zinc-950 dark:text-zinc-50"
|
| 24 |
-
>
|
| 25 |
-
Templates
|
| 26 |
-
</a>{" "}
|
| 27 |
-
or the{" "}
|
| 28 |
-
<a
|
| 29 |
-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 30 |
-
className="font-medium text-zinc-950 dark:text-zinc-50"
|
| 31 |
-
>
|
| 32 |
-
Learning
|
| 33 |
-
</a>{" "}
|
| 34 |
-
center.
|
| 35 |
-
</p>
|
| 36 |
-
</div>
|
| 37 |
-
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
| 38 |
-
<a
|
| 39 |
-
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
| 40 |
-
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 41 |
-
target="_blank"
|
| 42 |
-
rel="noopener noreferrer"
|
| 43 |
-
>
|
| 44 |
-
<Image
|
| 45 |
-
className="dark:invert"
|
| 46 |
-
src="/vercel.svg"
|
| 47 |
-
alt="Vercel logomark"
|
| 48 |
-
width={16}
|
| 49 |
-
height={16}
|
| 50 |
-
/>
|
| 51 |
-
Deploy Now
|
| 52 |
-
</a>
|
| 53 |
-
<a
|
| 54 |
-
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
| 55 |
-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 56 |
-
target="_blank"
|
| 57 |
-
rel="noopener noreferrer"
|
| 58 |
-
>
|
| 59 |
-
Documentation
|
| 60 |
-
</a>
|
| 61 |
-
</div>
|
| 62 |
-
</main>
|
| 63 |
-
</div>
|
| 64 |
-
);
|
| 65 |
-
}
|
|
|
|
| 1 |
+
import HuggingFaceQRGenerator from '../components/HuggingFaceQRGenerator.tsx';
|
| 2 |
|
| 3 |
export default function Home() {
|
| 4 |
+
return <HuggingFaceQRGenerator />;
|
| 5 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
+
"style": "new-york",
|
| 4 |
+
"rsc": true,
|
| 5 |
+
"tsx": true,
|
| 6 |
+
"tailwind": {
|
| 7 |
+
"config": "",
|
| 8 |
+
"css": "app/globals.css",
|
| 9 |
+
"baseColor": "neutral",
|
| 10 |
+
"cssVariables": true,
|
| 11 |
+
"prefix": ""
|
| 12 |
+
},
|
| 13 |
+
"iconLibrary": "lucide",
|
| 14 |
+
"aliases": {
|
| 15 |
+
"components": "@/components",
|
| 16 |
+
"utils": "@/lib/utils",
|
| 17 |
+
"ui": "@/components/ui",
|
| 18 |
+
"lib": "@/lib",
|
| 19 |
+
"hooks": "@/hooks"
|
| 20 |
+
},
|
| 21 |
+
"registries": {}
|
| 22 |
+
}
|
components/HuggingFaceQRGenerator.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import React, { useState } from 'react';
|
| 4 |
+
import { parseHuggingFaceUrl } from '../lib/huggingface';
|
| 5 |
+
import QRCodeWithLogo from './QRCodeWithLogo';
|
| 6 |
+
import { saveAs } from 'file-saver';
|
| 7 |
+
import * as htmlToImage from 'html-to-image';
|
| 8 |
+
|
| 9 |
+
const HuggingFaceQRGenerator = () => {
|
| 10 |
+
const [inputUrl, setInputUrl] = useState('');
|
| 11 |
+
const [profileData, setProfileData] = useState(null);
|
| 12 |
+
const [loading, setLoading] = useState(false);
|
| 13 |
+
const [error, setError] = useState('');
|
| 14 |
+
const [qrCodeInstance, setQrCodeInstance] = useState(null);
|
| 15 |
+
|
| 16 |
+
const handleGenerate = async () => {
|
| 17 |
+
setError('');
|
| 18 |
+
setLoading(true);
|
| 19 |
+
|
| 20 |
+
try {
|
| 21 |
+
// Parse the URL to extract username and resource info
|
| 22 |
+
const parsed = parseHuggingFaceUrl(inputUrl);
|
| 23 |
+
|
| 24 |
+
// Fetch profile data from our API
|
| 25 |
+
const response = await fetch('/api/huggingface', {
|
| 26 |
+
method: 'POST',
|
| 27 |
+
headers: {
|
| 28 |
+
'Content-Type': 'application/json',
|
| 29 |
+
},
|
| 30 |
+
body: JSON.stringify({
|
| 31 |
+
username: parsed.username,
|
| 32 |
+
resourceType: parsed.resourceType,
|
| 33 |
+
resourceName: parsed.resourceName
|
| 34 |
+
}),
|
| 35 |
+
});
|
| 36 |
+
|
| 37 |
+
if (!response.ok) {
|
| 38 |
+
throw new Error('Failed to fetch profile data');
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
const data = await response.json();
|
| 42 |
+
|
| 43 |
+
// Use proxy for the avatar to avoid CORS issues
|
| 44 |
+
const proxiedAvatarUrl = `/api/proxy-image?url=${encodeURIComponent(data.avatarUrl)}`;
|
| 45 |
+
|
| 46 |
+
setProfileData({
|
| 47 |
+
...data,
|
| 48 |
+
avatarUrl: proxiedAvatarUrl,
|
| 49 |
+
originalAvatarUrl: data.avatarUrl,
|
| 50 |
+
qrValue: parsed.profileUrl
|
| 51 |
+
});
|
| 52 |
+
} catch (err) {
|
| 53 |
+
setError(err.message || 'Invalid URL or username');
|
| 54 |
+
setProfileData(null);
|
| 55 |
+
} finally {
|
| 56 |
+
setLoading(false);
|
| 57 |
+
}
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
const handleDownload = async (format = 'png') => {
|
| 61 |
+
if (!profileData) return;
|
| 62 |
+
|
| 63 |
+
try {
|
| 64 |
+
const cardElement = document.getElementById('qr-card');
|
| 65 |
+
|
| 66 |
+
if (format === 'png' || format === 'card') {
|
| 67 |
+
// Download the entire card with user info
|
| 68 |
+
const dataUrl = await htmlToImage.toPng(cardElement, {
|
| 69 |
+
quality: 1.0,
|
| 70 |
+
backgroundColor: '#ffffff',
|
| 71 |
+
pixelRatio: 2, // Higher quality
|
| 72 |
+
style: {
|
| 73 |
+
margin: '0',
|
| 74 |
+
padding: '20px',
|
| 75 |
+
borderRadius: '16px'
|
| 76 |
+
}
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
// Convert dataURL to blob
|
| 80 |
+
const response = await fetch(dataUrl);
|
| 81 |
+
const blob = await response.blob();
|
| 82 |
+
saveAs(blob, `huggingface-${profileData.username}-card.png`);
|
| 83 |
+
|
| 84 |
+
} else if (format === 'qr-only' && qrCodeInstance) {
|
| 85 |
+
// Download just the QR code
|
| 86 |
+
const blob = await qrCodeInstance.download({
|
| 87 |
+
name: `huggingface-${profileData.username}`,
|
| 88 |
+
extension: 'png'
|
| 89 |
+
});
|
| 90 |
+
saveAs(blob, `huggingface-${profileData.username}-qr.png`);
|
| 91 |
+
|
| 92 |
+
} else if (format === 'svg' && qrCodeInstance) {
|
| 93 |
+
// Download QR as SVG
|
| 94 |
+
const blob = await qrCodeInstance.download({
|
| 95 |
+
name: `huggingface-${profileData.username}`,
|
| 96 |
+
extension: 'svg'
|
| 97 |
+
});
|
| 98 |
+
saveAs(blob, `huggingface-${profileData.username}.svg`);
|
| 99 |
+
}
|
| 100 |
+
} catch (err) {
|
| 101 |
+
console.error('Download error:', err);
|
| 102 |
+
}
|
| 103 |
+
};
|
| 104 |
+
|
| 105 |
+
const handleShare = (platform) => {
|
| 106 |
+
const shareText = `Check out my Hugging Face profile!`;
|
| 107 |
+
const shareUrl = profileData?.qrValue || '';
|
| 108 |
+
|
| 109 |
+
const shareLinks = {
|
| 110 |
+
twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(shareUrl)}`,
|
| 111 |
+
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,
|
| 112 |
+
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`
|
| 113 |
+
};
|
| 114 |
+
|
| 115 |
+
if (shareLinks[platform]) {
|
| 116 |
+
window.open(shareLinks[platform], '_blank', 'width=600,height=400');
|
| 117 |
+
}
|
| 118 |
+
};
|
| 119 |
+
|
| 120 |
+
const isValid = inputUrl.trim().length > 0;
|
| 121 |
+
|
| 122 |
+
return (
|
| 123 |
+
<div className="qr-generator">
|
| 124 |
+
<div className="hf-card">
|
| 125 |
+
<div className="hf-header">
|
| 126 |
+
<div className="hf-emoji">🤗</div>
|
| 127 |
+
<div className="hf-title-wrap">
|
| 128 |
+
<h1 className="hf-title">Hugging Face</h1>
|
| 129 |
+
<p className="hf-subtitle">Roast your favorite Hugging Face user!</p>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
<div className="hf-form">
|
| 134 |
+
<div className="hf-field">
|
| 135 |
+
<label className="hf-label">HUGGING FACE USERNAME</label>
|
| 136 |
+
<div className="hf-input-wrap">
|
| 137 |
+
<input
|
| 138 |
+
type="text"
|
| 139 |
+
value={inputUrl}
|
| 140 |
+
onChange={(e) => setInputUrl(e.target.value)}
|
| 141 |
+
placeholder="https://huggingface.co/"
|
| 142 |
+
className="hf-input"
|
| 143 |
+
onKeyPress={(e) => e.key === 'Enter' && handleGenerate()}
|
| 144 |
+
/>
|
| 145 |
+
<span className={`hf-valid ${isValid ? 'active' : ''}`} aria-hidden="true">✓</span>
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<div className="hf-field">
|
| 150 |
+
<label className="hf-label">LANGUAGE</label>
|
| 151 |
+
<select className="hf-select" defaultValue="en">
|
| 152 |
+
<option value="en">English</option>
|
| 153 |
+
<option value="es">Spanish</option>
|
| 154 |
+
<option value="fr">French</option>
|
| 155 |
+
</select>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<button
|
| 159 |
+
onClick={handleGenerate}
|
| 160 |
+
disabled={!inputUrl || loading}
|
| 161 |
+
className="hf-cta"
|
| 162 |
+
>
|
| 163 |
+
{loading ? 'Roasting in progress...' : 'Roast'}
|
| 164 |
+
</button>
|
| 165 |
+
|
| 166 |
+
{error && (
|
| 167 |
+
<div className="error-message" role="alert">{error}</div>
|
| 168 |
+
)}
|
| 169 |
+
</div>
|
| 170 |
+
</div>
|
| 171 |
+
|
| 172 |
+
{profileData && (
|
| 173 |
+
<div className="result-section">
|
| 174 |
+
<div className="profile-info">
|
| 175 |
+
<img
|
| 176 |
+
src={profileData.avatarUrl}
|
| 177 |
+
alt={profileData.fullName}
|
| 178 |
+
className="profile-avatar"
|
| 179 |
+
crossOrigin="anonymous"
|
| 180 |
+
/>
|
| 181 |
+
<div className="profile-details">
|
| 182 |
+
<h2>{profileData.fullName}</h2>
|
| 183 |
+
<p className="profile-username">@{profileData.username}</p>
|
| 184 |
+
<a
|
| 185 |
+
href={profileData.profileUrl}
|
| 186 |
+
target="_blank"
|
| 187 |
+
rel="noopener noreferrer"
|
| 188 |
+
className="profile-link"
|
| 189 |
+
>
|
| 190 |
+
View Profile →
|
| 191 |
+
</a>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
|
| 195 |
+
<div className="qr-preview">
|
| 196 |
+
<div className="qr-phone-bg">
|
| 197 |
+
<div className="qr-card-v2" id="qr-card">
|
| 198 |
+
<div className="qr-avatar-wrap">
|
| 199 |
+
<img
|
| 200 |
+
src={profileData.originalAvatarUrl || profileData.avatarUrl}
|
| 201 |
+
alt={profileData.fullName}
|
| 202 |
+
className="qr-avatar"
|
| 203 |
+
crossOrigin="anonymous"
|
| 204 |
+
/>
|
| 205 |
+
</div>
|
| 206 |
+
<div className="qr-card-inner">
|
| 207 |
+
<div className="qr-name">{profileData.fullName}</div>
|
| 208 |
+
<div className="qr-code-holder">
|
| 209 |
+
<QRCodeWithLogo
|
| 210 |
+
value={profileData.qrValue}
|
| 211 |
+
logoUrl="https://huggingface.co/front/assets/huggingface_logo.svg"
|
| 212 |
+
size={260}
|
| 213 |
+
onQRCodeReady={setQrCodeInstance}
|
| 214 |
+
backgroundColor="#FFFFFF"
|
| 215 |
+
dotsColor="#000000"
|
| 216 |
+
/>
|
| 217 |
+
</div>
|
| 218 |
+
<p className="qr-caption">Share your QR code so others can follow you</p>
|
| 219 |
+
<div className="qr-brand">
|
| 220 |
+
<img src="https://huggingface.co/front/assets/huggingface_logo.svg" alt="Hugging Face" />
|
| 221 |
+
<span>Hugging Face</span>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
<div className="qr-bg-help">Tap background to change color</div>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
|
| 229 |
+
<div className="actions-section">
|
| 230 |
+
<div className="download-actions">
|
| 231 |
+
<h3>Download Options</h3>
|
| 232 |
+
<div className="button-group">
|
| 233 |
+
<button
|
| 234 |
+
onClick={() => handleDownload('card')}
|
| 235 |
+
className="download-btn primary"
|
| 236 |
+
>
|
| 237 |
+
Download Card (PNG)
|
| 238 |
+
</button>
|
| 239 |
+
<button
|
| 240 |
+
onClick={() => handleDownload('qr-only')}
|
| 241 |
+
className="download-btn secondary"
|
| 242 |
+
>
|
| 243 |
+
QR Code Only
|
| 244 |
+
</button>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
|
| 248 |
+
<div className="share-actions">
|
| 249 |
+
<h3>Share Profile</h3>
|
| 250 |
+
<div className="button-group">
|
| 251 |
+
<button
|
| 252 |
+
onClick={() => handleShare('twitter')}
|
| 253 |
+
className="share-btn twitter"
|
| 254 |
+
aria-label="Share on Twitter"
|
| 255 |
+
>
|
| 256 |
+
𝕏 Twitter
|
| 257 |
+
</button>
|
| 258 |
+
<button
|
| 259 |
+
onClick={() => handleShare('facebook')}
|
| 260 |
+
className="share-btn facebook"
|
| 261 |
+
aria-label="Share on Facebook"
|
| 262 |
+
>
|
| 263 |
+
f Facebook
|
| 264 |
+
</button>
|
| 265 |
+
<button
|
| 266 |
+
onClick={() => handleShare('linkedin')}
|
| 267 |
+
className="share-btn linkedin"
|
| 268 |
+
aria-label="Share on LinkedIn"
|
| 269 |
+
>
|
| 270 |
+
in LinkedIn
|
| 271 |
+
</button>
|
| 272 |
+
</div>
|
| 273 |
+
</div>
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
)}
|
| 277 |
+
</div>
|
| 278 |
+
);
|
| 279 |
+
};
|
| 280 |
+
|
| 281 |
+
export default HuggingFaceQRGenerator;
|
components/HuggingFaceQRGenerator.tsx
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import React, { useState } from 'react';
|
| 4 |
+
import { parseHuggingFaceUrl } from '../lib/huggingface';
|
| 5 |
+
import QRCodeWithLogo from './QRCodeWithLogo';
|
| 6 |
+
import { saveAs } from 'file-saver';
|
| 7 |
+
import * as htmlToImage from 'html-to-image';
|
| 8 |
+
import { Button } from '@/components/ui/button';
|
| 9 |
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
| 10 |
+
import { Input } from '@/components/ui/input';
|
| 11 |
+
import { Label } from '@/components/ui/label';
|
| 12 |
+
import { Badge } from '@/components/ui/badge';
|
| 13 |
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
| 14 |
+
import { Download, Loader2, QrCode, Twitter, Facebook, Linkedin, Sparkles, ExternalLink, CheckCircle2, MessageCircle, Mail, MoreHorizontal, ChevronLeft, Menu } from 'lucide-react';
|
| 15 |
+
|
| 16 |
+
const HuggingFaceQRGenerator = () => {
|
| 17 |
+
const [inputUrl, setInputUrl] = useState('');
|
| 18 |
+
const [profileData, setProfileData] = useState<any>(null);
|
| 19 |
+
const [loading, setLoading] = useState(false);
|
| 20 |
+
const [error, setError] = useState('');
|
| 21 |
+
const [qrCodeInstance, setQrCodeInstance] = useState<any>(null);
|
| 22 |
+
|
| 23 |
+
const handleGenerate = async () => {
|
| 24 |
+
setError('');
|
| 25 |
+
setLoading(true);
|
| 26 |
+
|
| 27 |
+
try {
|
| 28 |
+
// Parse the URL to extract username and resource info
|
| 29 |
+
const parsed = parseHuggingFaceUrl(inputUrl);
|
| 30 |
+
|
| 31 |
+
// Fetch profile data from our API
|
| 32 |
+
const response = await fetch('/api/huggingface', {
|
| 33 |
+
method: 'POST',
|
| 34 |
+
headers: {
|
| 35 |
+
'Content-Type': 'application/json',
|
| 36 |
+
},
|
| 37 |
+
body: JSON.stringify({
|
| 38 |
+
username: parsed.username,
|
| 39 |
+
resourceType: parsed.resourceType,
|
| 40 |
+
resourceName: parsed.resourceName
|
| 41 |
+
}),
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
if (!response.ok) {
|
| 45 |
+
throw new Error('Failed to fetch profile data');
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
const data = await response.json();
|
| 49 |
+
|
| 50 |
+
// Use proxy for the avatar to avoid CORS issues
|
| 51 |
+
const proxiedAvatarUrl = `/api/proxy-image?url=${encodeURIComponent(data.avatarUrl)}`;
|
| 52 |
+
|
| 53 |
+
setProfileData({
|
| 54 |
+
...data,
|
| 55 |
+
avatarUrl: proxiedAvatarUrl,
|
| 56 |
+
originalAvatarUrl: data.avatarUrl,
|
| 57 |
+
qrValue: parsed.profileUrl
|
| 58 |
+
});
|
| 59 |
+
} catch (err: any) {
|
| 60 |
+
setError(err.message || 'Invalid URL or username');
|
| 61 |
+
setProfileData(null);
|
| 62 |
+
} finally {
|
| 63 |
+
setLoading(false);
|
| 64 |
+
}
|
| 65 |
+
};
|
| 66 |
+
|
| 67 |
+
const handleDownload = async (format = 'png') => {
|
| 68 |
+
if (!profileData) return;
|
| 69 |
+
|
| 70 |
+
try {
|
| 71 |
+
const cardElement = document.getElementById('qr-card');
|
| 72 |
+
|
| 73 |
+
if (format === 'png' || format === 'card') {
|
| 74 |
+
// Download the entire card with user info
|
| 75 |
+
const dataUrl = await htmlToImage.toPng(cardElement!, {
|
| 76 |
+
quality: 1.0,
|
| 77 |
+
backgroundColor: '#ffffff',
|
| 78 |
+
pixelRatio: 2,
|
| 79 |
+
style: {
|
| 80 |
+
margin: '0',
|
| 81 |
+
borderRadius: '12px'
|
| 82 |
+
}
|
| 83 |
+
});
|
| 84 |
+
|
| 85 |
+
// Convert dataURL to blob
|
| 86 |
+
const response = await fetch(dataUrl);
|
| 87 |
+
const blob = await response.blob();
|
| 88 |
+
saveAs(blob, `huggingface-${profileData.username}-card.png`);
|
| 89 |
+
|
| 90 |
+
} else if (format === 'qr-only' && qrCodeInstance) {
|
| 91 |
+
// Download just the QR code
|
| 92 |
+
const blob = await qrCodeInstance.download({
|
| 93 |
+
name: `huggingface-${profileData.username}`,
|
| 94 |
+
extension: 'png'
|
| 95 |
+
});
|
| 96 |
+
saveAs(blob, `huggingface-${profileData.username}-qr.png`);
|
| 97 |
+
}
|
| 98 |
+
} catch (err) {
|
| 99 |
+
console.error('Download error:', err);
|
| 100 |
+
}
|
| 101 |
+
};
|
| 102 |
+
|
| 103 |
+
const handleShare = (platform: string) => {
|
| 104 |
+
const shareText = `Check out my Hugging Face profile!`;
|
| 105 |
+
const shareUrl = profileData?.qrValue || '';
|
| 106 |
+
|
| 107 |
+
const shareLinks: { [key: string]: string } = {
|
| 108 |
+
twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(shareUrl)}`,
|
| 109 |
+
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,
|
| 110 |
+
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
if (shareLinks[platform]) {
|
| 114 |
+
window.open(shareLinks[platform], '_blank', 'width=600,height=400');
|
| 115 |
+
}
|
| 116 |
+
};
|
| 117 |
+
|
| 118 |
+
const getResourceIcon = (type: string) => {
|
| 119 |
+
switch(type) {
|
| 120 |
+
case 'model': return '🤖';
|
| 121 |
+
case 'dataset': return '📊';
|
| 122 |
+
case 'space': return '🚀';
|
| 123 |
+
default: return '👤';
|
| 124 |
+
}
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
const isValid = inputUrl.trim().length > 0
|
| 128 |
+
|
| 129 |
+
return (
|
| 130 |
+
<div className="min-h-screen grid place-items-center bg-linear-to-br from-purple-50 via-pink-50 to-blue-50 dark:from-gray-900 dark:via-purple-900 dark:to-gray-900 p-4 md:p-8">
|
| 131 |
+
<div className="w-full max-w-3xl">
|
| 132 |
+
{/* Screenshot-1 inspired input card */}
|
| 133 |
+
<Card className="shadow-xl" style={{ fontFamily: 'var(--font-inter)' }}>
|
| 134 |
+
<CardHeader className="pb-2">
|
| 135 |
+
<div className="flex items-center gap-2">
|
| 136 |
+
<span className="text-3xl">🤗</span>
|
| 137 |
+
<CardTitle className="text-2xl">Hugging Face</CardTitle>
|
| 138 |
+
</div>
|
| 139 |
+
</CardHeader>
|
| 140 |
+
<CardContent className="space-y-5">
|
| 141 |
+
<div className="space-y-2">
|
| 142 |
+
<Label>HUGGING FACE USERNAME</Label>
|
| 143 |
+
<div className="relative">
|
| 144 |
+
<div className="flex items-stretch">
|
| 145 |
+
<span className="hidden sm:flex items-center px-3 text-sm text-muted-foreground bg-secondary/60 border border-input rounded-l-md">
|
| 146 |
+
https://huggingface.co/
|
| 147 |
+
</span>
|
| 148 |
+
<Input
|
| 149 |
+
type="text"
|
| 150 |
+
value={inputUrl}
|
| 151 |
+
onChange={(e) => setInputUrl(e.target.value)}
|
| 152 |
+
placeholder="username or full URL"
|
| 153 |
+
className="h-11 sm:rounded-l-none sm:border-l-0 pr-10"
|
| 154 |
+
onKeyPress={(e) => e.key === 'Enter' && handleGenerate()}
|
| 155 |
+
disabled={loading}
|
| 156 |
+
/>
|
| 157 |
+
</div>
|
| 158 |
+
<CheckCircle2
|
| 159 |
+
className={`absolute right-3 top-1/2 -translate-y-1/2 ${isValid ? 'text-emerald-500' : 'text-muted-foreground/30'}`}
|
| 160 |
+
/>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<Button
|
| 165 |
+
onClick={handleGenerate}
|
| 166 |
+
disabled={!inputUrl || loading}
|
| 167 |
+
className="rounded-full px-6 bg-muted text-foreground hover:bg-muted/80 dark:bg-input/40"
|
| 168 |
+
>
|
| 169 |
+
{loading ? 'Generating…' : 'Generate QR Code'}
|
| 170 |
+
</Button>
|
| 171 |
+
|
| 172 |
+
{error && (
|
| 173 |
+
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 px-4 py-2 rounded-lg text-sm">
|
| 174 |
+
{error}
|
| 175 |
+
</div>
|
| 176 |
+
)}
|
| 177 |
+
</CardContent>
|
| 178 |
+
</Card>
|
| 179 |
+
|
| 180 |
+
{/* Result Section - Phone-like preview matching screenshot */}
|
| 181 |
+
{profileData && (
|
| 182 |
+
<div className="qr-preview flex justify-center">
|
| 183 |
+
<div className="qr-phone-bg">
|
| 184 |
+
<div className="qr-topbar">
|
| 185 |
+
<button aria-label="Back"><ChevronLeft size={18} /></button>
|
| 186 |
+
<button aria-label="Menu"><Menu size={18} /></button>
|
| 187 |
+
</div>
|
| 188 |
+
|
| 189 |
+
<div className="qr-card-v2" id="qr-card">
|
| 190 |
+
<div className="qr-avatar-wrap">
|
| 191 |
+
<img
|
| 192 |
+
src={profileData.originalAvatarUrl || profileData.avatarUrl}
|
| 193 |
+
alt={profileData.fullName}
|
| 194 |
+
className="qr-avatar"
|
| 195 |
+
crossOrigin="anonymous"
|
| 196 |
+
/>
|
| 197 |
+
</div>
|
| 198 |
+
<div className="qr-card-inner">
|
| 199 |
+
<div className="qr-name">{profileData.fullName}</div>
|
| 200 |
+
<div className="qr-code-holder">
|
| 201 |
+
<QRCodeWithLogo
|
| 202 |
+
value={profileData.qrValue}
|
| 203 |
+
logoUrl="https://huggingface.co/front/assets/huggingface_logo.svg"
|
| 204 |
+
size={210}
|
| 205 |
+
onQRCodeReady={setQrCodeInstance}
|
| 206 |
+
backgroundColor="#FFFFFF"
|
| 207 |
+
dotsColor="#000000"
|
| 208 |
+
/>
|
| 209 |
+
</div>
|
| 210 |
+
<p className="qr-caption">Share your QR code so others can follow you</p>
|
| 211 |
+
<div className="qr-brand">
|
| 212 |
+
<img src="https://huggingface.co/front/assets/huggingface_logo.svg" alt="Hugging Face" />
|
| 213 |
+
<span>Hugging Face</span>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<div className="qr-bg-help">Tap background to change color</div>
|
| 219 |
+
|
| 220 |
+
<div className="qr-share-sheet">
|
| 221 |
+
<div className="qr-download">
|
| 222 |
+
<button onClick={() => handleDownload('card')} className="qr-circle">
|
| 223 |
+
<Download size={18} />
|
| 224 |
+
</button>
|
| 225 |
+
<span>Download</span>
|
| 226 |
+
</div>
|
| 227 |
+
<div className="qr-share-group">
|
| 228 |
+
<span className="qr-share-label">Share to</span>
|
| 229 |
+
<div className="qr-share-actions">
|
| 230 |
+
<button className="qr-circle"><MessageCircle size={18} /></button>
|
| 231 |
+
<button className="qr-circle"><Mail size={18} /></button>
|
| 232 |
+
<button className="qr-circle"><MoreHorizontal size={18} /></button>
|
| 233 |
+
</div>
|
| 234 |
+
<div className="qr-share-texts">
|
| 235 |
+
<span>SMS</span>
|
| 236 |
+
<span>Email</span>
|
| 237 |
+
<span>Other</span>
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
<button className="qr-close" aria-label="Close">×</button>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
)}
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
);
|
| 248 |
+
};
|
| 249 |
+
|
| 250 |
+
export default HuggingFaceQRGenerator;
|
components/QRCodeWithLogo.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import React, { useEffect, useRef, useState } from 'react';
|
| 4 |
+
import QRCodeStyling from 'qr-code-styling';
|
| 5 |
+
|
| 6 |
+
const QRCodeWithLogo = ({
|
| 7 |
+
value,
|
| 8 |
+
logoUrl,
|
| 9 |
+
size = 300,
|
| 10 |
+
onQRCodeReady,
|
| 11 |
+
backgroundColor = '#FFFFFF',
|
| 12 |
+
dotsColor = '#000000'
|
| 13 |
+
}) => {
|
| 14 |
+
const ref = useRef(null);
|
| 15 |
+
const [qrCode, setQrCode] = useState(null);
|
| 16 |
+
|
| 17 |
+
useEffect(() => {
|
| 18 |
+
if (!value) return;
|
| 19 |
+
|
| 20 |
+
const qrCodeInstance = new QRCodeStyling({
|
| 21 |
+
width: size,
|
| 22 |
+
height: size,
|
| 23 |
+
type: 'svg',
|
| 24 |
+
data: value,
|
| 25 |
+
image: logoUrl,
|
| 26 |
+
dotsOptions: {
|
| 27 |
+
color: dotsColor,
|
| 28 |
+
type: 'rounded'
|
| 29 |
+
},
|
| 30 |
+
backgroundOptions: {
|
| 31 |
+
color: backgroundColor,
|
| 32 |
+
},
|
| 33 |
+
imageOptions: {
|
| 34 |
+
crossOrigin: 'anonymous',
|
| 35 |
+
margin: 10,
|
| 36 |
+
imageSize: 0.3,
|
| 37 |
+
hideBackgroundDots: true
|
| 38 |
+
},
|
| 39 |
+
cornersSquareOptions: {
|
| 40 |
+
type: 'extra-rounded',
|
| 41 |
+
color: dotsColor
|
| 42 |
+
},
|
| 43 |
+
cornersDotOptions: {
|
| 44 |
+
type: 'dot',
|
| 45 |
+
color: dotsColor
|
| 46 |
+
}
|
| 47 |
+
});
|
| 48 |
+
|
| 49 |
+
setQrCode(qrCodeInstance);
|
| 50 |
+
|
| 51 |
+
if (ref.current) {
|
| 52 |
+
ref.current.innerHTML = '';
|
| 53 |
+
qrCodeInstance.append(ref.current);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
if (onQRCodeReady) {
|
| 57 |
+
onQRCodeReady(qrCodeInstance);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
return () => {
|
| 61 |
+
if (ref.current) {
|
| 62 |
+
ref.current.innerHTML = '';
|
| 63 |
+
}
|
| 64 |
+
};
|
| 65 |
+
}, [value, logoUrl, size, backgroundColor, dotsColor]);
|
| 66 |
+
|
| 67 |
+
return <div ref={ref} className="qr-code-container" />;
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
export default QRCodeWithLogo;
|
components/QRCodeWithLogo.tsx
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import React, { useEffect, useRef, useState } from 'react';
|
| 4 |
+
import QRCodeStyling from 'qr-code-styling';
|
| 5 |
+
|
| 6 |
+
interface QRCodeWithLogoProps {
|
| 7 |
+
value: string;
|
| 8 |
+
logoUrl?: string;
|
| 9 |
+
size?: number;
|
| 10 |
+
onQRCodeReady?: (qrCode: QRCodeStyling) => void;
|
| 11 |
+
backgroundColor?: string;
|
| 12 |
+
dotsColor?: string;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
const QRCodeWithLogo: React.FC<QRCodeWithLogoProps> = ({
|
| 16 |
+
value,
|
| 17 |
+
logoUrl,
|
| 18 |
+
size = 300,
|
| 19 |
+
onQRCodeReady,
|
| 20 |
+
backgroundColor = '#FFFFFF',
|
| 21 |
+
dotsColor = '#000000'
|
| 22 |
+
}) => {
|
| 23 |
+
const ref = useRef<HTMLDivElement>(null);
|
| 24 |
+
const [qrCode, setQrCode] = useState<QRCodeStyling | null>(null);
|
| 25 |
+
|
| 26 |
+
useEffect(() => {
|
| 27 |
+
if (!value) return;
|
| 28 |
+
|
| 29 |
+
const qrCodeInstance = new QRCodeStyling({
|
| 30 |
+
width: size,
|
| 31 |
+
height: size,
|
| 32 |
+
type: 'svg',
|
| 33 |
+
data: value,
|
| 34 |
+
image: logoUrl,
|
| 35 |
+
dotsOptions: {
|
| 36 |
+
color: dotsColor,
|
| 37 |
+
type: 'rounded'
|
| 38 |
+
},
|
| 39 |
+
backgroundOptions: {
|
| 40 |
+
color: backgroundColor,
|
| 41 |
+
},
|
| 42 |
+
imageOptions: {
|
| 43 |
+
crossOrigin: 'anonymous',
|
| 44 |
+
margin: 10,
|
| 45 |
+
imageSize: 0.3,
|
| 46 |
+
hideBackgroundDots: true
|
| 47 |
+
},
|
| 48 |
+
cornersSquareOptions: {
|
| 49 |
+
type: 'extra-rounded',
|
| 50 |
+
color: dotsColor
|
| 51 |
+
},
|
| 52 |
+
cornersDotOptions: {
|
| 53 |
+
type: 'dot',
|
| 54 |
+
color: dotsColor
|
| 55 |
+
}
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
setQrCode(qrCodeInstance);
|
| 59 |
+
|
| 60 |
+
if (ref.current) {
|
| 61 |
+
ref.current.innerHTML = '';
|
| 62 |
+
qrCodeInstance.append(ref.current);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
if (onQRCodeReady) {
|
| 66 |
+
onQRCodeReady(qrCodeInstance);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
return () => {
|
| 70 |
+
if (ref.current) {
|
| 71 |
+
ref.current.innerHTML = '';
|
| 72 |
+
}
|
| 73 |
+
};
|
| 74 |
+
}, [value, logoUrl, size, backgroundColor, dotsColor, onQRCodeReady]);
|
| 75 |
+
|
| 76 |
+
return <div ref={ref} className="qr-code-container" />;
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
export default QRCodeWithLogo;
|
components/ui/avatar.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
function Avatar({
|
| 9 |
+
className,
|
| 10 |
+
...props
|
| 11 |
+
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
| 12 |
+
return (
|
| 13 |
+
<AvatarPrimitive.Root
|
| 14 |
+
data-slot="avatar"
|
| 15 |
+
className={cn(
|
| 16 |
+
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
| 17 |
+
className
|
| 18 |
+
)}
|
| 19 |
+
{...props}
|
| 20 |
+
/>
|
| 21 |
+
)
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
function AvatarImage({
|
| 25 |
+
className,
|
| 26 |
+
...props
|
| 27 |
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
| 28 |
+
return (
|
| 29 |
+
<AvatarPrimitive.Image
|
| 30 |
+
data-slot="avatar-image"
|
| 31 |
+
className={cn("aspect-square size-full", className)}
|
| 32 |
+
{...props}
|
| 33 |
+
/>
|
| 34 |
+
)
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
function AvatarFallback({
|
| 38 |
+
className,
|
| 39 |
+
...props
|
| 40 |
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
| 41 |
+
return (
|
| 42 |
+
<AvatarPrimitive.Fallback
|
| 43 |
+
data-slot="avatar-fallback"
|
| 44 |
+
className={cn(
|
| 45 |
+
"bg-muted flex size-full items-center justify-center rounded-full",
|
| 46 |
+
className
|
| 47 |
+
)}
|
| 48 |
+
{...props}
|
| 49 |
+
/>
|
| 50 |
+
)
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
components/ui/badge.tsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 badgeVariants = cva(
|
| 8 |
+
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default:
|
| 13 |
+
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
| 14 |
+
secondary:
|
| 15 |
+
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
| 16 |
+
destructive:
|
| 17 |
+
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
| 18 |
+
outline:
|
| 19 |
+
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
| 20 |
+
},
|
| 21 |
+
},
|
| 22 |
+
defaultVariants: {
|
| 23 |
+
variant: "default",
|
| 24 |
+
},
|
| 25 |
+
}
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
function Badge({
|
| 29 |
+
className,
|
| 30 |
+
variant,
|
| 31 |
+
asChild = false,
|
| 32 |
+
...props
|
| 33 |
+
}: React.ComponentProps<"span"> &
|
| 34 |
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
| 35 |
+
const Comp = asChild ? Slot : "span"
|
| 36 |
+
|
| 37 |
+
return (
|
| 38 |
+
<Comp
|
| 39 |
+
data-slot="badge"
|
| 40 |
+
className={cn(badgeVariants({ variant }), className)}
|
| 41 |
+
{...props}
|
| 42 |
+
/>
|
| 43 |
+
)
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
export { Badge, badgeVariants }
|
components/ui/button.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
| 13 |
+
destructive:
|
| 14 |
+
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
| 15 |
+
outline:
|
| 16 |
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
| 17 |
+
secondary:
|
| 18 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
| 19 |
+
ghost:
|
| 20 |
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
| 21 |
+
link: "text-primary underline-offset-4 hover:underline",
|
| 22 |
+
},
|
| 23 |
+
size: {
|
| 24 |
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
| 25 |
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
| 26 |
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
| 27 |
+
icon: "size-9",
|
| 28 |
+
"icon-sm": "size-8",
|
| 29 |
+
"icon-lg": "size-10",
|
| 30 |
+
},
|
| 31 |
+
},
|
| 32 |
+
defaultVariants: {
|
| 33 |
+
variant: "default",
|
| 34 |
+
size: "default",
|
| 35 |
+
},
|
| 36 |
+
}
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
function Button({
|
| 40 |
+
className,
|
| 41 |
+
variant,
|
| 42 |
+
size,
|
| 43 |
+
asChild = false,
|
| 44 |
+
...props
|
| 45 |
+
}: React.ComponentProps<"button"> &
|
| 46 |
+
VariantProps<typeof buttonVariants> & {
|
| 47 |
+
asChild?: boolean
|
| 48 |
+
}) {
|
| 49 |
+
const Comp = asChild ? Slot : "button"
|
| 50 |
+
|
| 51 |
+
return (
|
| 52 |
+
<Comp
|
| 53 |
+
data-slot="button"
|
| 54 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
| 55 |
+
{...props}
|
| 56 |
+
/>
|
| 57 |
+
)
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
export { Button, buttonVariants }
|
components/ui/card.tsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
| 6 |
+
return (
|
| 7 |
+
<div
|
| 8 |
+
data-slot="card"
|
| 9 |
+
className={cn(
|
| 10 |
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
| 11 |
+
className
|
| 12 |
+
)}
|
| 13 |
+
{...props}
|
| 14 |
+
/>
|
| 15 |
+
)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
| 19 |
+
return (
|
| 20 |
+
<div
|
| 21 |
+
data-slot="card-header"
|
| 22 |
+
className={cn(
|
| 23 |
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
| 24 |
+
className
|
| 25 |
+
)}
|
| 26 |
+
{...props}
|
| 27 |
+
/>
|
| 28 |
+
)
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
| 32 |
+
return (
|
| 33 |
+
<div
|
| 34 |
+
data-slot="card-title"
|
| 35 |
+
className={cn("leading-none font-semibold", className)}
|
| 36 |
+
{...props}
|
| 37 |
+
/>
|
| 38 |
+
)
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
| 42 |
+
return (
|
| 43 |
+
<div
|
| 44 |
+
data-slot="card-description"
|
| 45 |
+
className={cn("text-muted-foreground text-sm", className)}
|
| 46 |
+
{...props}
|
| 47 |
+
/>
|
| 48 |
+
)
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
| 52 |
+
return (
|
| 53 |
+
<div
|
| 54 |
+
data-slot="card-action"
|
| 55 |
+
className={cn(
|
| 56 |
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
| 57 |
+
className
|
| 58 |
+
)}
|
| 59 |
+
{...props}
|
| 60 |
+
/>
|
| 61 |
+
)
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
| 65 |
+
return (
|
| 66 |
+
<div
|
| 67 |
+
data-slot="card-content"
|
| 68 |
+
className={cn("px-6", className)}
|
| 69 |
+
{...props}
|
| 70 |
+
/>
|
| 71 |
+
)
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
| 75 |
+
return (
|
| 76 |
+
<div
|
| 77 |
+
data-slot="card-footer"
|
| 78 |
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
| 79 |
+
{...props}
|
| 80 |
+
/>
|
| 81 |
+
)
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
export {
|
| 85 |
+
Card,
|
| 86 |
+
CardHeader,
|
| 87 |
+
CardFooter,
|
| 88 |
+
CardTitle,
|
| 89 |
+
CardAction,
|
| 90 |
+
CardDescription,
|
| 91 |
+
CardContent,
|
| 92 |
+
}
|
components/ui/input.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
| 6 |
+
return (
|
| 7 |
+
<input
|
| 8 |
+
type={type}
|
| 9 |
+
data-slot="input"
|
| 10 |
+
className={cn(
|
| 11 |
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
| 12 |
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
| 13 |
+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
| 14 |
+
className
|
| 15 |
+
)}
|
| 16 |
+
{...props}
|
| 17 |
+
/>
|
| 18 |
+
)
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export { Input }
|
components/ui/label.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
| 6 |
+
return (
|
| 7 |
+
<label
|
| 8 |
+
data-slot="label"
|
| 9 |
+
className={cn(
|
| 10 |
+
"text-xs font-semibold tracking-wider text-muted-foreground",
|
| 11 |
+
className
|
| 12 |
+
)}
|
| 13 |
+
{...props}
|
| 14 |
+
/>
|
| 15 |
+
)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export { Label }
|
| 19 |
+
|
| 20 |
+
|
lib/huggingface.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Parse Hugging Face URL and extract username/organization
|
| 3 |
+
* @param {string} url - The Hugging Face URL
|
| 4 |
+
* @returns {object} - Parsed data with type and username
|
| 5 |
+
*/
|
| 6 |
+
export function parseHuggingFaceUrl(url) {
|
| 7 |
+
try {
|
| 8 |
+
// Remove trailing slash if present
|
| 9 |
+
url = url.trim().replace(/\/$/, '');
|
| 10 |
+
|
| 11 |
+
// Handle different URL formats
|
| 12 |
+
let parsedUrl;
|
| 13 |
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
| 14 |
+
parsedUrl = new URL(url);
|
| 15 |
+
} else if (url.startsWith('huggingface.co')) {
|
| 16 |
+
parsedUrl = new URL(`https://${url}`);
|
| 17 |
+
} else {
|
| 18 |
+
// Assume it's just a username
|
| 19 |
+
return {
|
| 20 |
+
type: 'profile',
|
| 21 |
+
username: url,
|
| 22 |
+
profileUrl: `https://huggingface.co/${url}`,
|
| 23 |
+
resourceType: null,
|
| 24 |
+
resourceName: null,
|
| 25 |
+
fullUrl: `https://huggingface.co/${url}`
|
| 26 |
+
};
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Check if it's a Hugging Face domain
|
| 30 |
+
if (!parsedUrl.hostname.includes('huggingface.co')) {
|
| 31 |
+
throw new Error('Not a valid Hugging Face URL');
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
const pathParts = parsedUrl.pathname.split('/').filter(part => part);
|
| 35 |
+
|
| 36 |
+
if (pathParts.length === 0) {
|
| 37 |
+
throw new Error('No username or organization found in URL');
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// Extract username/org from different URL types
|
| 41 |
+
let username = pathParts[0];
|
| 42 |
+
let resourceType = null;
|
| 43 |
+
let resourceName = null;
|
| 44 |
+
let type = 'profile';
|
| 45 |
+
|
| 46 |
+
// Detect resource type and name
|
| 47 |
+
if (pathParts.length >= 2) {
|
| 48 |
+
// Check for direct resource URLs like /username/model-name
|
| 49 |
+
const secondPart = pathParts[1];
|
| 50 |
+
|
| 51 |
+
// Check if it's a resource collection page
|
| 52 |
+
if (secondPart === 'models' || secondPart === 'datasets' || secondPart === 'spaces') {
|
| 53 |
+
// This is a collection page, not a specific resource
|
| 54 |
+
type = 'profile';
|
| 55 |
+
resourceType = null;
|
| 56 |
+
resourceName = null;
|
| 57 |
+
} else {
|
| 58 |
+
// This might be a direct resource URL
|
| 59 |
+
// Models, datasets, and spaces have direct URLs like /username/resource-name
|
| 60 |
+
// We need to detect what type based on context or default to model
|
| 61 |
+
resourceName = secondPart;
|
| 62 |
+
|
| 63 |
+
// Try to detect resource type from URL structure
|
| 64 |
+
if (parsedUrl.hostname === 'huggingface.co') {
|
| 65 |
+
// Check for spaces subdomain or path hints
|
| 66 |
+
if (pathParts.length >= 3) {
|
| 67 |
+
if (pathParts[2] === 'tree' || pathParts[2] === 'blob' || pathParts[2] === 'resolve') {
|
| 68 |
+
resourceType = 'model'; // Repository-like structure usually means model
|
| 69 |
+
} else if (pathParts[2] === 'discussions' || pathParts[2] === 'settings') {
|
| 70 |
+
resourceType = 'model'; // These are common in model repos
|
| 71 |
+
}
|
| 72 |
+
} else {
|
| 73 |
+
// Default to model for direct /username/name pattern
|
| 74 |
+
resourceType = 'model';
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
} else if (parsedUrl.hostname.includes('.hf.space')) {
|
| 79 |
+
// This is a Spaces URL like username-spacename.hf.space
|
| 80 |
+
const subdomain = parsedUrl.hostname.split('.')[0];
|
| 81 |
+
if (subdomain.includes('-')) {
|
| 82 |
+
const parts = subdomain.split('-');
|
| 83 |
+
username = parts[0];
|
| 84 |
+
resourceName = parts.slice(1).join('-');
|
| 85 |
+
resourceType = 'space';
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// Check for datasets in path
|
| 90 |
+
if (parsedUrl.pathname.includes('/datasets/')) {
|
| 91 |
+
const datasetPath = parsedUrl.pathname.split('/datasets/')[1];
|
| 92 |
+
const datasetParts = datasetPath.split('/').filter(p => p);
|
| 93 |
+
if (datasetParts.length >= 2) {
|
| 94 |
+
username = datasetParts[0]; // Correct the username
|
| 95 |
+
resourceType = 'dataset';
|
| 96 |
+
resourceName = datasetParts[1];
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
// Check for spaces in path
|
| 101 |
+
if (parsedUrl.pathname.includes('/spaces/')) {
|
| 102 |
+
const spacePath = parsedUrl.pathname.split('/spaces/')[1];
|
| 103 |
+
const spaceParts = spacePath.split('/').filter(p => p);
|
| 104 |
+
if (spaceParts.length >= 2) {
|
| 105 |
+
username = spaceParts[0]; // Correct the username
|
| 106 |
+
resourceType = 'space';
|
| 107 |
+
resourceName = spaceParts[1];
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
return {
|
| 112 |
+
type,
|
| 113 |
+
username,
|
| 114 |
+
profileUrl: `https://huggingface.co/${username}`,
|
| 115 |
+
resourceType,
|
| 116 |
+
resourceName,
|
| 117 |
+
fullUrl: parsedUrl.href
|
| 118 |
+
};
|
| 119 |
+
} catch (error) {
|
| 120 |
+
throw new Error(`Invalid Hugging Face URL: ${error.message}`);
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
/**
|
| 125 |
+
* Get the avatar URL for a Hugging Face user/organization
|
| 126 |
+
* @param {string} username - The username or organization name
|
| 127 |
+
* @returns {string} - The avatar URL
|
| 128 |
+
*/
|
| 129 |
+
export function getAvatarUrl(username) {
|
| 130 |
+
// Hugging Face avatar URL pattern
|
| 131 |
+
return `https://huggingface.co/avatars/${username}.svg`;
|
| 132 |
+
}
|
lib/huggingface.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interface ParsedHuggingFaceUrl {
|
| 2 |
+
type: string;
|
| 3 |
+
username: string;
|
| 4 |
+
profileUrl: string;
|
| 5 |
+
resourceType: string | null;
|
| 6 |
+
resourceName: string | null;
|
| 7 |
+
fullUrl: string;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/**
|
| 11 |
+
* Parse Hugging Face URL and extract username/organization
|
| 12 |
+
* @param {string} url - The Hugging Face URL
|
| 13 |
+
* @returns {ParsedHuggingFaceUrl} - Parsed data with type and username
|
| 14 |
+
*/
|
| 15 |
+
export function parseHuggingFaceUrl(url: string): ParsedHuggingFaceUrl {
|
| 16 |
+
try {
|
| 17 |
+
// Remove trailing slash if present
|
| 18 |
+
url = url.trim().replace(/\/$/, '');
|
| 19 |
+
|
| 20 |
+
// Handle different URL formats
|
| 21 |
+
let parsedUrl: URL;
|
| 22 |
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
| 23 |
+
parsedUrl = new URL(url);
|
| 24 |
+
} else if (url.startsWith('huggingface.co')) {
|
| 25 |
+
parsedUrl = new URL(`https://${url}`);
|
| 26 |
+
} else {
|
| 27 |
+
// Assume it's just a username
|
| 28 |
+
return {
|
| 29 |
+
type: 'profile',
|
| 30 |
+
username: url,
|
| 31 |
+
profileUrl: `https://huggingface.co/${url}`,
|
| 32 |
+
resourceType: null,
|
| 33 |
+
resourceName: null,
|
| 34 |
+
fullUrl: `https://huggingface.co/${url}`
|
| 35 |
+
};
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// Check if it's a Hugging Face domain
|
| 39 |
+
if (!parsedUrl.hostname.includes('huggingface.co')) {
|
| 40 |
+
throw new Error('Not a valid Hugging Face URL');
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const pathParts = parsedUrl.pathname.split('/').filter(part => part);
|
| 44 |
+
|
| 45 |
+
if (pathParts.length === 0) {
|
| 46 |
+
throw new Error('No username or organization found in URL');
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Extract username/org from different URL types
|
| 50 |
+
let username = pathParts[0];
|
| 51 |
+
let resourceType: string | null = null;
|
| 52 |
+
let resourceName: string | null = null;
|
| 53 |
+
let type = 'profile';
|
| 54 |
+
|
| 55 |
+
// Detect resource type and name
|
| 56 |
+
if (pathParts.length >= 2) {
|
| 57 |
+
// Check for direct resource URLs like /username/model-name
|
| 58 |
+
const secondPart = pathParts[1];
|
| 59 |
+
|
| 60 |
+
// Check if it's a resource collection page
|
| 61 |
+
if (secondPart === 'models' || secondPart === 'datasets' || secondPart === 'spaces') {
|
| 62 |
+
// This is a collection page, not a specific resource
|
| 63 |
+
type = 'profile';
|
| 64 |
+
resourceType = null;
|
| 65 |
+
resourceName = null;
|
| 66 |
+
} else {
|
| 67 |
+
// This might be a direct resource URL
|
| 68 |
+
// Models, datasets, and spaces have direct URLs like /username/resource-name
|
| 69 |
+
// We need to detect what type based on context or default to model
|
| 70 |
+
resourceName = secondPart;
|
| 71 |
+
|
| 72 |
+
// Try to detect resource type from URL structure
|
| 73 |
+
if (parsedUrl.hostname === 'huggingface.co') {
|
| 74 |
+
// Check for spaces subdomain or path hints
|
| 75 |
+
if (pathParts.length >= 3) {
|
| 76 |
+
if (pathParts[2] === 'tree' || pathParts[2] === 'blob' || pathParts[2] === 'resolve') {
|
| 77 |
+
resourceType = 'model'; // Repository-like structure usually means model
|
| 78 |
+
} else if (pathParts[2] === 'discussions' || pathParts[2] === 'settings') {
|
| 79 |
+
resourceType = 'model'; // These are common in model repos
|
| 80 |
+
}
|
| 81 |
+
} else {
|
| 82 |
+
// Default to model for direct /username/name pattern
|
| 83 |
+
resourceType = 'model';
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
} else if (parsedUrl.hostname.includes('.hf.space')) {
|
| 88 |
+
// This is a Spaces URL like username-spacename.hf.space
|
| 89 |
+
const subdomain = parsedUrl.hostname.split('.')[0];
|
| 90 |
+
if (subdomain.includes('-')) {
|
| 91 |
+
const parts = subdomain.split('-');
|
| 92 |
+
username = parts[0];
|
| 93 |
+
resourceName = parts.slice(1).join('-');
|
| 94 |
+
resourceType = 'space';
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// Check for datasets in path
|
| 99 |
+
if (parsedUrl.pathname.includes('/datasets/')) {
|
| 100 |
+
const datasetPath = parsedUrl.pathname.split('/datasets/')[1];
|
| 101 |
+
const datasetParts = datasetPath.split('/').filter(p => p);
|
| 102 |
+
if (datasetParts.length >= 2) {
|
| 103 |
+
username = datasetParts[0]; // Correct the username
|
| 104 |
+
resourceType = 'dataset';
|
| 105 |
+
resourceName = datasetParts[1];
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Check for spaces in path
|
| 110 |
+
if (parsedUrl.pathname.includes('/spaces/')) {
|
| 111 |
+
const spacePath = parsedUrl.pathname.split('/spaces/')[1];
|
| 112 |
+
const spaceParts = spacePath.split('/').filter(p => p);
|
| 113 |
+
if (spaceParts.length >= 2) {
|
| 114 |
+
username = spaceParts[0]; // Correct the username
|
| 115 |
+
resourceType = 'space';
|
| 116 |
+
resourceName = spaceParts[1];
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
return {
|
| 121 |
+
type,
|
| 122 |
+
username,
|
| 123 |
+
profileUrl: `https://huggingface.co/${username}`,
|
| 124 |
+
resourceType,
|
| 125 |
+
resourceName,
|
| 126 |
+
fullUrl: parsedUrl.href
|
| 127 |
+
};
|
| 128 |
+
} catch (error: any) {
|
| 129 |
+
throw new Error(`Invalid Hugging Face URL: ${error.message}`);
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
/**
|
| 134 |
+
* Get the avatar URL for a Hugging Face user/organization
|
| 135 |
+
* @param {string} username - The username or organization name
|
| 136 |
+
* @returns {string} - The avatar URL
|
| 137 |
+
*/
|
| 138 |
+
export function getAvatarUrl(username: string): string {
|
| 139 |
+
// Hugging Face avatar URL pattern
|
| 140 |
+
return `https://huggingface.co/avatars/${username}.svg`;
|
| 141 |
+
}
|
lib/utils.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { clsx, type ClassValue } from "clsx"
|
| 2 |
+
import { twMerge } from "tailwind-merge"
|
| 3 |
+
|
| 4 |
+
export function cn(...inputs: ClassValue[]) {
|
| 5 |
+
return twMerge(clsx(inputs))
|
| 6 |
+
}
|
package-lock.json
CHANGED
|
@@ -8,18 +8,32 @@
|
|
| 8 |
"name": "qr-code",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"next": "16.0.1",
|
|
|
|
|
|
|
| 12 |
"react": "19.2.0",
|
| 13 |
-
"react-dom": "19.2.0"
|
|
|
|
|
|
|
| 14 |
},
|
| 15 |
"devDependencies": {
|
| 16 |
"@tailwindcss/postcss": "^4",
|
|
|
|
| 17 |
"@types/node": "^20",
|
| 18 |
"@types/react": "^19",
|
| 19 |
"@types/react-dom": "^19",
|
| 20 |
"eslint": "^9",
|
| 21 |
"eslint-config-next": "16.0.1",
|
| 22 |
"tailwindcss": "^4",
|
|
|
|
| 23 |
"typescript": "^5"
|
| 24 |
}
|
| 25 |
},
|
|
@@ -1214,6 +1228,152 @@
|
|
| 1214 |
"node": ">=12.4.0"
|
| 1215 |
}
|
| 1216 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1217 |
"node_modules/@rtsao/scc": {
|
| 1218 |
"version": "1.1.0",
|
| 1219 |
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
@@ -1519,6 +1679,13 @@
|
|
| 1519 |
"dev": true,
|
| 1520 |
"license": "MIT"
|
| 1521 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1522 |
"node_modules/@types/json-schema": {
|
| 1523 |
"version": "7.0.15",
|
| 1524 |
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
|
@@ -1547,7 +1714,7 @@
|
|
| 1547 |
"version": "19.2.2",
|
| 1548 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
| 1549 |
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
| 1550 |
-
"
|
| 1551 |
"license": "MIT",
|
| 1552 |
"dependencies": {
|
| 1553 |
"csstype": "^3.0.2"
|
|
@@ -1557,7 +1724,7 @@
|
|
| 1557 |
"version": "19.2.2",
|
| 1558 |
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
|
| 1559 |
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
| 1560 |
-
"
|
| 1561 |
"license": "MIT",
|
| 1562 |
"peerDependencies": {
|
| 1563 |
"@types/react": "^19.2.0"
|
|
@@ -2383,6 +2550,12 @@
|
|
| 2383 |
"node": ">= 0.4"
|
| 2384 |
}
|
| 2385 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2386 |
"node_modules/available-typed-arrays": {
|
| 2387 |
"version": "1.0.7",
|
| 2388 |
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
|
@@ -2409,6 +2582,17 @@
|
|
| 2409 |
"node": ">=4"
|
| 2410 |
}
|
| 2411 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2412 |
"node_modules/axobject-query": {
|
| 2413 |
"version": "4.1.0",
|
| 2414 |
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
|
@@ -2517,7 +2701,6 @@
|
|
| 2517 |
"version": "1.0.2",
|
| 2518 |
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 2519 |
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 2520 |
-
"dev": true,
|
| 2521 |
"license": "MIT",
|
| 2522 |
"dependencies": {
|
| 2523 |
"es-errors": "^1.3.0",
|
|
@@ -2591,12 +2774,33 @@
|
|
| 2591 |
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 2592 |
}
|
| 2593 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2594 |
"node_modules/client-only": {
|
| 2595 |
"version": "0.0.1",
|
| 2596 |
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
| 2597 |
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
| 2598 |
"license": "MIT"
|
| 2599 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2600 |
"node_modules/color-convert": {
|
| 2601 |
"version": "2.0.1",
|
| 2602 |
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
@@ -2617,6 +2821,18 @@
|
|
| 2617 |
"dev": true,
|
| 2618 |
"license": "MIT"
|
| 2619 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2620 |
"node_modules/concat-map": {
|
| 2621 |
"version": "0.0.1",
|
| 2622 |
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
@@ -2650,7 +2866,7 @@
|
|
| 2650 |
"version": "3.1.3",
|
| 2651 |
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
| 2652 |
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
| 2653 |
-
"
|
| 2654 |
"license": "MIT"
|
| 2655 |
},
|
| 2656 |
"node_modules/damerau-levenshtein": {
|
|
@@ -2775,6 +2991,15 @@
|
|
| 2775 |
"url": "https://github.com/sponsors/ljharb"
|
| 2776 |
}
|
| 2777 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2778 |
"node_modules/detect-libc": {
|
| 2779 |
"version": "2.1.2",
|
| 2780 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
@@ -2802,7 +3027,6 @@
|
|
| 2802 |
"version": "1.0.1",
|
| 2803 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 2804 |
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 2805 |
-
"dev": true,
|
| 2806 |
"license": "MIT",
|
| 2807 |
"dependencies": {
|
| 2808 |
"call-bind-apply-helpers": "^1.0.1",
|
|
@@ -2914,7 +3138,6 @@
|
|
| 2914 |
"version": "1.0.1",
|
| 2915 |
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 2916 |
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 2917 |
-
"dev": true,
|
| 2918 |
"license": "MIT",
|
| 2919 |
"engines": {
|
| 2920 |
"node": ">= 0.4"
|
|
@@ -2924,7 +3147,6 @@
|
|
| 2924 |
"version": "1.3.0",
|
| 2925 |
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 2926 |
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 2927 |
-
"dev": true,
|
| 2928 |
"license": "MIT",
|
| 2929 |
"engines": {
|
| 2930 |
"node": ">= 0.4"
|
|
@@ -2962,7 +3184,6 @@
|
|
| 2962 |
"version": "1.1.1",
|
| 2963 |
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 2964 |
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
| 2965 |
-
"dev": true,
|
| 2966 |
"license": "MIT",
|
| 2967 |
"dependencies": {
|
| 2968 |
"es-errors": "^1.3.0"
|
|
@@ -2975,7 +3196,6 @@
|
|
| 2975 |
"version": "2.1.0",
|
| 2976 |
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
| 2977 |
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
| 2978 |
-
"dev": true,
|
| 2979 |
"license": "MIT",
|
| 2980 |
"dependencies": {
|
| 2981 |
"es-errors": "^1.3.0",
|
|
@@ -3539,6 +3759,12 @@
|
|
| 3539 |
"node": ">=16.0.0"
|
| 3540 |
}
|
| 3541 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3542 |
"node_modules/fill-range": {
|
| 3543 |
"version": "7.1.1",
|
| 3544 |
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
@@ -3590,6 +3816,26 @@
|
|
| 3590 |
"dev": true,
|
| 3591 |
"license": "ISC"
|
| 3592 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3593 |
"node_modules/for-each": {
|
| 3594 |
"version": "0.3.5",
|
| 3595 |
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
|
@@ -3606,11 +3852,26 @@
|
|
| 3606 |
"url": "https://github.com/sponsors/ljharb"
|
| 3607 |
}
|
| 3608 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3609 |
"node_modules/function-bind": {
|
| 3610 |
"version": "1.1.2",
|
| 3611 |
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 3612 |
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 3613 |
-
"dev": true,
|
| 3614 |
"license": "MIT",
|
| 3615 |
"funding": {
|
| 3616 |
"url": "https://github.com/sponsors/ljharb"
|
|
@@ -3671,7 +3932,6 @@
|
|
| 3671 |
"version": "1.3.0",
|
| 3672 |
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 3673 |
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 3674 |
-
"dev": true,
|
| 3675 |
"license": "MIT",
|
| 3676 |
"dependencies": {
|
| 3677 |
"call-bind-apply-helpers": "^1.0.2",
|
|
@@ -3696,7 +3956,6 @@
|
|
| 3696 |
"version": "1.0.1",
|
| 3697 |
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 3698 |
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 3699 |
-
"dev": true,
|
| 3700 |
"license": "MIT",
|
| 3701 |
"dependencies": {
|
| 3702 |
"dunder-proto": "^1.0.1",
|
|
@@ -3784,7 +4043,6 @@
|
|
| 3784 |
"version": "1.2.0",
|
| 3785 |
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 3786 |
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 3787 |
-
"dev": true,
|
| 3788 |
"license": "MIT",
|
| 3789 |
"engines": {
|
| 3790 |
"node": ">= 0.4"
|
|
@@ -3863,7 +4121,6 @@
|
|
| 3863 |
"version": "1.1.0",
|
| 3864 |
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 3865 |
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 3866 |
-
"dev": true,
|
| 3867 |
"license": "MIT",
|
| 3868 |
"engines": {
|
| 3869 |
"node": ">= 0.4"
|
|
@@ -3876,7 +4133,6 @@
|
|
| 3876 |
"version": "1.0.2",
|
| 3877 |
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
| 3878 |
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
| 3879 |
-
"dev": true,
|
| 3880 |
"license": "MIT",
|
| 3881 |
"dependencies": {
|
| 3882 |
"has-symbols": "^1.0.3"
|
|
@@ -3892,7 +4148,6 @@
|
|
| 3892 |
"version": "2.0.2",
|
| 3893 |
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 3894 |
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 3895 |
-
"dev": true,
|
| 3896 |
"license": "MIT",
|
| 3897 |
"dependencies": {
|
| 3898 |
"function-bind": "^1.1.2"
|
|
@@ -3918,6 +4173,12 @@
|
|
| 3918 |
"hermes-estree": "0.25.1"
|
| 3919 |
}
|
| 3920 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3921 |
"node_modules/ignore": {
|
| 3922 |
"version": "5.3.2",
|
| 3923 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
@@ -4431,7 +4692,6 @@
|
|
| 4431 |
"version": "4.0.0",
|
| 4432 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 4433 |
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 4434 |
-
"dev": true,
|
| 4435 |
"license": "MIT"
|
| 4436 |
},
|
| 4437 |
"node_modules/js-yaml": {
|
|
@@ -4842,7 +5102,6 @@
|
|
| 4842 |
"version": "1.4.0",
|
| 4843 |
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
| 4844 |
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
| 4845 |
-
"dev": true,
|
| 4846 |
"license": "MIT",
|
| 4847 |
"dependencies": {
|
| 4848 |
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
@@ -4861,6 +5120,15 @@
|
|
| 4861 |
"yallist": "^3.0.2"
|
| 4862 |
}
|
| 4863 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4864 |
"node_modules/magic-string": {
|
| 4865 |
"version": "0.30.21",
|
| 4866 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
|
@@ -4875,7 +5143,6 @@
|
|
| 4875 |
"version": "1.1.0",
|
| 4876 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 4877 |
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 4878 |
-
"dev": true,
|
| 4879 |
"license": "MIT",
|
| 4880 |
"engines": {
|
| 4881 |
"node": ">= 0.4"
|
|
@@ -4905,6 +5172,27 @@
|
|
| 4905 |
"node": ">=8.6"
|
| 4906 |
}
|
| 4907 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4908 |
"node_modules/minimatch": {
|
| 4909 |
"version": "3.1.2",
|
| 4910 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
|
@@ -5067,7 +5355,6 @@
|
|
| 5067 |
"version": "4.1.1",
|
| 5068 |
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 5069 |
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 5070 |
-
"dev": true,
|
| 5071 |
"license": "MIT",
|
| 5072 |
"engines": {
|
| 5073 |
"node": ">=0.10.0"
|
|
@@ -5366,7 +5653,6 @@
|
|
| 5366 |
"version": "15.8.1",
|
| 5367 |
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
| 5368 |
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
| 5369 |
-
"dev": true,
|
| 5370 |
"license": "MIT",
|
| 5371 |
"dependencies": {
|
| 5372 |
"loose-envify": "^1.4.0",
|
|
@@ -5374,6 +5660,12 @@
|
|
| 5374 |
"react-is": "^16.13.1"
|
| 5375 |
}
|
| 5376 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5377 |
"node_modules/punycode": {
|
| 5378 |
"version": "2.3.1",
|
| 5379 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
@@ -5384,6 +5676,36 @@
|
|
| 5384 |
"node": ">=6"
|
| 5385 |
}
|
| 5386 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5387 |
"node_modules/queue-microtask": {
|
| 5388 |
"version": "1.2.3",
|
| 5389 |
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
|
@@ -5430,9 +5752,21 @@
|
|
| 5430 |
"version": "16.13.1",
|
| 5431 |
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
| 5432 |
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
| 5433 |
-
"dev": true,
|
| 5434 |
"license": "MIT"
|
| 5435 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5436 |
"node_modules/reflect.getprototypeof": {
|
| 5437 |
"version": "1.0.10",
|
| 5438 |
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
|
@@ -6043,6 +6377,16 @@
|
|
| 6043 |
"url": "https://github.com/sponsors/ljharb"
|
| 6044 |
}
|
| 6045 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6046 |
"node_modules/tailwindcss": {
|
| 6047 |
"version": "4.1.16",
|
| 6048 |
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
|
|
@@ -6170,6 +6514,16 @@
|
|
| 6170 |
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 6171 |
"license": "0BSD"
|
| 6172 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6173 |
"node_modules/type-check": {
|
| 6174 |
"version": "0.4.0",
|
| 6175 |
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
|
@@ -6401,6 +6755,15 @@
|
|
| 6401 |
"punycode": "^2.1.0"
|
| 6402 |
}
|
| 6403 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6404 |
"node_modules/which": {
|
| 6405 |
"version": "2.0.2",
|
| 6406 |
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
|
|
|
| 8 |
"name": "qr-code",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"@radix-ui/react-avatar": "^1.1.10",
|
| 12 |
+
"@radix-ui/react-slot": "^1.2.3",
|
| 13 |
+
"axios": "^1.13.1",
|
| 14 |
+
"class-variance-authority": "^0.7.1",
|
| 15 |
+
"clsx": "^2.1.1",
|
| 16 |
+
"file-saver": "^2.0.5",
|
| 17 |
+
"html-to-image": "^1.11.13",
|
| 18 |
+
"lucide-react": "^0.552.0",
|
| 19 |
"next": "16.0.1",
|
| 20 |
+
"qr-code-styling": "^1.9.2",
|
| 21 |
+
"qrcode-generator": "^2.0.4",
|
| 22 |
"react": "19.2.0",
|
| 23 |
+
"react-dom": "19.2.0",
|
| 24 |
+
"react-qr-code": "^2.0.18",
|
| 25 |
+
"tailwind-merge": "^3.3.1"
|
| 26 |
},
|
| 27 |
"devDependencies": {
|
| 28 |
"@tailwindcss/postcss": "^4",
|
| 29 |
+
"@types/file-saver": "^2.0.7",
|
| 30 |
"@types/node": "^20",
|
| 31 |
"@types/react": "^19",
|
| 32 |
"@types/react-dom": "^19",
|
| 33 |
"eslint": "^9",
|
| 34 |
"eslint-config-next": "16.0.1",
|
| 35 |
"tailwindcss": "^4",
|
| 36 |
+
"tw-animate-css": "^1.4.0",
|
| 37 |
"typescript": "^5"
|
| 38 |
}
|
| 39 |
},
|
|
|
|
| 1228 |
"node": ">=12.4.0"
|
| 1229 |
}
|
| 1230 |
},
|
| 1231 |
+
"node_modules/@radix-ui/react-avatar": {
|
| 1232 |
+
"version": "1.1.10",
|
| 1233 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
|
| 1234 |
+
"integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==",
|
| 1235 |
+
"license": "MIT",
|
| 1236 |
+
"dependencies": {
|
| 1237 |
+
"@radix-ui/react-context": "1.1.2",
|
| 1238 |
+
"@radix-ui/react-primitive": "2.1.3",
|
| 1239 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
| 1240 |
+
"@radix-ui/react-use-is-hydrated": "0.1.0",
|
| 1241 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
| 1242 |
+
},
|
| 1243 |
+
"peerDependencies": {
|
| 1244 |
+
"@types/react": "*",
|
| 1245 |
+
"@types/react-dom": "*",
|
| 1246 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
| 1247 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1248 |
+
},
|
| 1249 |
+
"peerDependenciesMeta": {
|
| 1250 |
+
"@types/react": {
|
| 1251 |
+
"optional": true
|
| 1252 |
+
},
|
| 1253 |
+
"@types/react-dom": {
|
| 1254 |
+
"optional": true
|
| 1255 |
+
}
|
| 1256 |
+
}
|
| 1257 |
+
},
|
| 1258 |
+
"node_modules/@radix-ui/react-compose-refs": {
|
| 1259 |
+
"version": "1.1.2",
|
| 1260 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
| 1261 |
+
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
| 1262 |
+
"license": "MIT",
|
| 1263 |
+
"peerDependencies": {
|
| 1264 |
+
"@types/react": "*",
|
| 1265 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1266 |
+
},
|
| 1267 |
+
"peerDependenciesMeta": {
|
| 1268 |
+
"@types/react": {
|
| 1269 |
+
"optional": true
|
| 1270 |
+
}
|
| 1271 |
+
}
|
| 1272 |
+
},
|
| 1273 |
+
"node_modules/@radix-ui/react-context": {
|
| 1274 |
+
"version": "1.1.2",
|
| 1275 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
| 1276 |
+
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
| 1277 |
+
"license": "MIT",
|
| 1278 |
+
"peerDependencies": {
|
| 1279 |
+
"@types/react": "*",
|
| 1280 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1281 |
+
},
|
| 1282 |
+
"peerDependenciesMeta": {
|
| 1283 |
+
"@types/react": {
|
| 1284 |
+
"optional": true
|
| 1285 |
+
}
|
| 1286 |
+
}
|
| 1287 |
+
},
|
| 1288 |
+
"node_modules/@radix-ui/react-primitive": {
|
| 1289 |
+
"version": "2.1.3",
|
| 1290 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
| 1291 |
+
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
| 1292 |
+
"license": "MIT",
|
| 1293 |
+
"dependencies": {
|
| 1294 |
+
"@radix-ui/react-slot": "1.2.3"
|
| 1295 |
+
},
|
| 1296 |
+
"peerDependencies": {
|
| 1297 |
+
"@types/react": "*",
|
| 1298 |
+
"@types/react-dom": "*",
|
| 1299 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
| 1300 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1301 |
+
},
|
| 1302 |
+
"peerDependenciesMeta": {
|
| 1303 |
+
"@types/react": {
|
| 1304 |
+
"optional": true
|
| 1305 |
+
},
|
| 1306 |
+
"@types/react-dom": {
|
| 1307 |
+
"optional": true
|
| 1308 |
+
}
|
| 1309 |
+
}
|
| 1310 |
+
},
|
| 1311 |
+
"node_modules/@radix-ui/react-slot": {
|
| 1312 |
+
"version": "1.2.3",
|
| 1313 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
| 1314 |
+
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
| 1315 |
+
"license": "MIT",
|
| 1316 |
+
"dependencies": {
|
| 1317 |
+
"@radix-ui/react-compose-refs": "1.1.2"
|
| 1318 |
+
},
|
| 1319 |
+
"peerDependencies": {
|
| 1320 |
+
"@types/react": "*",
|
| 1321 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1322 |
+
},
|
| 1323 |
+
"peerDependenciesMeta": {
|
| 1324 |
+
"@types/react": {
|
| 1325 |
+
"optional": true
|
| 1326 |
+
}
|
| 1327 |
+
}
|
| 1328 |
+
},
|
| 1329 |
+
"node_modules/@radix-ui/react-use-callback-ref": {
|
| 1330 |
+
"version": "1.1.1",
|
| 1331 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
| 1332 |
+
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
| 1333 |
+
"license": "MIT",
|
| 1334 |
+
"peerDependencies": {
|
| 1335 |
+
"@types/react": "*",
|
| 1336 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1337 |
+
},
|
| 1338 |
+
"peerDependenciesMeta": {
|
| 1339 |
+
"@types/react": {
|
| 1340 |
+
"optional": true
|
| 1341 |
+
}
|
| 1342 |
+
}
|
| 1343 |
+
},
|
| 1344 |
+
"node_modules/@radix-ui/react-use-is-hydrated": {
|
| 1345 |
+
"version": "0.1.0",
|
| 1346 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
|
| 1347 |
+
"integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
|
| 1348 |
+
"license": "MIT",
|
| 1349 |
+
"dependencies": {
|
| 1350 |
+
"use-sync-external-store": "^1.5.0"
|
| 1351 |
+
},
|
| 1352 |
+
"peerDependencies": {
|
| 1353 |
+
"@types/react": "*",
|
| 1354 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1355 |
+
},
|
| 1356 |
+
"peerDependenciesMeta": {
|
| 1357 |
+
"@types/react": {
|
| 1358 |
+
"optional": true
|
| 1359 |
+
}
|
| 1360 |
+
}
|
| 1361 |
+
},
|
| 1362 |
+
"node_modules/@radix-ui/react-use-layout-effect": {
|
| 1363 |
+
"version": "1.1.1",
|
| 1364 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
| 1365 |
+
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
| 1366 |
+
"license": "MIT",
|
| 1367 |
+
"peerDependencies": {
|
| 1368 |
+
"@types/react": "*",
|
| 1369 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
| 1370 |
+
},
|
| 1371 |
+
"peerDependenciesMeta": {
|
| 1372 |
+
"@types/react": {
|
| 1373 |
+
"optional": true
|
| 1374 |
+
}
|
| 1375 |
+
}
|
| 1376 |
+
},
|
| 1377 |
"node_modules/@rtsao/scc": {
|
| 1378 |
"version": "1.1.0",
|
| 1379 |
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
|
|
| 1679 |
"dev": true,
|
| 1680 |
"license": "MIT"
|
| 1681 |
},
|
| 1682 |
+
"node_modules/@types/file-saver": {
|
| 1683 |
+
"version": "2.0.7",
|
| 1684 |
+
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
|
| 1685 |
+
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
| 1686 |
+
"dev": true,
|
| 1687 |
+
"license": "MIT"
|
| 1688 |
+
},
|
| 1689 |
"node_modules/@types/json-schema": {
|
| 1690 |
"version": "7.0.15",
|
| 1691 |
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
|
|
|
| 1714 |
"version": "19.2.2",
|
| 1715 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
| 1716 |
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
| 1717 |
+
"devOptional": true,
|
| 1718 |
"license": "MIT",
|
| 1719 |
"dependencies": {
|
| 1720 |
"csstype": "^3.0.2"
|
|
|
|
| 1724 |
"version": "19.2.2",
|
| 1725 |
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
|
| 1726 |
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
| 1727 |
+
"devOptional": true,
|
| 1728 |
"license": "MIT",
|
| 1729 |
"peerDependencies": {
|
| 1730 |
"@types/react": "^19.2.0"
|
|
|
|
| 2550 |
"node": ">= 0.4"
|
| 2551 |
}
|
| 2552 |
},
|
| 2553 |
+
"node_modules/asynckit": {
|
| 2554 |
+
"version": "0.4.0",
|
| 2555 |
+
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
| 2556 |
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
| 2557 |
+
"license": "MIT"
|
| 2558 |
+
},
|
| 2559 |
"node_modules/available-typed-arrays": {
|
| 2560 |
"version": "1.0.7",
|
| 2561 |
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
|
|
|
| 2582 |
"node": ">=4"
|
| 2583 |
}
|
| 2584 |
},
|
| 2585 |
+
"node_modules/axios": {
|
| 2586 |
+
"version": "1.13.1",
|
| 2587 |
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
|
| 2588 |
+
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
| 2589 |
+
"license": "MIT",
|
| 2590 |
+
"dependencies": {
|
| 2591 |
+
"follow-redirects": "^1.15.6",
|
| 2592 |
+
"form-data": "^4.0.4",
|
| 2593 |
+
"proxy-from-env": "^1.1.0"
|
| 2594 |
+
}
|
| 2595 |
+
},
|
| 2596 |
"node_modules/axobject-query": {
|
| 2597 |
"version": "4.1.0",
|
| 2598 |
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
|
|
|
| 2701 |
"version": "1.0.2",
|
| 2702 |
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 2703 |
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
|
|
| 2704 |
"license": "MIT",
|
| 2705 |
"dependencies": {
|
| 2706 |
"es-errors": "^1.3.0",
|
|
|
|
| 2774 |
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 2775 |
}
|
| 2776 |
},
|
| 2777 |
+
"node_modules/class-variance-authority": {
|
| 2778 |
+
"version": "0.7.1",
|
| 2779 |
+
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
| 2780 |
+
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
| 2781 |
+
"license": "Apache-2.0",
|
| 2782 |
+
"dependencies": {
|
| 2783 |
+
"clsx": "^2.1.1"
|
| 2784 |
+
},
|
| 2785 |
+
"funding": {
|
| 2786 |
+
"url": "https://polar.sh/cva"
|
| 2787 |
+
}
|
| 2788 |
+
},
|
| 2789 |
"node_modules/client-only": {
|
| 2790 |
"version": "0.0.1",
|
| 2791 |
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
| 2792 |
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
| 2793 |
"license": "MIT"
|
| 2794 |
},
|
| 2795 |
+
"node_modules/clsx": {
|
| 2796 |
+
"version": "2.1.1",
|
| 2797 |
+
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
| 2798 |
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
| 2799 |
+
"license": "MIT",
|
| 2800 |
+
"engines": {
|
| 2801 |
+
"node": ">=6"
|
| 2802 |
+
}
|
| 2803 |
+
},
|
| 2804 |
"node_modules/color-convert": {
|
| 2805 |
"version": "2.0.1",
|
| 2806 |
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
|
|
| 2821 |
"dev": true,
|
| 2822 |
"license": "MIT"
|
| 2823 |
},
|
| 2824 |
+
"node_modules/combined-stream": {
|
| 2825 |
+
"version": "1.0.8",
|
| 2826 |
+
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
| 2827 |
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
| 2828 |
+
"license": "MIT",
|
| 2829 |
+
"dependencies": {
|
| 2830 |
+
"delayed-stream": "~1.0.0"
|
| 2831 |
+
},
|
| 2832 |
+
"engines": {
|
| 2833 |
+
"node": ">= 0.8"
|
| 2834 |
+
}
|
| 2835 |
+
},
|
| 2836 |
"node_modules/concat-map": {
|
| 2837 |
"version": "0.0.1",
|
| 2838 |
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
|
|
| 2866 |
"version": "3.1.3",
|
| 2867 |
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
| 2868 |
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
| 2869 |
+
"devOptional": true,
|
| 2870 |
"license": "MIT"
|
| 2871 |
},
|
| 2872 |
"node_modules/damerau-levenshtein": {
|
|
|
|
| 2991 |
"url": "https://github.com/sponsors/ljharb"
|
| 2992 |
}
|
| 2993 |
},
|
| 2994 |
+
"node_modules/delayed-stream": {
|
| 2995 |
+
"version": "1.0.0",
|
| 2996 |
+
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
| 2997 |
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
| 2998 |
+
"license": "MIT",
|
| 2999 |
+
"engines": {
|
| 3000 |
+
"node": ">=0.4.0"
|
| 3001 |
+
}
|
| 3002 |
+
},
|
| 3003 |
"node_modules/detect-libc": {
|
| 3004 |
"version": "2.1.2",
|
| 3005 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
|
|
| 3027 |
"version": "1.0.1",
|
| 3028 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 3029 |
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
|
|
| 3030 |
"license": "MIT",
|
| 3031 |
"dependencies": {
|
| 3032 |
"call-bind-apply-helpers": "^1.0.1",
|
|
|
|
| 3138 |
"version": "1.0.1",
|
| 3139 |
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 3140 |
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
|
|
|
| 3141 |
"license": "MIT",
|
| 3142 |
"engines": {
|
| 3143 |
"node": ">= 0.4"
|
|
|
|
| 3147 |
"version": "1.3.0",
|
| 3148 |
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 3149 |
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
|
|
|
| 3150 |
"license": "MIT",
|
| 3151 |
"engines": {
|
| 3152 |
"node": ">= 0.4"
|
|
|
|
| 3184 |
"version": "1.1.1",
|
| 3185 |
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 3186 |
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
|
|
|
| 3187 |
"license": "MIT",
|
| 3188 |
"dependencies": {
|
| 3189 |
"es-errors": "^1.3.0"
|
|
|
|
| 3196 |
"version": "2.1.0",
|
| 3197 |
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
| 3198 |
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
|
|
|
| 3199 |
"license": "MIT",
|
| 3200 |
"dependencies": {
|
| 3201 |
"es-errors": "^1.3.0",
|
|
|
|
| 3759 |
"node": ">=16.0.0"
|
| 3760 |
}
|
| 3761 |
},
|
| 3762 |
+
"node_modules/file-saver": {
|
| 3763 |
+
"version": "2.0.5",
|
| 3764 |
+
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
| 3765 |
+
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
|
| 3766 |
+
"license": "MIT"
|
| 3767 |
+
},
|
| 3768 |
"node_modules/fill-range": {
|
| 3769 |
"version": "7.1.1",
|
| 3770 |
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
|
|
| 3816 |
"dev": true,
|
| 3817 |
"license": "ISC"
|
| 3818 |
},
|
| 3819 |
+
"node_modules/follow-redirects": {
|
| 3820 |
+
"version": "1.15.11",
|
| 3821 |
+
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
| 3822 |
+
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
| 3823 |
+
"funding": [
|
| 3824 |
+
{
|
| 3825 |
+
"type": "individual",
|
| 3826 |
+
"url": "https://github.com/sponsors/RubenVerborgh"
|
| 3827 |
+
}
|
| 3828 |
+
],
|
| 3829 |
+
"license": "MIT",
|
| 3830 |
+
"engines": {
|
| 3831 |
+
"node": ">=4.0"
|
| 3832 |
+
},
|
| 3833 |
+
"peerDependenciesMeta": {
|
| 3834 |
+
"debug": {
|
| 3835 |
+
"optional": true
|
| 3836 |
+
}
|
| 3837 |
+
}
|
| 3838 |
+
},
|
| 3839 |
"node_modules/for-each": {
|
| 3840 |
"version": "0.3.5",
|
| 3841 |
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
|
|
|
| 3852 |
"url": "https://github.com/sponsors/ljharb"
|
| 3853 |
}
|
| 3854 |
},
|
| 3855 |
+
"node_modules/form-data": {
|
| 3856 |
+
"version": "4.0.4",
|
| 3857 |
+
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
| 3858 |
+
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
| 3859 |
+
"license": "MIT",
|
| 3860 |
+
"dependencies": {
|
| 3861 |
+
"asynckit": "^0.4.0",
|
| 3862 |
+
"combined-stream": "^1.0.8",
|
| 3863 |
+
"es-set-tostringtag": "^2.1.0",
|
| 3864 |
+
"hasown": "^2.0.2",
|
| 3865 |
+
"mime-types": "^2.1.12"
|
| 3866 |
+
},
|
| 3867 |
+
"engines": {
|
| 3868 |
+
"node": ">= 6"
|
| 3869 |
+
}
|
| 3870 |
+
},
|
| 3871 |
"node_modules/function-bind": {
|
| 3872 |
"version": "1.1.2",
|
| 3873 |
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 3874 |
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
|
|
|
| 3875 |
"license": "MIT",
|
| 3876 |
"funding": {
|
| 3877 |
"url": "https://github.com/sponsors/ljharb"
|
|
|
|
| 3932 |
"version": "1.3.0",
|
| 3933 |
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 3934 |
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
|
|
| 3935 |
"license": "MIT",
|
| 3936 |
"dependencies": {
|
| 3937 |
"call-bind-apply-helpers": "^1.0.2",
|
|
|
|
| 3956 |
"version": "1.0.1",
|
| 3957 |
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 3958 |
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
|
|
| 3959 |
"license": "MIT",
|
| 3960 |
"dependencies": {
|
| 3961 |
"dunder-proto": "^1.0.1",
|
|
|
|
| 4043 |
"version": "1.2.0",
|
| 4044 |
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 4045 |
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
|
|
|
| 4046 |
"license": "MIT",
|
| 4047 |
"engines": {
|
| 4048 |
"node": ">= 0.4"
|
|
|
|
| 4121 |
"version": "1.1.0",
|
| 4122 |
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 4123 |
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
|
|
|
| 4124 |
"license": "MIT",
|
| 4125 |
"engines": {
|
| 4126 |
"node": ">= 0.4"
|
|
|
|
| 4133 |
"version": "1.0.2",
|
| 4134 |
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
| 4135 |
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
|
|
|
| 4136 |
"license": "MIT",
|
| 4137 |
"dependencies": {
|
| 4138 |
"has-symbols": "^1.0.3"
|
|
|
|
| 4148 |
"version": "2.0.2",
|
| 4149 |
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 4150 |
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
|
|
|
| 4151 |
"license": "MIT",
|
| 4152 |
"dependencies": {
|
| 4153 |
"function-bind": "^1.1.2"
|
|
|
|
| 4173 |
"hermes-estree": "0.25.1"
|
| 4174 |
}
|
| 4175 |
},
|
| 4176 |
+
"node_modules/html-to-image": {
|
| 4177 |
+
"version": "1.11.13",
|
| 4178 |
+
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
|
| 4179 |
+
"integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
|
| 4180 |
+
"license": "MIT"
|
| 4181 |
+
},
|
| 4182 |
"node_modules/ignore": {
|
| 4183 |
"version": "5.3.2",
|
| 4184 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
|
|
| 4692 |
"version": "4.0.0",
|
| 4693 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 4694 |
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
|
|
|
| 4695 |
"license": "MIT"
|
| 4696 |
},
|
| 4697 |
"node_modules/js-yaml": {
|
|
|
|
| 5102 |
"version": "1.4.0",
|
| 5103 |
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
| 5104 |
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
|
|
| 5105 |
"license": "MIT",
|
| 5106 |
"dependencies": {
|
| 5107 |
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
|
|
| 5120 |
"yallist": "^3.0.2"
|
| 5121 |
}
|
| 5122 |
},
|
| 5123 |
+
"node_modules/lucide-react": {
|
| 5124 |
+
"version": "0.552.0",
|
| 5125 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz",
|
| 5126 |
+
"integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==",
|
| 5127 |
+
"license": "ISC",
|
| 5128 |
+
"peerDependencies": {
|
| 5129 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 5130 |
+
}
|
| 5131 |
+
},
|
| 5132 |
"node_modules/magic-string": {
|
| 5133 |
"version": "0.30.21",
|
| 5134 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
|
|
|
| 5143 |
"version": "1.1.0",
|
| 5144 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 5145 |
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
|
|
|
| 5146 |
"license": "MIT",
|
| 5147 |
"engines": {
|
| 5148 |
"node": ">= 0.4"
|
|
|
|
| 5172 |
"node": ">=8.6"
|
| 5173 |
}
|
| 5174 |
},
|
| 5175 |
+
"node_modules/mime-db": {
|
| 5176 |
+
"version": "1.52.0",
|
| 5177 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 5178 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 5179 |
+
"license": "MIT",
|
| 5180 |
+
"engines": {
|
| 5181 |
+
"node": ">= 0.6"
|
| 5182 |
+
}
|
| 5183 |
+
},
|
| 5184 |
+
"node_modules/mime-types": {
|
| 5185 |
+
"version": "2.1.35",
|
| 5186 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 5187 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 5188 |
+
"license": "MIT",
|
| 5189 |
+
"dependencies": {
|
| 5190 |
+
"mime-db": "1.52.0"
|
| 5191 |
+
},
|
| 5192 |
+
"engines": {
|
| 5193 |
+
"node": ">= 0.6"
|
| 5194 |
+
}
|
| 5195 |
+
},
|
| 5196 |
"node_modules/minimatch": {
|
| 5197 |
"version": "3.1.2",
|
| 5198 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
|
|
|
| 5355 |
"version": "4.1.1",
|
| 5356 |
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 5357 |
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
|
|
|
| 5358 |
"license": "MIT",
|
| 5359 |
"engines": {
|
| 5360 |
"node": ">=0.10.0"
|
|
|
|
| 5653 |
"version": "15.8.1",
|
| 5654 |
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
| 5655 |
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
|
|
|
| 5656 |
"license": "MIT",
|
| 5657 |
"dependencies": {
|
| 5658 |
"loose-envify": "^1.4.0",
|
|
|
|
| 5660 |
"react-is": "^16.13.1"
|
| 5661 |
}
|
| 5662 |
},
|
| 5663 |
+
"node_modules/proxy-from-env": {
|
| 5664 |
+
"version": "1.1.0",
|
| 5665 |
+
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
| 5666 |
+
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
| 5667 |
+
"license": "MIT"
|
| 5668 |
+
},
|
| 5669 |
"node_modules/punycode": {
|
| 5670 |
"version": "2.3.1",
|
| 5671 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
|
|
| 5676 |
"node": ">=6"
|
| 5677 |
}
|
| 5678 |
},
|
| 5679 |
+
"node_modules/qr-code-styling": {
|
| 5680 |
+
"version": "1.9.2",
|
| 5681 |
+
"resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.9.2.tgz",
|
| 5682 |
+
"integrity": "sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==",
|
| 5683 |
+
"license": "MIT",
|
| 5684 |
+
"dependencies": {
|
| 5685 |
+
"qrcode-generator": "^1.4.4"
|
| 5686 |
+
},
|
| 5687 |
+
"engines": {
|
| 5688 |
+
"node": ">=18.18.0"
|
| 5689 |
+
}
|
| 5690 |
+
},
|
| 5691 |
+
"node_modules/qr-code-styling/node_modules/qrcode-generator": {
|
| 5692 |
+
"version": "1.5.2",
|
| 5693 |
+
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz",
|
| 5694 |
+
"integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==",
|
| 5695 |
+
"license": "MIT"
|
| 5696 |
+
},
|
| 5697 |
+
"node_modules/qr.js": {
|
| 5698 |
+
"version": "0.0.0",
|
| 5699 |
+
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
|
| 5700 |
+
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==",
|
| 5701 |
+
"license": "MIT"
|
| 5702 |
+
},
|
| 5703 |
+
"node_modules/qrcode-generator": {
|
| 5704 |
+
"version": "2.0.4",
|
| 5705 |
+
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz",
|
| 5706 |
+
"integrity": "sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==",
|
| 5707 |
+
"license": "MIT"
|
| 5708 |
+
},
|
| 5709 |
"node_modules/queue-microtask": {
|
| 5710 |
"version": "1.2.3",
|
| 5711 |
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
|
|
|
| 5752 |
"version": "16.13.1",
|
| 5753 |
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
| 5754 |
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
|
|
|
| 5755 |
"license": "MIT"
|
| 5756 |
},
|
| 5757 |
+
"node_modules/react-qr-code": {
|
| 5758 |
+
"version": "2.0.18",
|
| 5759 |
+
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.18.tgz",
|
| 5760 |
+
"integrity": "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==",
|
| 5761 |
+
"license": "MIT",
|
| 5762 |
+
"dependencies": {
|
| 5763 |
+
"prop-types": "^15.8.1",
|
| 5764 |
+
"qr.js": "0.0.0"
|
| 5765 |
+
},
|
| 5766 |
+
"peerDependencies": {
|
| 5767 |
+
"react": "*"
|
| 5768 |
+
}
|
| 5769 |
+
},
|
| 5770 |
"node_modules/reflect.getprototypeof": {
|
| 5771 |
"version": "1.0.10",
|
| 5772 |
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
|
|
|
| 6377 |
"url": "https://github.com/sponsors/ljharb"
|
| 6378 |
}
|
| 6379 |
},
|
| 6380 |
+
"node_modules/tailwind-merge": {
|
| 6381 |
+
"version": "3.3.1",
|
| 6382 |
+
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
|
| 6383 |
+
"integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
|
| 6384 |
+
"license": "MIT",
|
| 6385 |
+
"funding": {
|
| 6386 |
+
"type": "github",
|
| 6387 |
+
"url": "https://github.com/sponsors/dcastil"
|
| 6388 |
+
}
|
| 6389 |
+
},
|
| 6390 |
"node_modules/tailwindcss": {
|
| 6391 |
"version": "4.1.16",
|
| 6392 |
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
|
|
|
|
| 6514 |
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 6515 |
"license": "0BSD"
|
| 6516 |
},
|
| 6517 |
+
"node_modules/tw-animate-css": {
|
| 6518 |
+
"version": "1.4.0",
|
| 6519 |
+
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
|
| 6520 |
+
"integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
|
| 6521 |
+
"dev": true,
|
| 6522 |
+
"license": "MIT",
|
| 6523 |
+
"funding": {
|
| 6524 |
+
"url": "https://github.com/sponsors/Wombosvideo"
|
| 6525 |
+
}
|
| 6526 |
+
},
|
| 6527 |
"node_modules/type-check": {
|
| 6528 |
"version": "0.4.0",
|
| 6529 |
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
|
|
|
| 6755 |
"punycode": "^2.1.0"
|
| 6756 |
}
|
| 6757 |
},
|
| 6758 |
+
"node_modules/use-sync-external-store": {
|
| 6759 |
+
"version": "1.6.0",
|
| 6760 |
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 6761 |
+
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 6762 |
+
"license": "MIT",
|
| 6763 |
+
"peerDependencies": {
|
| 6764 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 6765 |
+
}
|
| 6766 |
+
},
|
| 6767 |
"node_modules/which": {
|
| 6768 |
"version": "2.0.2",
|
| 6769 |
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
package.json
CHANGED
|
@@ -9,18 +9,32 @@
|
|
| 9 |
"lint": "eslint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"react": "19.2.0",
|
| 13 |
"react-dom": "19.2.0",
|
| 14 |
-
"
|
|
|
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
-
"
|
|
|
|
| 18 |
"@types/node": "^20",
|
| 19 |
"@types/react": "^19",
|
| 20 |
"@types/react-dom": "^19",
|
| 21 |
-
"@tailwindcss/postcss": "^4",
|
| 22 |
-
"tailwindcss": "^4",
|
| 23 |
"eslint": "^9",
|
| 24 |
-
"eslint-config-next": "16.0.1"
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
| 26 |
-
}
|
|
|
|
| 9 |
"lint": "eslint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
+
"@radix-ui/react-avatar": "^1.1.10",
|
| 13 |
+
"@radix-ui/react-slot": "^1.2.3",
|
| 14 |
+
"axios": "^1.13.1",
|
| 15 |
+
"class-variance-authority": "^0.7.1",
|
| 16 |
+
"clsx": "^2.1.1",
|
| 17 |
+
"file-saver": "^2.0.5",
|
| 18 |
+
"html-to-image": "^1.11.13",
|
| 19 |
+
"lucide-react": "^0.552.0",
|
| 20 |
+
"next": "16.0.1",
|
| 21 |
+
"qr-code-styling": "^1.9.2",
|
| 22 |
+
"qrcode-generator": "^2.0.4",
|
| 23 |
"react": "19.2.0",
|
| 24 |
"react-dom": "19.2.0",
|
| 25 |
+
"react-qr-code": "^2.0.18",
|
| 26 |
+
"tailwind-merge": "^3.3.1"
|
| 27 |
},
|
| 28 |
"devDependencies": {
|
| 29 |
+
"@tailwindcss/postcss": "^4",
|
| 30 |
+
"@types/file-saver": "^2.0.7",
|
| 31 |
"@types/node": "^20",
|
| 32 |
"@types/react": "^19",
|
| 33 |
"@types/react-dom": "^19",
|
|
|
|
|
|
|
| 34 |
"eslint": "^9",
|
| 35 |
+
"eslint-config-next": "16.0.1",
|
| 36 |
+
"tailwindcss": "^4",
|
| 37 |
+
"tw-animate-css": "^1.4.0",
|
| 38 |
+
"typescript": "^5"
|
| 39 |
}
|
| 40 |
+
}
|
styles/qr-generator.css
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.qr-generator {
|
| 2 |
+
min-height: 100vh;
|
| 3 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 4 |
+
padding: 2rem;
|
| 5 |
+
display: flex;
|
| 6 |
+
flex-direction: column;
|
| 7 |
+
align-items: center;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.input-section {
|
| 11 |
+
width: 100%;
|
| 12 |
+
max-width: 800px;
|
| 13 |
+
background: white;
|
| 14 |
+
border-radius: 20px;
|
| 15 |
+
padding: 3rem;
|
| 16 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
| 17 |
+
margin-bottom: 2rem;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.title {
|
| 21 |
+
font-size: 2.5rem;
|
| 22 |
+
font-weight: 800;
|
| 23 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 24 |
+
-webkit-background-clip: text;
|
| 25 |
+
-webkit-text-fill-color: transparent;
|
| 26 |
+
background-clip: text;
|
| 27 |
+
text-align: center;
|
| 28 |
+
margin-bottom: 0.5rem;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.subtitle {
|
| 32 |
+
text-align: center;
|
| 33 |
+
color: #6b7280;
|
| 34 |
+
font-size: 1.1rem;
|
| 35 |
+
margin-bottom: 2rem;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.input-group {
|
| 39 |
+
display: flex;
|
| 40 |
+
gap: 1rem;
|
| 41 |
+
margin-bottom: 1rem;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.url-input {
|
| 45 |
+
flex: 1;
|
| 46 |
+
padding: 1rem 1.5rem;
|
| 47 |
+
font-size: 1rem;
|
| 48 |
+
border: 2px solid #e5e7eb;
|
| 49 |
+
border-radius: 12px;
|
| 50 |
+
transition: all 0.3s ease;
|
| 51 |
+
outline: none;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.url-input:focus {
|
| 55 |
+
border-color: #667eea;
|
| 56 |
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.generate-btn {
|
| 60 |
+
padding: 1rem 2rem;
|
| 61 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 62 |
+
color: white;
|
| 63 |
+
border: none;
|
| 64 |
+
border-radius: 12px;
|
| 65 |
+
font-size: 1rem;
|
| 66 |
+
font-weight: 600;
|
| 67 |
+
cursor: pointer;
|
| 68 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
| 69 |
+
white-space: nowrap;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.generate-btn:hover:not(:disabled) {
|
| 73 |
+
transform: translateY(-2px);
|
| 74 |
+
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.generate-btn:disabled {
|
| 78 |
+
opacity: 0.6;
|
| 79 |
+
cursor: not-allowed;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.error-message {
|
| 83 |
+
background: #fee2e2;
|
| 84 |
+
color: #dc2626;
|
| 85 |
+
padding: 0.75rem 1rem;
|
| 86 |
+
border-radius: 8px;
|
| 87 |
+
margin-top: 1rem;
|
| 88 |
+
font-size: 0.95rem;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.result-section {
|
| 92 |
+
width: 100%;
|
| 93 |
+
max-width: 800px;
|
| 94 |
+
background: white;
|
| 95 |
+
border-radius: 20px;
|
| 96 |
+
padding: 3rem;
|
| 97 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
| 98 |
+
animation: slideUp 0.5s ease;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
@keyframes slideUp {
|
| 102 |
+
from {
|
| 103 |
+
opacity: 0;
|
| 104 |
+
transform: translateY(20px);
|
| 105 |
+
}
|
| 106 |
+
to {
|
| 107 |
+
opacity: 1;
|
| 108 |
+
transform: translateY(0);
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.profile-info {
|
| 113 |
+
display: flex;
|
| 114 |
+
align-items: center;
|
| 115 |
+
gap: 1.5rem;
|
| 116 |
+
margin-bottom: 2rem;
|
| 117 |
+
padding-bottom: 2rem;
|
| 118 |
+
border-bottom: 2px solid #f3f4f6;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.profile-avatar {
|
| 122 |
+
width: 80px;
|
| 123 |
+
height: 80px;
|
| 124 |
+
border-radius: 50%;
|
| 125 |
+
object-fit: cover;
|
| 126 |
+
border: 3px solid #667eea;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.profile-details h2 {
|
| 130 |
+
font-size: 1.5rem;
|
| 131 |
+
font-weight: 700;
|
| 132 |
+
color: #1f2937;
|
| 133 |
+
margin-bottom: 0.25rem;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.profile-username {
|
| 137 |
+
color: #6b7280;
|
| 138 |
+
font-size: 1rem;
|
| 139 |
+
margin-bottom: 0.5rem;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.profile-link {
|
| 143 |
+
color: #667eea;
|
| 144 |
+
text-decoration: none;
|
| 145 |
+
font-weight: 500;
|
| 146 |
+
display: inline-flex;
|
| 147 |
+
align-items: center;
|
| 148 |
+
gap: 0.25rem;
|
| 149 |
+
transition: gap 0.2s ease;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.profile-link:hover {
|
| 153 |
+
gap: 0.5rem;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.qr-code-section {
|
| 157 |
+
display: flex;
|
| 158 |
+
justify-content: center;
|
| 159 |
+
margin: 2rem 0;
|
| 160 |
+
padding: 0;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.qr-code-wrapper {
|
| 164 |
+
background: white;
|
| 165 |
+
border-radius: 16px;
|
| 166 |
+
overflow: hidden;
|
| 167 |
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.qr-code-container {
|
| 171 |
+
background: white;
|
| 172 |
+
padding: 2rem;
|
| 173 |
+
display: flex;
|
| 174 |
+
justify-content: center;
|
| 175 |
+
align-items: center;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.profile-bar {
|
| 179 |
+
display: flex;
|
| 180 |
+
align-items: center;
|
| 181 |
+
gap: 1rem;
|
| 182 |
+
padding: 1.5rem 2rem;
|
| 183 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 184 |
+
color: white;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.profile-bar-avatar {
|
| 188 |
+
width: 50px;
|
| 189 |
+
height: 50px;
|
| 190 |
+
border-radius: 50%;
|
| 191 |
+
border: 3px solid white;
|
| 192 |
+
object-fit: cover;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.profile-bar-info {
|
| 196 |
+
flex: 1;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.profile-bar-name {
|
| 200 |
+
font-size: 1.1rem;
|
| 201 |
+
font-weight: 700;
|
| 202 |
+
margin: 0 0 0.25rem 0;
|
| 203 |
+
color: white;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.profile-bar-username {
|
| 207 |
+
font-size: 0.9rem;
|
| 208 |
+
opacity: 0.9;
|
| 209 |
+
margin: 0;
|
| 210 |
+
color: white;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.profile-bar-details {
|
| 214 |
+
display: flex;
|
| 215 |
+
align-items: center;
|
| 216 |
+
gap: 0.75rem;
|
| 217 |
+
flex-wrap: wrap;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.resource-type-badge {
|
| 221 |
+
display: inline-flex;
|
| 222 |
+
align-items: center;
|
| 223 |
+
padding: 0.2rem 0.5rem;
|
| 224 |
+
background: rgba(255, 255, 255, 0.2);
|
| 225 |
+
border-radius: 6px;
|
| 226 |
+
font-size: 0.8rem;
|
| 227 |
+
font-weight: 600;
|
| 228 |
+
color: white;
|
| 229 |
+
text-transform: capitalize;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.resource-name {
|
| 233 |
+
font-size: 0.9rem;
|
| 234 |
+
color: white;
|
| 235 |
+
font-weight: 500;
|
| 236 |
+
overflow: hidden;
|
| 237 |
+
text-overflow: ellipsis;
|
| 238 |
+
white-space: nowrap;
|
| 239 |
+
max-width: 200px;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.actions-section {
|
| 243 |
+
display: grid;
|
| 244 |
+
grid-template-columns: 1fr 1fr;
|
| 245 |
+
gap: 2rem;
|
| 246 |
+
margin-top: 2rem;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.download-actions h3,
|
| 250 |
+
.share-actions h3 {
|
| 251 |
+
font-size: 1.1rem;
|
| 252 |
+
font-weight: 600;
|
| 253 |
+
color: #374151;
|
| 254 |
+
margin-bottom: 1rem;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.button-group {
|
| 258 |
+
display: flex;
|
| 259 |
+
gap: 1rem;
|
| 260 |
+
flex-wrap: wrap;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.download-btn {
|
| 264 |
+
flex: 1;
|
| 265 |
+
padding: 0.75rem 1.5rem;
|
| 266 |
+
background: #6b7280;
|
| 267 |
+
color: white;
|
| 268 |
+
border: none;
|
| 269 |
+
border-radius: 8px;
|
| 270 |
+
font-weight: 500;
|
| 271 |
+
cursor: pointer;
|
| 272 |
+
transition: all 0.2s ease;
|
| 273 |
+
min-width: 120px;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
.download-btn:hover {
|
| 277 |
+
background: #4b5563;
|
| 278 |
+
transform: translateY(-1px);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.download-btn.primary {
|
| 282 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
.download-btn.primary:hover {
|
| 286 |
+
background: linear-gradient(135deg, #5a67d8 0%, #6b42a0 100%);
|
| 287 |
+
transform: translateY(-1px);
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.download-btn.secondary {
|
| 291 |
+
background: #6b7280;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.download-btn.secondary:hover {
|
| 295 |
+
background: #4b5563;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
.share-btn {
|
| 299 |
+
flex: 1;
|
| 300 |
+
padding: 0.75rem 1rem;
|
| 301 |
+
border: none;
|
| 302 |
+
border-radius: 8px;
|
| 303 |
+
font-weight: 500;
|
| 304 |
+
cursor: pointer;
|
| 305 |
+
transition: all 0.2s ease;
|
| 306 |
+
color: white;
|
| 307 |
+
min-width: 100px;
|
| 308 |
+
font-size: 0.9rem;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.share-btn.twitter {
|
| 312 |
+
background: #1DA1F2;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.share-btn.twitter:hover {
|
| 316 |
+
background: #1a91da;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.share-btn.facebook {
|
| 320 |
+
background: #1877F2;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.share-btn.facebook:hover {
|
| 324 |
+
background: #166fe5;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.share-btn.linkedin {
|
| 328 |
+
background: #0A66C2;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.share-btn.linkedin:hover {
|
| 332 |
+
background: #095196;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
/* Mobile responsiveness */
|
| 336 |
+
@media (max-width: 768px) {
|
| 337 |
+
.qr-generator {
|
| 338 |
+
padding: 1rem;
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
.input-section,
|
| 342 |
+
.result-section {
|
| 343 |
+
padding: 2rem 1.5rem;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.title {
|
| 347 |
+
font-size: 1.8rem;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
.subtitle {
|
| 351 |
+
font-size: 1rem;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.input-group {
|
| 355 |
+
flex-direction: column;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.url-input,
|
| 359 |
+
.generate-btn {
|
| 360 |
+
width: 100%;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.profile-info {
|
| 364 |
+
flex-direction: column;
|
| 365 |
+
text-align: center;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
.qr-code-wrapper {
|
| 369 |
+
width: 100%;
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
.profile-bar {
|
| 373 |
+
padding: 1rem 1.5rem;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.profile-bar-avatar {
|
| 377 |
+
width: 40px;
|
| 378 |
+
height: 40px;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
.profile-bar-name {
|
| 382 |
+
font-size: 1rem;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.profile-bar-username {
|
| 386 |
+
font-size: 0.85rem;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
.resource-type-badge {
|
| 390 |
+
font-size: 0.75rem;
|
| 391 |
+
padding: 0.15rem 0.4rem;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.resource-name {
|
| 395 |
+
font-size: 0.85rem;
|
| 396 |
+
max-width: 150px;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
.actions-section {
|
| 400 |
+
grid-template-columns: 1fr;
|
| 401 |
+
gap: 1.5rem;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
.button-group {
|
| 405 |
+
flex-direction: column;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
.download-btn,
|
| 409 |
+
.share-btn {
|
| 410 |
+
width: 100%;
|
| 411 |
+
}
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
/* ===== New UI (Screenshot-inspired) ===== */
|
| 415 |
+
.hf-card {
|
| 416 |
+
width: 100%;
|
| 417 |
+
max-width: 720px;
|
| 418 |
+
background: #ffffff;
|
| 419 |
+
border-radius: 20px;
|
| 420 |
+
padding: 2.25rem;
|
| 421 |
+
box-shadow: 0 14px 40px rgba(0,0,0,0.08);
|
| 422 |
+
margin-bottom: 2rem;
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
.hf-header {
|
| 426 |
+
display: flex;
|
| 427 |
+
align-items: center;
|
| 428 |
+
gap: 0.75rem;
|
| 429 |
+
margin-bottom: 1.25rem;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.hf-emoji {
|
| 433 |
+
font-size: 2rem;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.hf-title-wrap { display:flex; flex-direction: column; }
|
| 437 |
+
.hf-title {
|
| 438 |
+
font-size: 1.5rem;
|
| 439 |
+
font-weight: 800;
|
| 440 |
+
color: #111827;
|
| 441 |
+
}
|
| 442 |
+
.hf-subtitle { color:#6b7280; font-size: 0.95rem; }
|
| 443 |
+
|
| 444 |
+
.hf-form { display:flex; flex-direction: column; gap: 1rem; }
|
| 445 |
+
.hf-field { display:flex; flex-direction: column; gap: 0.4rem; }
|
| 446 |
+
.hf-label { font-size: 0.75rem; letter-spacing: .05em; color:#6b7280; font-weight: 700; }
|
| 447 |
+
|
| 448 |
+
.hf-input-wrap { position: relative; display:flex; align-items: center; }
|
| 449 |
+
.hf-input {
|
| 450 |
+
width: 100%;
|
| 451 |
+
padding: 0.9rem 2.75rem 0.9rem 1rem;
|
| 452 |
+
border: 1.5px solid #e5e7eb;
|
| 453 |
+
border-radius: 12px;
|
| 454 |
+
font-size: 0.95rem;
|
| 455 |
+
}
|
| 456 |
+
.hf-input:focus { outline: none; border-color: #10b981; box-shadow: 0 0 0 3px rgba(16,185,129,.15); }
|
| 457 |
+
.hf-valid {
|
| 458 |
+
position: absolute;
|
| 459 |
+
right: 0.75rem;
|
| 460 |
+
color: #10b981;
|
| 461 |
+
opacity: 0;
|
| 462 |
+
transition: opacity .2s ease;
|
| 463 |
+
font-weight: 900;
|
| 464 |
+
}
|
| 465 |
+
.hf-valid.active { opacity: 1; }
|
| 466 |
+
|
| 467 |
+
.hf-select {
|
| 468 |
+
width: 220px;
|
| 469 |
+
padding: 0.7rem 0.85rem;
|
| 470 |
+
border: 1.5px solid #e5e7eb;
|
| 471 |
+
border-radius: 12px;
|
| 472 |
+
font-size: 0.95rem;
|
| 473 |
+
background: #fff;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.hf-cta {
|
| 477 |
+
align-self: flex-start;
|
| 478 |
+
margin-top: 0.25rem;
|
| 479 |
+
padding: 0.85rem 1.25rem;
|
| 480 |
+
border-radius: 9999px;
|
| 481 |
+
border: none;
|
| 482 |
+
background: #111827;
|
| 483 |
+
color: #fff;
|
| 484 |
+
font-weight: 600;
|
| 485 |
+
cursor: pointer;
|
| 486 |
+
}
|
| 487 |
+
.hf-cta:disabled { opacity: .6; cursor: not-allowed; }
|
| 488 |
+
|
| 489 |
+
/* Download card layout (screenshot 2) */
|
| 490 |
+
.qr-preview { display:flex; justify-content:center; margin: 1.5rem 0 0.5rem; }
|
| 491 |
+
.qr-phone-bg {
|
| 492 |
+
position: relative;
|
| 493 |
+
width: 380px;
|
| 494 |
+
max-width: 100%;
|
| 495 |
+
border-radius: 32px;
|
| 496 |
+
background: linear-gradient(135deg,#f1c40f,#f39c12);
|
| 497 |
+
padding: 52px 18px 26px;
|
| 498 |
+
box-shadow: 0 12px 28px rgba(0,0,0,.15);
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.qr-card-v2 {
|
| 502 |
+
position: relative;
|
| 503 |
+
background: #fff;
|
| 504 |
+
border-radius: 22px;
|
| 505 |
+
padding: 56px 18px 22px;
|
| 506 |
+
box-shadow: 0 8px 18px rgba(0,0,0,.08);
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
.qr-avatar-wrap {
|
| 510 |
+
position: absolute;
|
| 511 |
+
top: -28px;
|
| 512 |
+
left: 50%;
|
| 513 |
+
transform: translateX(-50%);
|
| 514 |
+
width: 56px; height: 56px;
|
| 515 |
+
border-radius: 9999px;
|
| 516 |
+
border: 3px solid #fff;
|
| 517 |
+
overflow: hidden;
|
| 518 |
+
box-shadow: 0 6px 12px rgba(0,0,0,.12);
|
| 519 |
+
}
|
| 520 |
+
.qr-avatar { width:100%; height:100%; object-fit: cover; }
|
| 521 |
+
|
| 522 |
+
.qr-card-inner { display:flex; flex-direction: column; align-items:center; gap: 12px; }
|
| 523 |
+
.qr-name { font-weight: 700; color:#111827; margin-top: -6px; }
|
| 524 |
+
.qr-code-holder { padding: 6px 0 2px; }
|
| 525 |
+
.qr-caption { color:#9ca3af; font-size: .8rem; text-align:center; }
|
| 526 |
+
.qr-brand { display:flex; align-items:center; gap:8px; color:#111827; font-weight:700; }
|
| 527 |
+
.qr-brand img { width:18px; height:18px; }
|
| 528 |
+
.qr-bg-help { text-align:center; color:#fef3c7; font-size: .78rem; margin-top: 10px; }
|
| 529 |
+
|
| 530 |
+
@media (max-width: 480px) {
|
| 531 |
+
.qr-phone-bg { padding: 48px 14px 22px; border-radius: 28px; }
|
| 532 |
+
.qr-card-v2 { padding: 52px 14px 18px; border-radius: 20px; }
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
/* Top icons (back + menu) */
|
| 536 |
+
.qr-topbar {
|
| 537 |
+
position: absolute;
|
| 538 |
+
top: 10px;
|
| 539 |
+
left: 10px;
|
| 540 |
+
right: 10px;
|
| 541 |
+
display: flex;
|
| 542 |
+
align-items: center;
|
| 543 |
+
justify-content: space-between;
|
| 544 |
+
color: rgba(255,255,255,0.95);
|
| 545 |
+
}
|
| 546 |
+
.qr-topbar button { background: transparent; border: 0; color: inherit; padding: 4px; cursor: default; }
|
| 547 |
+
|
| 548 |
+
/* Bottom share sheet */
|
| 549 |
+
.qr-share-sheet {
|
| 550 |
+
position: absolute;
|
| 551 |
+
left: 8px;
|
| 552 |
+
right: 8px;
|
| 553 |
+
bottom: 4px;
|
| 554 |
+
background: #fff;
|
| 555 |
+
border-top-left-radius: 18px;
|
| 556 |
+
border-top-right-radius: 18px;
|
| 557 |
+
box-shadow: 0 -8px 24px rgba(0,0,0,.22);
|
| 558 |
+
display: grid;
|
| 559 |
+
grid-template-columns: 110px 1fr;
|
| 560 |
+
align-items: center;
|
| 561 |
+
gap: 8px 12px;
|
| 562 |
+
padding: 10px 12px 12px;
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
.qr-share-sheet .qr-close {
|
| 566 |
+
position: absolute;
|
| 567 |
+
right: 10px;
|
| 568 |
+
top: 8px;
|
| 569 |
+
border: 0;
|
| 570 |
+
background: transparent;
|
| 571 |
+
color: #111827;
|
| 572 |
+
opacity: .7;
|
| 573 |
+
font-size: 18px;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.qr-download { display:flex; flex-direction: column; align-items: center; gap: 6px; }
|
| 577 |
+
.qr-circle {
|
| 578 |
+
width: 36px; height: 36px; border-radius: 9999px; display:flex; align-items:center; justify-content:center;
|
| 579 |
+
background: #f3f4f6; border: 1px solid #e5e7eb; color:#374151;
|
| 580 |
+
}
|
| 581 |
+
.qr-share-group { display:flex; flex-direction: column; gap: 6px; }
|
| 582 |
+
.qr-share-label { font-size: 12px; color:#6b7280; margin-left: 8px; }
|
| 583 |
+
.qr-share-actions { display:flex; gap: 14px; align-items:center; }
|
| 584 |
+
.qr-share-texts { display:flex; gap: 18px; color:#6b7280; font-size: 12px; margin-left: 6px; }
|
types/html-to-image.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
declare module 'html-to-image' {
|
| 2 |
+
export interface Options {
|
| 3 |
+
quality?: number;
|
| 4 |
+
backgroundColor?: string;
|
| 5 |
+
pixelRatio?: number;
|
| 6 |
+
style?: Partial<CSSStyleDeclaration>;
|
| 7 |
+
filter?: (node: HTMLElement) => boolean;
|
| 8 |
+
width?: number;
|
| 9 |
+
height?: number;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export function toPng(node: HTMLElement, options?: Options): Promise<string>;
|
| 13 |
+
export function toJpeg(node: HTMLElement, options?: Options): Promise<string>;
|
| 14 |
+
export function toBlob(node: HTMLElement, options?: Options): Promise<Blob>;
|
| 15 |
+
export function toSvg(node: HTMLElement, options?: Options): Promise<string>;
|
| 16 |
+
export function toCanvas(node: HTMLElement, options?: Options): Promise<HTMLCanvasElement>;
|
| 17 |
+
}
|
types/qr-code-styling.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
declare module 'qr-code-styling' {
|
| 2 |
+
export interface Options {
|
| 3 |
+
width?: number;
|
| 4 |
+
height?: number;
|
| 5 |
+
type?: 'canvas' | 'svg';
|
| 6 |
+
data?: string;
|
| 7 |
+
image?: string;
|
| 8 |
+
margin?: number;
|
| 9 |
+
qrOptions?: {
|
| 10 |
+
typeNumber?: number;
|
| 11 |
+
mode?: 'Numeric' | 'Alphanumeric' | 'Byte' | 'Kanji';
|
| 12 |
+
errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
|
| 13 |
+
};
|
| 14 |
+
imageOptions?: {
|
| 15 |
+
hideBackgroundDots?: boolean;
|
| 16 |
+
imageSize?: number;
|
| 17 |
+
margin?: number;
|
| 18 |
+
crossOrigin?: string;
|
| 19 |
+
};
|
| 20 |
+
dotsOptions?: {
|
| 21 |
+
color?: string;
|
| 22 |
+
type?: 'rounded' | 'dots' | 'classy' | 'classy-rounded' | 'square' | 'extra-rounded';
|
| 23 |
+
};
|
| 24 |
+
backgroundOptions?: {
|
| 25 |
+
color?: string;
|
| 26 |
+
};
|
| 27 |
+
cornersSquareOptions?: {
|
| 28 |
+
color?: string;
|
| 29 |
+
type?: 'dot' | 'square' | 'extra-rounded';
|
| 30 |
+
};
|
| 31 |
+
cornersDotOptions?: {
|
| 32 |
+
color?: string;
|
| 33 |
+
type?: 'dot' | 'square';
|
| 34 |
+
};
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
export default class QRCodeStyling {
|
| 38 |
+
constructor(options?: Options);
|
| 39 |
+
append(container: HTMLElement): void;
|
| 40 |
+
download(options?: {
|
| 41 |
+
name?: string;
|
| 42 |
+
extension?: 'png' | 'jpeg' | 'webp' | 'svg';
|
| 43 |
+
}): Promise<Blob>;
|
| 44 |
+
update(options?: Options): void;
|
| 45 |
+
}
|
| 46 |
+
}
|