done
Browse files- app/globals.css +1 -1
- app/layout.tsx +14 -4
- components/HuggingFaceQRGenerator.tsx +79 -35
- package-lock.json +11 -1
- package.json +2 -1
- public/file.svg +0 -1
- public/globe.svg +0 -1
- public/hf-logo.svg +28 -0
- public/logo.svg +29 -0
- public/next.svg +0 -1
- public/vercel.svg +0 -1
- public/window.svg +0 -1
- styles/qr-generator.css +11 -9
app/globals.css
CHANGED
|
@@ -49,7 +49,7 @@
|
|
| 49 |
}
|
| 50 |
|
| 51 |
body {
|
| 52 |
-
font-family: var(--font-
|
| 53 |
}
|
| 54 |
|
| 55 |
:root {
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
body {
|
| 52 |
+
font-family: var(--font-poppins), var(--font-inter), Arial, Helvetica, sans-serif;
|
| 53 |
}
|
| 54 |
|
| 55 |
:root {
|
app/layout.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 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 |
|
|
@@ -18,9 +18,19 @@ const inter = Inter({
|
|
| 18 |
subsets: ["latin"],
|
| 19 |
});
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
export const metadata: Metadata = {
|
| 22 |
-
title: "
|
| 23 |
-
description: "Generate beautiful QR codes for Hugging Face profiles with
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
};
|
| 25 |
|
| 26 |
export default function RootLayout({
|
|
@@ -31,7 +41,7 @@ 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>
|
|
|
|
| 1 |
import type { Metadata } from "next";
|
| 2 |
+
import { Geist, Geist_Mono, Inter, Poppins } from "next/font/google";
|
| 3 |
import "./globals.css";
|
| 4 |
import "../styles/qr-generator.css";
|
| 5 |
|
|
|
|
| 18 |
subsets: ["latin"],
|
| 19 |
});
|
| 20 |
|
| 21 |
+
const poppins = Poppins({
|
| 22 |
+
variable: "--font-poppins",
|
| 23 |
+
subsets: ["latin"],
|
| 24 |
+
weight: ["400", "600", "700"],
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
export const metadata: Metadata = {
|
| 28 |
+
title: "HF QR Generator",
|
| 29 |
+
description: "Generate clean and beautiful QR codes for Hugging Face user profiles, models, datasets, and spaces with custom avatars",
|
| 30 |
+
icons: {
|
| 31 |
+
icon: "/hf-logo.svg",
|
| 32 |
+
apple: "/hf-logo.svg",
|
| 33 |
+
},
|
| 34 |
};
|
| 35 |
|
| 36 |
export default function RootLayout({
|
|
|
|
| 41 |
return (
|
| 42 |
<html lang="en">
|
| 43 |
<body
|
| 44 |
+
className={`${geistSans.variable} ${geistMono.variable} ${inter.variable} ${poppins.variable} antialiased`}
|
| 45 |
>
|
| 46 |
{children}
|
| 47 |
</body>
|
components/HuggingFaceQRGenerator.tsx
CHANGED
|
@@ -11,7 +11,9 @@ 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,
|
|
|
|
|
|
|
| 15 |
|
| 16 |
const HuggingFaceQRGenerator = () => {
|
| 17 |
const [inputUrl, setInputUrl] = useState('');
|
|
@@ -182,6 +184,40 @@ const HuggingFaceQRGenerator = () => {
|
|
| 182 |
}
|
| 183 |
};
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
const getResourceIcon = (type: string) => {
|
| 186 |
switch(type) {
|
| 187 |
case 'model': return '🤖';
|
|
@@ -210,51 +246,53 @@ const HuggingFaceQRGenerator = () => {
|
|
| 210 |
<div className="min-h-screen 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">
|
| 211 |
{/* Input form - hidden when QR is shown */}
|
| 212 |
{!showQR && (
|
| 213 |
-
<div className="min-h-screen grid place-items-center p-6 md:p-10">
|
| 214 |
<div className="w-full max-w-2xl mx-auto">
|
| 215 |
{/* Input card */}
|
| 216 |
-
<Card className="shadow-xl">
|
| 217 |
<CardHeader className="pb-4">
|
| 218 |
-
<div className="flex
|
| 219 |
-
<
|
| 220 |
-
<
|
| 221 |
-
<CardTitle className="text-2xl font-bold tracking-tight">Hugging Face</CardTitle>
|
| 222 |
-
<CardDescription className="mt-1 text-muted-foreground">Generate a clean QR code for any Hugging Face profile or resource.</CardDescription>
|
| 223 |
-
</div>
|
| 224 |
</div>
|
| 225 |
</CardHeader>
|
| 226 |
<CardContent className="p-6 md:p-8 space-y-7">
|
| 227 |
<div className="space-y-3">
|
| 228 |
-
<Label className="text-xs tracking-wider font-medium">HUGGING FACE USERNAME</Label>
|
| 229 |
<div className="relative">
|
| 230 |
<div className="flex items-stretch overflow-hidden rounded-md border">
|
| 231 |
-
<span className="hidden sm:inline-flex items-center
|
| 232 |
https://huggingface.co/
|
| 233 |
</span>
|
| 234 |
<Input
|
| 235 |
type="text"
|
| 236 |
value={inputUrl}
|
| 237 |
onChange={(e) => setInputUrl(e.target.value)}
|
| 238 |
-
placeholder="
|
| 239 |
-
className="h-
|
|
|
|
| 240 |
onKeyPress={(e) => e.key === 'Enter' && handleGenerate()}
|
| 241 |
disabled={loading}
|
| 242 |
aria-invalid={inputIsInvalid}
|
| 243 |
aria-describedby="hf-input-help"
|
|
|
|
| 244 |
/>
|
| 245 |
</div>
|
| 246 |
</div>
|
| 247 |
-
<p id="hf-input-help" className="text-xs text-muted-foreground">Paste a full URL or just the username, e.g. <span className="font-mono">reubencf</span>.</p>
|
| 248 |
</div>
|
| 249 |
|
| 250 |
-
<
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
|
|
|
|
|
|
|
|
|
| 258 |
|
| 259 |
{error && (
|
| 260 |
<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">
|
|
@@ -322,23 +360,29 @@ const HuggingFaceQRGenerator = () => {
|
|
| 322 |
</div>
|
| 323 |
{/* Share sheet placed below the phone (not part of exported element) */}
|
| 324 |
<div className="qr-share-sheet" onClick={(e) => e.stopPropagation()}>
|
| 325 |
-
<div className="qr-download">
|
| 326 |
-
<
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
</div>
|
| 331 |
<div className="qr-share-group">
|
| 332 |
<span className="qr-share-label">Share to</span>
|
| 333 |
<div className="qr-share-actions">
|
| 334 |
-
<button className="qr-circle" onClick={() => handleShare('linkedin')} aria-label="Share on LinkedIn"><
|
| 335 |
-
<button className="qr-circle" onClick={() => handleShare('facebook')} aria-label="Share on Facebook"><
|
| 336 |
-
<button className="qr-circle" onClick={() => handleShare('twitter')} aria-label="Share on X (Twitter)"><
|
| 337 |
-
</div>
|
| 338 |
-
<div className="qr-share-texts">
|
| 339 |
-
<span>LinkedIn</span>
|
| 340 |
-
<span>Facebook</span>
|
| 341 |
-
<span>X</span>
|
| 342 |
</div>
|
| 343 |
</div>
|
| 344 |
</div>
|
|
|
|
| 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, ChevronLeft, Copy } from 'lucide-react';
|
| 15 |
+
import { FaFacebook, FaLinkedin } from 'react-icons/fa';
|
| 16 |
+
import { FaSquareXTwitter } from 'react-icons/fa6';
|
| 17 |
|
| 18 |
const HuggingFaceQRGenerator = () => {
|
| 19 |
const [inputUrl, setInputUrl] = useState('');
|
|
|
|
| 184 |
}
|
| 185 |
};
|
| 186 |
|
| 187 |
+
const handleCopyImage = async () => {
|
| 188 |
+
if (!profileData) return;
|
| 189 |
+
|
| 190 |
+
try {
|
| 191 |
+
const phoneElement = phoneRef.current;
|
| 192 |
+
if (!phoneElement) return;
|
| 193 |
+
|
| 194 |
+
const rect = phoneElement.getBoundingClientRect();
|
| 195 |
+
const dataUrl = await htmlToImage.toPng(phoneElement, {
|
| 196 |
+
quality: 1.0,
|
| 197 |
+
pixelRatio: 2,
|
| 198 |
+
width: Math.ceil(rect.width),
|
| 199 |
+
height: Math.ceil(rect.height),
|
| 200 |
+
style: { margin: '0' }
|
| 201 |
+
});
|
| 202 |
+
|
| 203 |
+
// Convert data URL to blob
|
| 204 |
+
const response = await fetch(dataUrl);
|
| 205 |
+
const blob = await response.blob();
|
| 206 |
+
|
| 207 |
+
// Copy to clipboard
|
| 208 |
+
await navigator.clipboard.write([
|
| 209 |
+
new ClipboardItem({
|
| 210 |
+
'image/png': blob
|
| 211 |
+
})
|
| 212 |
+
]);
|
| 213 |
+
|
| 214 |
+
alert('Image copied to clipboard!');
|
| 215 |
+
} catch (err) {
|
| 216 |
+
console.error('Copy error:', err);
|
| 217 |
+
alert('Failed to copy image. Your browser may not support this feature.');
|
| 218 |
+
}
|
| 219 |
+
};
|
| 220 |
+
|
| 221 |
const getResourceIcon = (type: string) => {
|
| 222 |
switch(type) {
|
| 223 |
case 'model': return '🤖';
|
|
|
|
| 246 |
<div className="min-h-screen 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">
|
| 247 |
{/* Input form - hidden when QR is shown */}
|
| 248 |
{!showQR && (
|
| 249 |
+
<div className="min-h-screen grid place-items-center p-6 md:p-10 bg-white/80">
|
| 250 |
<div className="w-full max-w-2xl mx-auto">
|
| 251 |
{/* Input card */}
|
| 252 |
+
<Card className="shadow-xl" style={{ padding: 15, fontFamily: 'var(--font-inter)', boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1), 0 2px 8px rgba(0, 0, 0, 0.06)' }}>
|
| 253 |
<CardHeader className="pb-4">
|
| 254 |
+
<div className="flex flex-col gap-3">
|
| 255 |
+
<img src="/logo.svg" alt="Hugging Face" className="h-7 w-auto" />
|
| 256 |
+
<CardDescription className="text-muted-foreground" style={{ fontFamily: 'var(--font-inter)' }}>Generate a clean QR code for any Hugging Face profile or resource.</CardDescription>
|
|
|
|
|
|
|
|
|
|
| 257 |
</div>
|
| 258 |
</CardHeader>
|
| 259 |
<CardContent className="p-6 md:p-8 space-y-7">
|
| 260 |
<div className="space-y-3">
|
| 261 |
+
<Label className="text-xs tracking-wider font-medium" style={{ fontFamily: 'var(--font-inter)' }}>HUGGING FACE USERNAME</Label>
|
| 262 |
<div className="relative">
|
| 263 |
<div className="flex items-stretch overflow-hidden rounded-md border">
|
| 264 |
+
<span className="hidden sm:inline-flex items-center text-sm text-muted-foreground select-none" style={{ paddingLeft: '8px', paddingRight: '5px', backgroundColor: '#f5f5f5', fontFamily: 'var(--font-inter)' }}>
|
| 265 |
https://huggingface.co/
|
| 266 |
</span>
|
| 267 |
<Input
|
| 268 |
type="text"
|
| 269 |
value={inputUrl}
|
| 270 |
onChange={(e) => setInputUrl(e.target.value)}
|
| 271 |
+
placeholder="Reubencf"
|
| 272 |
+
className="h-12 border-0 rounded-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
| 273 |
+
style={{ paddingLeft: '3px', paddingRight: '12px', fontFamily: 'var(--font-inter)' }}
|
| 274 |
onKeyPress={(e) => e.key === 'Enter' && handleGenerate()}
|
| 275 |
disabled={loading}
|
| 276 |
aria-invalid={inputIsInvalid}
|
| 277 |
aria-describedby="hf-input-help"
|
| 278 |
+
autoFocus
|
| 279 |
/>
|
| 280 |
</div>
|
| 281 |
</div>
|
| 282 |
+
<p id="hf-input-help" className="text-xs text-muted-foreground" style={{ paddingTop: '5px', paddingBottom: '4px', fontFamily: 'var(--font-inter)' }}>Paste a full URL or just the username, e.g. <span className="font-mono">reubencf</span>.</p>
|
| 283 |
</div>
|
| 284 |
|
| 285 |
+
<div className="pt-2 flex justify-end">
|
| 286 |
+
<Button
|
| 287 |
+
onClick={handleGenerate}
|
| 288 |
+
disabled={!inputUrl || loading}
|
| 289 |
+
className="rounded-md h-auto bg-neutral-900 text-white hover:bg-neutral-800 disabled:bg-neutral-700 disabled:opacity-100"
|
| 290 |
+
style={{ padding: '12px' }}
|
| 291 |
+
aria-label="Generate QR Code"
|
| 292 |
+
>
|
| 293 |
+
{loading ? 'Generating…' : 'Generate QR Code'}
|
| 294 |
+
</Button>
|
| 295 |
+
</div>
|
| 296 |
|
| 297 |
{error && (
|
| 298 |
<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">
|
|
|
|
| 360 |
</div>
|
| 361 |
{/* Share sheet placed below the phone (not part of exported element) */}
|
| 362 |
<div className="qr-share-sheet" onClick={(e) => e.stopPropagation()}>
|
| 363 |
+
<div className="qr-download-group">
|
| 364 |
+
<span className="qr-share-label">Actions</span>
|
| 365 |
+
<div className="qr-download-actions">
|
| 366 |
+
<div className="qr-download-item">
|
| 367 |
+
<button onClick={() => handleDownload('full')} className="qr-circle" aria-label="Download">
|
| 368 |
+
<Download size={18} />
|
| 369 |
+
</button>
|
| 370 |
+
<span className="qr-action-text">Download</span>
|
| 371 |
+
</div>
|
| 372 |
+
<div className="qr-download-item">
|
| 373 |
+
<button onClick={handleCopyImage} className="qr-circle" aria-label="Copy">
|
| 374 |
+
<Copy size={18} />
|
| 375 |
+
</button>
|
| 376 |
+
<span className="qr-action-text">Copy</span>
|
| 377 |
+
</div>
|
| 378 |
+
</div>
|
| 379 |
</div>
|
| 380 |
<div className="qr-share-group">
|
| 381 |
<span className="qr-share-label">Share to</span>
|
| 382 |
<div className="qr-share-actions">
|
| 383 |
+
<button className="qr-circle" onClick={() => handleShare('linkedin')} aria-label="Share on LinkedIn"><FaLinkedin size={20} /></button>
|
| 384 |
+
<button className="qr-circle" onClick={() => handleShare('facebook')} aria-label="Share on Facebook"><FaFacebook size={20} /></button>
|
| 385 |
+
<button className="qr-circle" onClick={() => handleShare('twitter')} aria-label="Share on X (Twitter)"><FaSquareXTwitter size={20} /></button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
</div>
|
| 387 |
</div>
|
| 388 |
</div>
|
package-lock.json
CHANGED
|
@@ -21,6 +21,7 @@
|
|
| 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 |
},
|
|
@@ -34,7 +35,7 @@
|
|
| 34 |
"eslint-config-next": "16.0.1",
|
| 35 |
"tailwindcss": "^4",
|
| 36 |
"tw-animate-css": "^1.4.0",
|
| 37 |
-
"typescript": "
|
| 38 |
}
|
| 39 |
},
|
| 40 |
"node_modules/@alloc/quick-lru": {
|
|
@@ -5748,6 +5749,15 @@
|
|
| 5748 |
"react": "^19.2.0"
|
| 5749 |
}
|
| 5750 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5751 |
"node_modules/react-is": {
|
| 5752 |
"version": "16.13.1",
|
| 5753 |
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
|
|
| 21 |
"qrcode-generator": "^2.0.4",
|
| 22 |
"react": "19.2.0",
|
| 23 |
"react-dom": "19.2.0",
|
| 24 |
+
"react-icons": "^5.5.0",
|
| 25 |
"react-qr-code": "^2.0.18",
|
| 26 |
"tailwind-merge": "^3.3.1"
|
| 27 |
},
|
|
|
|
| 35 |
"eslint-config-next": "16.0.1",
|
| 36 |
"tailwindcss": "^4",
|
| 37 |
"tw-animate-css": "^1.4.0",
|
| 38 |
+
"typescript": "5.9.3"
|
| 39 |
}
|
| 40 |
},
|
| 41 |
"node_modules/@alloc/quick-lru": {
|
|
|
|
| 5749 |
"react": "^19.2.0"
|
| 5750 |
}
|
| 5751 |
},
|
| 5752 |
+
"node_modules/react-icons": {
|
| 5753 |
+
"version": "5.5.0",
|
| 5754 |
+
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
| 5755 |
+
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
| 5756 |
+
"license": "MIT",
|
| 5757 |
+
"peerDependencies": {
|
| 5758 |
+
"react": "*"
|
| 5759 |
+
}
|
| 5760 |
+
},
|
| 5761 |
"node_modules/react-is": {
|
| 5762 |
"version": "16.13.1",
|
| 5763 |
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
package.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
| 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 |
},
|
|
@@ -35,6 +36,6 @@
|
|
| 35 |
"eslint-config-next": "16.0.1",
|
| 36 |
"tailwindcss": "^4",
|
| 37 |
"tw-animate-css": "^1.4.0",
|
| 38 |
-
"typescript": "
|
| 39 |
}
|
| 40 |
}
|
|
|
|
| 22 |
"qrcode-generator": "^2.0.4",
|
| 23 |
"react": "19.2.0",
|
| 24 |
"react-dom": "19.2.0",
|
| 25 |
+
"react-icons": "^5.5.0",
|
| 26 |
"react-qr-code": "^2.0.18",
|
| 27 |
"tailwind-merge": "^3.3.1"
|
| 28 |
},
|
|
|
|
| 36 |
"eslint-config-next": "16.0.1",
|
| 37 |
"tailwindcss": "^4",
|
| 38 |
"tw-animate-css": "^1.4.0",
|
| 39 |
+
"typescript": "5.9.3"
|
| 40 |
}
|
| 41 |
}
|
public/file.svg
DELETED
public/globe.svg
DELETED
public/hf-logo.svg
ADDED
|
|
public/logo.svg
ADDED
|
|
public/next.svg
DELETED
public/vercel.svg
DELETED
public/window.svg
DELETED
styles/qr-generator.css
CHANGED
|
@@ -556,10 +556,10 @@
|
|
| 556 |
border-radius: 18px;
|
| 557 |
box-shadow: 0 -8px 24px rgba(0,0,0,.22);
|
| 558 |
display: grid;
|
| 559 |
-
grid-template-columns:
|
| 560 |
-
align-items:
|
| 561 |
-
gap:
|
| 562 |
-
padding:
|
| 563 |
font-family: var(--font-inter), var(--font-geist-sans), system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
| 564 |
}
|
| 565 |
|
|
@@ -574,13 +574,15 @@
|
|
| 574 |
font-size: 18px;
|
| 575 |
}
|
| 576 |
|
| 577 |
-
.qr-download { display:flex; flex-direction: column;
|
|
|
|
|
|
|
|
|
|
| 578 |
.qr-circle {
|
| 579 |
width: 36px; height: 36px; border-radius: 9999px; display:flex; align-items:center; justify-content:center;
|
| 580 |
background: #f3f4f6; border: 1px solid #e5e7eb; color:#374151;
|
| 581 |
cursor: pointer;
|
| 582 |
}
|
| 583 |
-
.qr-share-group { display:
|
| 584 |
-
.qr-share-label { font-size: 12px; color:#6b7280; margin-left: 0; text-align:
|
| 585 |
-
.qr-share-actions { display:
|
| 586 |
-
.qr-share-texts { display:grid; grid-template-columns: repeat(3, 1fr); gap: 12px; color:#6b7280; font-size: 12px; text-align: center; }
|
|
|
|
| 556 |
border-radius: 18px;
|
| 557 |
box-shadow: 0 -8px 24px rgba(0,0,0,.22);
|
| 558 |
display: grid;
|
| 559 |
+
grid-template-columns: 1fr 1fr;
|
| 560 |
+
align-items: start;
|
| 561 |
+
gap: 12px 16px;
|
| 562 |
+
padding: 12px 16px 14px;
|
| 563 |
font-family: var(--font-inter), var(--font-geist-sans), system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
| 564 |
}
|
| 565 |
|
|
|
|
| 574 |
font-size: 18px;
|
| 575 |
}
|
| 576 |
|
| 577 |
+
.qr-download-group { display:flex; flex-direction: column; gap: 8px; }
|
| 578 |
+
.qr-download-actions { display:flex; gap: 12px; }
|
| 579 |
+
.qr-download-item { display:flex; flex-direction: column; align-items: center; gap: 6px; }
|
| 580 |
+
.qr-action-text { font-size: 11px; color:#6b7280; }
|
| 581 |
.qr-circle {
|
| 582 |
width: 36px; height: 36px; border-radius: 9999px; display:flex; align-items:center; justify-content:center;
|
| 583 |
background: #f3f4f6; border: 1px solid #e5e7eb; color:#374151;
|
| 584 |
cursor: pointer;
|
| 585 |
}
|
| 586 |
+
.qr-share-group { display:flex; flex-direction: column; gap: 8px; }
|
| 587 |
+
.qr-share-label { font-size: 12px; color:#6b7280; margin-left: 0; text-align: left; letter-spacing: .02em; font-weight: 600; }
|
| 588 |
+
.qr-share-actions { display:flex; gap: 14px; }
|
|
|