@satirr/openqtr
v0.3.0
Published
OpenQRL is an open-source website for creating and customizing QR codes with an elegant Frutiger Aero dusk theme. Built with Next.js 15, TypeScript, and Tailwind CSS v4.
Readme
OpenQRL
OpenQRL is an open-source website for creating and customizing QR codes with an elegant Frutiger Aero dusk theme. Built with Next.js 15, TypeScript, and Tailwind CSS v4.
Features
5 QR Types
- Text — Plain text
- URL — Website link (auto-prepends
https://) - Email — Email address with optional subject & body
- Wi-Fi — Wi-Fi credentials (SSID, encryption, password, hidden toggle)
- vCard — Complete contact (name, phone, email, org, title, website, address)
Style Customization
- Colors: Data / Finder / Background (color picker + hex input)
- Gradient Fill: Linear gradient with customizable angle & two colors (Solid/Gradient toggle)
- Dot Shape: Square, Dots, Rounded, Classy, Soft
- Error Correction: L (~7%), M (~15%), Q (~25%), H (~30%)
- Size: Slider 160–400px
- Margin: Slider 0–40px
- Logo: File upload (drag & drop or click), image URL, size slider, hide-dots toggle
Export
- PNG (raster, 2x scale)
- JPG (raster, quality 0.95)
- SVG (vector, scalable)
History
- Auto-saves QR configuration on export (colors, shape, content, logo)
- Per-entry format buttons (PNG / JPG / SVG) for re-download
- Persisted to
localStorage— survives page refresh
Undo / Redo
- Tracks style, color, logo, content, and QR type changes
- Up to 50 undo levels
- Buttons in the Style panel header
UX & Performance
- 350ms debounce + onBlur commit (avoids per-keystroke re-render)
- Zustand selectors (narrow subscriptions, no cascading re-renders)
- Responsive: split layout on desktop, stacked on mobile
- Dark glass panels with premium shadows
- Dusk gradient background: navy → purple → sunset + stars + bubbles + ground mist
Script SDK (OpenQTR)
OpenQTR (Open QR Tool Renderer) is a standalone browser SDK that lets anyone generate QR codes from any website — no React, no framework, no bundler needed.
<script src="https://cdn.jsdelivr.net/npm/@satirr/openqtr/dist/sdk.js"></script>
<div id="qr"></div>
<script>
OpenQTR.generate({
text: "Hello World",
element: "#qr",
size: 250,
});
</script>| Feature | Description |
|---------|-------------|
| Formats | PNG (canvas) & SVG |
| Colors | Custom fgColor & bgColor |
| Error correction | L / M / Q / H |
| CDN | jsDelivr — zero setup |
| Self-host | /openqtr-sdk.js when running the app |
See the full API guide at /GUIDE-API or read GUIDE-API.md for the expert reference.
Tech Stack
| Layer | Technology |
|-------|-----------|
| Framework | Next.js 15 (App Router, Turbopack) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS v4 |
| State | Zustand v5 (persist middleware for history) |
| QR Engine | qrcode (matrix generation) + custom Canvas/SVG renderers |
| Icons | lucide-react |
| Font | Inter (via next/font) |
Project Structure
openqrl/
├── app/
│ ├── globals.css # Design tokens, glass utilities, animations
│ ├── layout.tsx # Root layout + Inter font + metadata
│ └── page.tsx # Main page: header, split panels, footer
├── components/
│ ├── BackgroundEffect.tsx # Dusk gradient, stars, sunset glow, bubbles, mist
│ ├── ExportButtons.tsx # PNG / JPG / SVG download buttons
│ ├── HistoryPanel.tsx # Collapsible history, per-entry re-export, clear
│ ├── LogoUploader.tsx # Dropzone + URL input + hidden file input (always mounted)
│ ├── QRForm.tsx # Dynamic form per QR type, onBlur commit
│ ├── QRPreview.tsx # Sticky right panel, debounced QR render
│ ├── QRTypeSelector.tsx # 5 tab buttons with icons, cyan active state
│ ├── StylePanel.tsx # Color pickers, gradient, dot shapes, ECC, size/margin, undo/redo
│ └── UpdateModal.tsx # One-time v0.2.0 changelog notification modal
├── lib/
│ ├── qrCore.ts # Custom QR rendering (HTML, Canvas, SVG export)
│ ├── qrData.ts # Generate QR content string per type
│ ├── store.ts # Zustand store: formData, style, logo, history, undo/redo
│ └── useDebounce.ts # Generic debounce hook
├── types/
│ └── index.ts # QRType, DotType, QRFormData, StyleOptions, etc.
├── public/ # Static assets (SVG icons)
├── .gitignore
├── eslint.config.mjs
├── LICENSE.md # Apache 2.0
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── README.md
└── tsconfig.jsonGetting Started
Prerequisites
- Node.js 18+
- npm / pnpm / yarn / bun
Installation & Development
# Clone repo
git clone https://gitlab.com/satirr/openqtr.git
cd openqrl
# Install dependencies
npm install
# Start development server
npm run devOpen http://localhost:3000 in your browser.
Production Build
npm run build
npm startKey Configuration Details
Design Tokens (app/globals.css)
:root {
--glass-bg: rgba(15, 18, 28, 0.9);
--glass-border: rgba(255, 255, 255, 0.08);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
--accent-cyan: #00f5d4;
--radius-input: 12px;
--radius-panel: 16px;
}Layout Container (app/page.tsx)
<div className="max-w-5xl mx-auto px-6 lg:px-16 xl:px-24">
<div className="flex gap-6 lg:justify-center">
{/* Left: w-full lg:w-auto lg:max-w-2xl */}
{/* Right: lg:w-80 xl:w-96 shrink-0 sticky top-24 */}
</div>
</div>Debounce & Commit (lib/useDebounce.ts, components/QRForm.tsx)
// Commits to store on blur, not on every keystroke
const debouncedValue = useDebounce(value, 350);
<input onBlur={() => commit()} ... />getCornerType() — Outside Component
// components/QRPreview.tsx (~line 15)
function getCornerType(dotType: DotType): 'square' | 'dot' | 'rounded' | 'classy' | 'extra-rounded' {
// mapping...
}Prevents infinite loop inside useEffect.
File Input Always Mounted (components/LogoUploader.tsx)
// Always in DOM, never conditionally rendered
<input
type="file"
ref={fileInputRef}
className="hidden"
onChange={handleFileChange}
/>Prevents uncontrolled → controlled React error.
Theme & Colors
Dusk Gradient Background
Navy (#0a0f1f) → Deep Purple (#1a0a2e) → Sunset Orange (#2d1018)- 20 animated stars (twinkle)
- 8 rising bubbles (bubbleUp)
- Sunset glow (glowPulse)
- Ground mist (translateY animation)
Glass Panel Variants
- Default:
bg-white/5 backdrop-blur-xl border-white/10 - Shimmer: + animated gradient overlay
- Strong:
bg-white/10 backdrop-blur-2xl border-white/15 shadow-[0_8px_32px_rgba(0,0,0,0.4)]
Accent Cyan
#00f5d4 — used for:
- Active tab indicator
- Button hover/active states
- Slider thumb & track
- Focus rings
- Logo upload zone border
Dependencies
Production
{
"next": "^15.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zustand": "^5.0.0",
"qrcode": "^1.5.4",
"lucide-react": "^0.468.0"
}Development
{
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4",
"@tailwindcss/postcss": "^4"
}QR Data Formats
Wi-Fi
WIFI:S:<SSID>;T:<WPA|WEP|nopass>;P:<password>;;vCard (v3.0)
BEGIN:VCARD
VERSION:3.0
FN:<Full Name>
TEL:<Phone>
EMAIL:<Email>
ORG:<Organization>
ADR:<Address>
END:VCARDContributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/feature-name - Commit your changes:
git commit -m "feat: add feature X" - Push:
git push origin feature/feature-name - Open a Pull Request
License
Apache License 2.0 — see LICENSE.md for full details.
Copyright 2025 Satir
Licensed under the Apache License, Version 2.0Demo & Result
Author & Community
Created by Satir
💬 Join WhatsApp Channel: bit.ly/tirrch
Acknowledgments
- qrcode — QR matrix generation
- lucide-react — Beautiful icons
- Tailwind CSS — Utility-first CSS
- Zustand — Simple state management
- Next.js — React framework
OpenQRL — Make QR codes beautiful again.
