1045 lines
39 KiB
HTML
1045 lines
39 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>QR Code Design Showcase</title>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.js"></script>
|
|
<style>
|
|
:root{
|
|
--bg:#0f1115;
|
|
--panel:#171a21;
|
|
--panel2:#1e2129;
|
|
--border:#2a2e38;
|
|
--text:#eef0f4;
|
|
--muted:#9aa2b1;
|
|
--accent:#7c5cff;
|
|
--accent2:#00d4b5;
|
|
}
|
|
*{box-sizing:border-box;}
|
|
body{
|
|
margin:0;
|
|
background:linear-gradient(160deg,#0f1115,#14161d 60%,#0f1115);
|
|
color:var(--text);
|
|
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
padding:32px 20px 80px;
|
|
}
|
|
header{
|
|
max-width:1200px;
|
|
margin:0 auto 28px;
|
|
text-align:center;
|
|
}
|
|
header h1{
|
|
font-size:28px;
|
|
margin:0 0 8px;
|
|
background:linear-gradient(90deg,var(--accent),var(--accent2));
|
|
-webkit-background-clip:text;
|
|
background-clip:text;
|
|
color:transparent;
|
|
font-weight:800;
|
|
}
|
|
header p{
|
|
color:var(--muted);
|
|
margin:0;
|
|
font-size:14px;
|
|
}
|
|
|
|
.customizer{
|
|
max-width:1200px;
|
|
margin:0 auto 40px;
|
|
background:var(--panel);
|
|
border:1px solid var(--border);
|
|
border-radius:16px;
|
|
padding:22px;
|
|
display:grid;
|
|
grid-template-columns:380px 1fr;
|
|
gap:24px;
|
|
}
|
|
@media (max-width:860px){
|
|
.customizer{grid-template-columns:1fr;}
|
|
}
|
|
.group{
|
|
border:1px solid var(--border);
|
|
border-radius:12px;
|
|
padding:14px 14px 4px;
|
|
margin-bottom:14px;
|
|
background:rgba(255,255,255,.015);
|
|
}
|
|
.group-title{
|
|
font-size:11px;
|
|
text-transform:uppercase;
|
|
letter-spacing:.08em;
|
|
color:var(--accent2);
|
|
font-weight:700;
|
|
margin-bottom:10px;
|
|
}
|
|
.field{margin-bottom:14px;}
|
|
.field label{
|
|
display:flex;
|
|
justify-content:space-between;
|
|
font-size:12px;
|
|
text-transform:uppercase;
|
|
letter-spacing:.06em;
|
|
color:var(--muted);
|
|
margin-bottom:6px;
|
|
}
|
|
.field label span.val{ color:var(--accent2); text-transform:none; letter-spacing:0; font-weight:600;}
|
|
.field input[type=text], .field select{
|
|
width:100%;
|
|
background:var(--panel2);
|
|
border:1px solid var(--border);
|
|
color:var(--text);
|
|
padding:9px 10px;
|
|
border-radius:8px;
|
|
font-size:14px;
|
|
}
|
|
.field input[type=color]{
|
|
width:100%;
|
|
height:38px;
|
|
border:1px solid var(--border);
|
|
border-radius:8px;
|
|
background:var(--panel2);
|
|
padding:2px;
|
|
}
|
|
.field input[type=range]{
|
|
width:100%;
|
|
accent-color: var(--accent);
|
|
}
|
|
.checkline{
|
|
display:flex;
|
|
align-items:center;
|
|
gap:8px;
|
|
font-size:13px;
|
|
color:var(--text);
|
|
margin-bottom:10px;
|
|
}
|
|
.checkline input{width:16px;height:16px;}
|
|
.row2{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
|
|
.row3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;}
|
|
.preview-wrap{
|
|
display:flex;
|
|
flex-direction:column;
|
|
align-items:center;
|
|
justify-content:center;
|
|
gap:14px;
|
|
position:sticky;
|
|
top:20px;
|
|
}
|
|
.preview-card{
|
|
background:#fff;
|
|
border-radius:18px;
|
|
padding:20px;
|
|
box-shadow:0 10px 40px rgba(0,0,0,.35);
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:center;
|
|
}
|
|
#liveCanvas{display:block;border-radius:6px; max-width:100%; height:auto;}
|
|
.btn-row{display:flex; gap:10px; flex-wrap:wrap; justify-content:center;}
|
|
.dl-btn{
|
|
background:var(--accent);
|
|
color:#fff;
|
|
border:none;
|
|
padding:10px 18px;
|
|
border-radius:8px;
|
|
font-size:13px;
|
|
font-weight:600;
|
|
cursor:pointer;
|
|
}
|
|
.dl-btn.secondary{
|
|
background:var(--panel2);
|
|
border:1px solid var(--border);
|
|
color:var(--text);
|
|
}
|
|
.dl-btn:hover{filter:brightness(1.15);}
|
|
|
|
h2.section-title{
|
|
max-width:1200px;
|
|
margin:40px auto 16px;
|
|
font-size:18px;
|
|
color:var(--text);
|
|
display:flex;
|
|
align-items:center;
|
|
gap:10px;
|
|
}
|
|
h2.section-title .badge{
|
|
font-size:11px;
|
|
background:var(--panel2);
|
|
border:1px solid var(--border);
|
|
color:var(--muted);
|
|
padding:3px 9px;
|
|
border-radius:20px;
|
|
font-weight:500;
|
|
}
|
|
|
|
.gallery{
|
|
max-width:1200px;
|
|
margin:0 auto;
|
|
display:grid;
|
|
grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
|
|
gap:18px;
|
|
}
|
|
.card{
|
|
background:var(--panel);
|
|
border:1px solid var(--border);
|
|
border-radius:14px;
|
|
padding:16px;
|
|
display:flex;
|
|
flex-direction:column;
|
|
align-items:center;
|
|
gap:10px;
|
|
transition:transform .15s ease, border-color .15s ease;
|
|
cursor:pointer;
|
|
}
|
|
.card:hover{
|
|
transform:translateY(-3px);
|
|
border-color:var(--accent);
|
|
}
|
|
.card .swatch{
|
|
background:#fff;
|
|
border-radius:10px;
|
|
padding:10px;
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:center;
|
|
}
|
|
.card canvas{display:block; max-width:100%; height:auto;}
|
|
.card .name{
|
|
font-size:13px;
|
|
font-weight:600;
|
|
text-align:center;
|
|
}
|
|
.card .desc{
|
|
font-size:11px;
|
|
color:var(--muted);
|
|
text-align:center;
|
|
line-height:1.4;
|
|
}
|
|
footer{
|
|
max-width:1200px;
|
|
margin:50px auto 0;
|
|
text-align:center;
|
|
color:var(--muted);
|
|
font-size:12px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<h1>QR Code Design Showcase</h1>
|
|
<p>Ein Baukasten für QR-Code-Designs — Modulformen, Ecken-Stile, Farbverläufe, Rahmen, Logos & mehr. Klick auf eine Karte, um sie in den Editor zu laden.</p>
|
|
</header>
|
|
|
|
<div class="customizer">
|
|
<div>
|
|
|
|
<div class="group">
|
|
<div class="group-title">Inhalt</div>
|
|
<div class="field">
|
|
<label>Inhalt / Link</label>
|
|
<input type="text" id="dataInput" value="https://qrmaster.com" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Fehlerkorrektur</label>
|
|
<select id="ecLevel">
|
|
<option value="L">L (7%)</option>
|
|
<option value="M" selected>M (15%)</option>
|
|
<option value="Q">Q (25%)</option>
|
|
<option value="H">H (30%) — empfohlen bei Logo</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group">
|
|
<div class="group-title">Formen</div>
|
|
<div class="row2">
|
|
<div class="field">
|
|
<label>Modul-Form</label>
|
|
<select id="bodyShape">
|
|
<option value="square">Eckig (klassisch)</option>
|
|
<option value="rounded" selected>Abgerundet</option>
|
|
<option value="dots">Punkte / Dots</option>
|
|
<option value="classy">Classy (fließend)</option>
|
|
<option value="classy-rounded">Classy Rounded</option>
|
|
<option value="liquid">Liquid / Blob</option>
|
|
<option value="diamond">Diamant</option>
|
|
<option value="star">Sterne</option>
|
|
<option value="hexagon">Hexagon</option>
|
|
<option value="plus">Plus / Kreuz</option>
|
|
<option value="mosaic">Mosaic (gemischt)</option>
|
|
</select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Ecken-Rahmen (Eyes)</label>
|
|
<select id="eyeFrame">
|
|
<option value="square">Eckig</option>
|
|
<option value="rounded" selected>Abgerundet</option>
|
|
<option value="circle">Kreis</option>
|
|
<option value="leaf">Blatt</option>
|
|
<option value="flower">Flower / Squircle</option>
|
|
<option value="hexagon">Hexagon</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Ecken-Kern (Eye Ball)</label>
|
|
<select id="eyeBall">
|
|
<option value="square">Eckig</option>
|
|
<option value="rounded">Abgerundet</option>
|
|
<option value="circle" selected>Kreis</option>
|
|
<option value="diamond">Diamant</option>
|
|
<option value="star">Stern</option>
|
|
<option value="hexagon">Hexagon</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group">
|
|
<div class="group-title">Farben</div>
|
|
<div class="field">
|
|
<label>Farb-Modus</label>
|
|
<select id="colorMode">
|
|
<option value="solid">Einfarbig</option>
|
|
<option value="linear" selected>Verlauf (linear)</option>
|
|
<option value="radial">Verlauf (radial)</option>
|
|
</select>
|
|
</div>
|
|
<div class="row2">
|
|
<div class="field">
|
|
<label>Farbe 1</label>
|
|
<input type="color" id="color1" value="#7c5cff" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Farbe 2</label>
|
|
<input type="color" id="color2" value="#00d4b5" />
|
|
</div>
|
|
</div>
|
|
<div class="field" id="angleField">
|
|
<label>Verlaufswinkel <span class="val" id="angleVal">45°</span></label>
|
|
<input type="range" id="gradAngle" min="0" max="359" value="45" />
|
|
</div>
|
|
<label class="checkline">
|
|
<input type="checkbox" id="eyeColorToggle" />
|
|
Eigene Farbe für die Ecken (Eyes)
|
|
</label>
|
|
<div class="row2" id="eyeColorFields" style="display:none;">
|
|
<div class="field">
|
|
<label>Eye-Farbe 1</label>
|
|
<input type="color" id="eyeColor1" value="#111111" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Eye-Farbe 2</label>
|
|
<input type="color" id="eyeColor2" value="#111111" />
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Hintergrund</label>
|
|
<input type="color" id="bgColor" value="#ffffff" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group">
|
|
<div class="group-title">Rahmen & Label</div>
|
|
<div class="field">
|
|
<label>Rahmen-Stil</label>
|
|
<select id="frameStyle">
|
|
<option value="none">Kein Rahmen</option>
|
|
<option value="card">Weiße Karte mit Schatten</option>
|
|
<option value="label-bottom">Karte + Label unten</option>
|
|
<option value="label-top">Karte + Label oben</option>
|
|
</select>
|
|
</div>
|
|
<div class="row2" id="labelFields" style="display:none;">
|
|
<div class="field">
|
|
<label>Label-Text</label>
|
|
<input type="text" id="labelText" value="SCAN MICH" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Label-Farbe</label>
|
|
<input type="color" id="labelColor" value="#111111" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group">
|
|
<div class="group-title">Logo in der Mitte</div>
|
|
<div class="field">
|
|
<label>Logo-Symbol</label>
|
|
<select id="logoShape">
|
|
<option value="none">Kein Logo</option>
|
|
<option value="dot">Punkt</option>
|
|
<option value="square">Quadrat</option>
|
|
<option value="heart">Herz</option>
|
|
<option value="bolt">Blitz</option>
|
|
<option value="wifi">WLAN-Symbol</option>
|
|
</select>
|
|
</div>
|
|
<div class="row2">
|
|
<div class="field">
|
|
<label>Logo-Farbe</label>
|
|
<input type="color" id="logoColor" value="#111111" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Logo-Hintergrund</label>
|
|
<input type="color" id="logoBg" value="#ffffff" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group">
|
|
<div class="group-title">Feintuning</div>
|
|
<div class="row3">
|
|
<div class="field">
|
|
<label>Modulgröße <span class="val" id="moduleVal">8</span></label>
|
|
<input type="range" id="moduleSize" min="4" max="14" value="8" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Weißraum <span class="val" id="quietVal">2</span></label>
|
|
<input type="range" id="quietZone" min="0" max="6" value="2" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Rotation <span class="val" id="rotVal">0°</span></label>
|
|
<input type="range" id="rotation" min="0" max="359" value="0" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="preview-wrap">
|
|
<div class="preview-card">
|
|
<canvas id="liveCanvas" width="420" height="420"></canvas>
|
|
</div>
|
|
<div class="btn-row">
|
|
<button class="dl-btn" id="downloadBtn">PNG herunterladen</button>
|
|
<button class="dl-btn secondary" id="randomBtn">🎲 Zufalls-Design</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="section-title">Design-Galerie <span class="badge" id="galleryCount">Varianten — anklicken zum Übernehmen</span></h2>
|
|
<div class="gallery" id="gallery"></div>
|
|
|
|
<footer>QR Master · Design Showcase · reine Frontend-Demo, alle Codes sind scanbar</footer>
|
|
|
|
<script>
|
|
/* ---------- QR matrix helper ---------- */
|
|
function getMatrix(text, ec){
|
|
const qr = qrcode(0, ec || 'M');
|
|
qr.addData(text || ' ');
|
|
qr.make();
|
|
const n = qr.getModuleCount();
|
|
const matrix = [];
|
|
for(let r=0;r<n;r++){
|
|
const row = [];
|
|
for(let c=0;c<n;c++) row.push(qr.isDark(r,c));
|
|
matrix.push(row);
|
|
}
|
|
return matrix;
|
|
}
|
|
|
|
function isInEye(r,c,n){
|
|
const eyes = [[0,0],[0,n-7],[n-7,0]];
|
|
return eyes.some(([er,ec])=> r>=er && r<er+7 && c>=ec && c<ec+7);
|
|
}
|
|
|
|
/* simple deterministic hash for mosaic variety */
|
|
function cellHash(r,c){
|
|
const v = Math.sin(r*127.1 + c*311.7) * 43758.5453;
|
|
return v - Math.floor(v);
|
|
}
|
|
|
|
/* ---------- shape drawing primitives ---------- */
|
|
function variableRoundRect(ctx,x,y,w,h,tl,tr,br,bl){
|
|
ctx.moveTo(x+tl,y);
|
|
ctx.lineTo(x+w-tr,y);
|
|
if(tr) ctx.arcTo(x+w,y,x+w,y+tr,tr); else ctx.lineTo(x+w,y);
|
|
ctx.lineTo(x+w,y+h-br);
|
|
if(br) ctx.arcTo(x+w,y+h,x+w-br,y+h,br); else ctx.lineTo(x+w,y+h);
|
|
ctx.lineTo(x+bl,y+h);
|
|
if(bl) ctx.arcTo(x,y+h,x,y+h-bl,bl); else ctx.lineTo(x,y+h);
|
|
ctx.lineTo(x,y+tl);
|
|
if(tl) ctx.arcTo(x,y,x+tl,y,tl); else ctx.lineTo(x,y);
|
|
ctx.closePath();
|
|
}
|
|
|
|
function hexagonPath(ctx,cx,cy,r){
|
|
for(let i=0;i<6;i++){
|
|
const angle = Math.PI/180*(60*i - 30);
|
|
const px = cx + r*Math.cos(angle);
|
|
const py = cy + r*Math.sin(angle);
|
|
if(i===0) ctx.moveTo(px,py); else ctx.lineTo(px,py);
|
|
}
|
|
ctx.closePath();
|
|
}
|
|
|
|
function starPath(ctx,cx,cy,outerR,innerR,spikes){
|
|
let rot = Math.PI/2*3;
|
|
const step = Math.PI/spikes;
|
|
ctx.moveTo(cx, cy-outerR);
|
|
for(let i=0;i<spikes;i++){
|
|
ctx.lineTo(cx+Math.cos(rot)*outerR, cy+Math.sin(rot)*outerR); rot+=step;
|
|
ctx.lineTo(cx+Math.cos(rot)*innerR, cy+Math.sin(rot)*innerR); rot+=step;
|
|
}
|
|
ctx.closePath();
|
|
}
|
|
|
|
function drawBodyModule(ctx, x, y, size, shape, neighbors, r, c){
|
|
const {top,bottom,left,right} = neighbors;
|
|
ctx.beginPath();
|
|
switch(shape){
|
|
case 'square': {
|
|
ctx.rect(x,y,size,size);
|
|
break;
|
|
}
|
|
case 'dots': {
|
|
ctx.arc(x+size/2,y+size/2,size*0.42,0,Math.PI*2);
|
|
break;
|
|
}
|
|
case 'diamond': {
|
|
ctx.moveTo(x+size/2,y);
|
|
ctx.lineTo(x+size,y+size/2);
|
|
ctx.lineTo(x+size/2,y+size);
|
|
ctx.lineTo(x,y+size/2);
|
|
ctx.closePath();
|
|
break;
|
|
}
|
|
case 'star': {
|
|
starPath(ctx, x+size/2, y+size/2, size*0.5, size*0.22, 5);
|
|
break;
|
|
}
|
|
case 'hexagon': {
|
|
hexagonPath(ctx, x+size/2, y+size/2, size*0.52);
|
|
break;
|
|
}
|
|
case 'plus': {
|
|
const armW = size*0.42;
|
|
const off = (size-armW)/2;
|
|
ctx.rect(x+off, y, armW, size);
|
|
ctx.rect(x, y+off, size, armW);
|
|
break;
|
|
}
|
|
case 'mosaic': {
|
|
const h = cellHash(r,c);
|
|
if(h < 0.34){
|
|
ctx.arc(x+size/2,y+size/2,size*0.42,0,Math.PI*2);
|
|
} else if(h < 0.67){
|
|
const rMed = size*0.28;
|
|
variableRoundRect(ctx,x,y,size,size,rMed,rMed,rMed,rMed);
|
|
} else {
|
|
ctx.rect(x,y,size,size);
|
|
}
|
|
break;
|
|
}
|
|
case 'liquid': {
|
|
const rBig = size*0.55;
|
|
const tl = (!top && !left) ? rBig : 0;
|
|
const tr = (!top && !right) ? rBig : 0;
|
|
const br = (!bottom && !right) ? rBig : 0;
|
|
const bl = (!bottom && !left) ? rBig : 0;
|
|
variableRoundRect(ctx,x,y,size,size,tl,tr,br,bl);
|
|
break;
|
|
}
|
|
case 'classy': {
|
|
const rBig = size*0.5;
|
|
const tl = (!top && !left) ? rBig : 0;
|
|
const tr = (!top && !right) ? rBig : 0;
|
|
const br = (!bottom && !right) ? rBig : 0;
|
|
const bl = (!bottom && !left) ? rBig : 0;
|
|
variableRoundRect(ctx,x,y,size,size,tl,tr,br,bl);
|
|
break;
|
|
}
|
|
case 'classy-rounded': {
|
|
const rBig = size*0.5;
|
|
const tl = (!top && !left) ? rBig : (top&&left? 0: rBig*0.35);
|
|
const tr = (!top && !right) ? rBig : (top&&right? 0: rBig*0.35);
|
|
const br = (!bottom && !right) ? rBig : (bottom&&right? 0: rBig*0.35);
|
|
const bl = (!bottom && !left) ? rBig : (bottom&&left? 0: rBig*0.35);
|
|
variableRoundRect(ctx,x,y,size,size,tl,tr,br,bl);
|
|
break;
|
|
}
|
|
case 'rounded':
|
|
default: {
|
|
const rMed = size*0.32;
|
|
const tl = (!top && !left) ? rMed : 0;
|
|
const tr = (!top && !right) ? rMed : 0;
|
|
const br = (!bottom && !right) ? rMed : 0;
|
|
const bl = (!bottom && !left) ? rMed : 0;
|
|
variableRoundRect(ctx,x,y,size,size,tl,tr,br,bl);
|
|
break;
|
|
}
|
|
}
|
|
ctx.fill();
|
|
}
|
|
|
|
function drawEyeFrame(ctx,x,y,size,shape){
|
|
const s = size;
|
|
ctx.beginPath();
|
|
if(shape==='circle'){
|
|
ctx.arc(x+s/2,y+s/2,s/2,0,Math.PI*2);
|
|
ctx.arc(x+s/2,y+s/2,s/2*(5/7),0,Math.PI*2,true);
|
|
} else if(shape==='hexagon'){
|
|
hexagonPath(ctx, x+s/2, y+s/2, s/2);
|
|
hexagonPath(ctx, x+s/2, y+s/2, s/2*(5/7));
|
|
} else if(shape==='leaf'){
|
|
variableRoundRect(ctx,x,y,s,s, s*0.45,0,s*0.45,0);
|
|
variableRoundRect(ctx,x+s*(1/7),y+s*(1/7), s*(5/7), s*(5/7), s*0.32*(5/7),0,s*0.32*(5/7),0);
|
|
} else if(shape==='flower'){
|
|
variableRoundRect(ctx,x,y,s,s, s*0.42,s*0.42,s*0.42,s*0.42);
|
|
variableRoundRect(ctx,x+s*(1/7),y+s*(1/7), s*(5/7), s*(5/7), s*0.4*(5/7),s*0.4*(5/7),s*0.4*(5/7),s*0.4*(5/7));
|
|
} else if(shape==='rounded'){
|
|
variableRoundRect(ctx,x,y,s,s,s*0.28,s*0.28,s*0.28,s*0.28);
|
|
variableRoundRect(ctx,x+s*(1/7),y+s*(1/7), s*(5/7), s*(5/7), s*0.22,s*0.22,s*0.22,s*0.22);
|
|
} else {
|
|
ctx.rect(x,y,s,s);
|
|
ctx.rect(x+s*(1/7),y+s*(1/7), s*(5/7), s*(5/7));
|
|
}
|
|
ctx.fill('evenodd');
|
|
}
|
|
|
|
function drawEyeBall(ctx,x,y,size,shape){
|
|
ctx.beginPath();
|
|
if(shape==='circle'){
|
|
ctx.arc(x+size/2,y+size/2,size/2,0,Math.PI*2);
|
|
} else if(shape==='diamond'){
|
|
ctx.moveTo(x+size/2,y);
|
|
ctx.lineTo(x+size,y+size/2);
|
|
ctx.lineTo(x+size/2,y+size);
|
|
ctx.lineTo(x,y+size/2);
|
|
ctx.closePath();
|
|
} else if(shape==='star'){
|
|
starPath(ctx, x+size/2, y+size/2, size*0.52, size*0.22, 5);
|
|
} else if(shape==='hexagon'){
|
|
hexagonPath(ctx, x+size/2, y+size/2, size*0.55);
|
|
} else if(shape==='rounded'){
|
|
variableRoundRect(ctx,x,y,size,size,size*0.3,size*0.3,size*0.3,size*0.3);
|
|
} else {
|
|
ctx.rect(x,y,size,size);
|
|
}
|
|
ctx.fill();
|
|
}
|
|
|
|
function makeGradient(ctx, colorMode, color1, color2, size, angleDeg){
|
|
if(colorMode === 'linear'){
|
|
const rad = (angleDeg||0) * Math.PI/180;
|
|
const cx = size/2, cy = size/2;
|
|
const dx = Math.cos(rad) * size/2;
|
|
const dy = Math.sin(rad) * size/2;
|
|
const g = ctx.createLinearGradient(cx-dx, cy-dy, cx+dx, cy+dy);
|
|
g.addColorStop(0,color1); g.addColorStop(1,color2);
|
|
return g;
|
|
} else if(colorMode === 'radial'){
|
|
const g = ctx.createRadialGradient(size/2,size/2,size*0.05,size/2,size/2,size*0.7);
|
|
g.addColorStop(0,color1); g.addColorStop(1,color2);
|
|
return g;
|
|
}
|
|
return color1;
|
|
}
|
|
|
|
/* ---------- logo drawing ---------- */
|
|
function drawLogo(ctx, cx, cy, r, shape, color, bgColor){
|
|
if(shape === 'none') return;
|
|
// knockout background circle
|
|
ctx.beginPath();
|
|
ctx.arc(cx,cy,r,0,Math.PI*2);
|
|
ctx.fillStyle = bgColor;
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle = color;
|
|
const ir = r*0.62;
|
|
ctx.beginPath();
|
|
if(shape==='dot'){
|
|
ctx.arc(cx,cy,ir*0.7,0,Math.PI*2);
|
|
} else if(shape==='square'){
|
|
const s = ir*1.1;
|
|
variableRoundRect(ctx, cx-s/2, cy-s/2, s, s, s*0.2,s*0.2,s*0.2,s*0.2);
|
|
} else if(shape==='heart'){
|
|
const s = ir*1.3;
|
|
ctx.moveTo(cx, cy+s*0.35);
|
|
ctx.bezierCurveTo(cx+s*0.7, cy-s*0.3, cx+s*0.15, cy-s*0.65, cx, cy-s*0.2);
|
|
ctx.bezierCurveTo(cx-s*0.15, cy-s*0.65, cx-s*0.7, cy-s*0.3, cx, cy+s*0.35);
|
|
ctx.closePath();
|
|
} else if(shape==='bolt'){
|
|
const s = ir*1.4;
|
|
ctx.moveTo(cx+s*0.1, cy-s*0.6);
|
|
ctx.lineTo(cx-s*0.35, cy+s*0.05);
|
|
ctx.lineTo(cx-s*0.05, cy+s*0.05);
|
|
ctx.lineTo(cx-s*0.15, cy+s*0.6);
|
|
ctx.lineTo(cx+s*0.4, cy-s*0.1);
|
|
ctx.lineTo(cx+s*0.05, cy-s*0.1);
|
|
ctx.closePath();
|
|
} else if(shape==='wifi'){
|
|
ctx.fill(); // flush knockout first
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy+ir*0.35, ir*0.14, 0, Math.PI*2);
|
|
ctx.fill();
|
|
ctx.lineWidth = ir*0.16;
|
|
ctx.strokeStyle = color;
|
|
for(let i=1;i<=2;i++){
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy+ir*0.35, ir*0.4*i, Math.PI*1.25, Math.PI*1.75);
|
|
ctx.stroke();
|
|
}
|
|
return;
|
|
}
|
|
ctx.fill();
|
|
}
|
|
|
|
/* ---------- main renderer ---------- */
|
|
function renderQR(canvas, opts){
|
|
const {
|
|
text, ec='M', module=8, quiet=2,
|
|
bodyShape='square', eyeFrame='square', eyeBall='square',
|
|
colorMode='solid', color1='#000000', color2='#000000', gradAngle=45,
|
|
eyeColorMode=false, eyeColor1='#000000', eyeColor2='#000000',
|
|
bgColor='#ffffff', rotation=0,
|
|
frameStyle='none', labelText='SCAN MICH', labelColor='#111111',
|
|
logoShape='none', logoColor='#111111', logoBg='#ffffff'
|
|
} = opts;
|
|
|
|
const matrix = getMatrix(text, ec);
|
|
const n = matrix.length;
|
|
const qrSize = (n + quiet*2) * module;
|
|
|
|
// offscreen canvas: pure QR code (rotatable), no frame
|
|
const qrCanvas = document.createElement('canvas');
|
|
qrCanvas.width = qrSize;
|
|
qrCanvas.height = qrSize;
|
|
const qctx = qrCanvas.getContext('2d');
|
|
qctx.fillStyle = bgColor;
|
|
qctx.fillRect(0,0,qrSize,qrSize);
|
|
|
|
const bodyFill = makeGradient(qctx, colorMode, color1, color2, qrSize, gradAngle);
|
|
const eyeFill = eyeColorMode ? makeGradient(qctx, colorMode==='solid'?'solid':colorMode, eyeColor1, eyeColor2, qrSize, gradAngle) : bodyFill;
|
|
|
|
const offset = quiet*module;
|
|
|
|
qctx.fillStyle = bodyFill;
|
|
for(let r=0;r<n;r++){
|
|
for(let c=0;c<n;c++){
|
|
if(!matrix[r][c]) continue;
|
|
if(isInEye(r,c,n)) continue;
|
|
const x = offset + c*module;
|
|
const y = offset + r*module;
|
|
const neighbors = {
|
|
top: r>0 && matrix[r-1][c] && !isInEye(r-1,c,n),
|
|
bottom: r<n-1 && matrix[r+1][c] && !isInEye(r+1,c,n),
|
|
left: c>0 && matrix[r][c-1] && !isInEye(r,c-1,n),
|
|
right: c<n-1 && matrix[r][c+1] && !isInEye(r,c+1,n)
|
|
};
|
|
drawBodyModule(qctx, x, y, module, bodyShape, neighbors, r, c);
|
|
}
|
|
}
|
|
|
|
qctx.fillStyle = eyeFill;
|
|
const eyePositions = [[0,0],[0,n-7],[n-7,0]];
|
|
eyePositions.forEach(([er,ecol])=>{
|
|
const x = offset + ecol*module;
|
|
const y = offset + er*module;
|
|
const s = module*7;
|
|
drawEyeFrame(qctx,x,y,s,eyeFrame);
|
|
const ballSize = module*3;
|
|
const bx = x + module*2;
|
|
const by = y + module*2;
|
|
drawEyeBall(qctx,bx,by,ballSize,eyeBall);
|
|
});
|
|
|
|
// logo on top of QR (before rotation, so it rotates together)
|
|
if(logoShape !== 'none'){
|
|
const r = qrSize * 0.13;
|
|
drawLogo(qctx, qrSize/2, qrSize/2, r, logoShape, logoColor, logoBg);
|
|
}
|
|
|
|
/* ---- compose final canvas with frame/label + rotation ---- */
|
|
const hasLabel = frameStyle === 'label-bottom' || frameStyle === 'label-top';
|
|
const framePad = frameStyle === 'none' ? 0 : Math.round(qrSize*0.09);
|
|
const bandH = hasLabel ? Math.round(qrSize*0.16) : 0;
|
|
|
|
const totalW = qrSize + framePad*2;
|
|
const totalH = qrSize + framePad*2 + bandH;
|
|
|
|
canvas.width = totalW;
|
|
canvas.height = totalH;
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.clearRect(0,0,totalW,totalH);
|
|
|
|
if(frameStyle !== 'none'){
|
|
ctx.save();
|
|
ctx.shadowColor = 'rgba(0,0,0,0.18)';
|
|
ctx.shadowBlur = qrSize*0.03;
|
|
ctx.shadowOffsetY = qrSize*0.01;
|
|
ctx.fillStyle = '#ffffff';
|
|
variableRoundRect(ctx, 2,2, totalW-4, totalH-4, 18,18,18,18);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
}
|
|
|
|
let qrX = framePad, qrY = framePad;
|
|
if(frameStyle === 'label-top') qrY = framePad + bandH;
|
|
|
|
// rotate only the QR portion around its own center
|
|
ctx.save();
|
|
const cx = qrX + qrSize/2, cy = qrY + qrSize/2;
|
|
ctx.translate(cx,cy);
|
|
ctx.rotate((rotation||0)*Math.PI/180);
|
|
ctx.drawImage(qrCanvas, -qrSize/2, -qrSize/2);
|
|
ctx.restore();
|
|
|
|
if(hasLabel){
|
|
const bandY = frameStyle === 'label-bottom' ? framePad + qrSize + (bandH-qrSize*0)*0 : 0;
|
|
ctx.fillStyle = labelColor;
|
|
ctx.font = `bold ${Math.round(bandH*0.5)}px -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif`;
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
const textY = frameStyle === 'label-bottom'
|
|
? framePad + qrSize + bandH/2
|
|
: framePad*0.55 + bandH*0.5;
|
|
// small arrow accent
|
|
ctx.fillText(labelText || 'SCAN MICH', totalW/2, textY);
|
|
}
|
|
}
|
|
|
|
/* ---------- gallery config ---------- */
|
|
const presets = [
|
|
{name:'Klassisch Schwarz', desc:'Eckig, kein Verlauf', bodyShape:'square', eyeFrame:'square', eyeBall:'square', colorMode:'solid', color1:'#111111', color2:'#111111', bgColor:'#ffffff'},
|
|
{name:'Soft Rounded', desc:'Abgerundete Module & Eyes', bodyShape:'rounded', eyeFrame:'rounded', eyeBall:'circle', colorMode:'solid', color1:'#1f2937', color2:'#1f2937', bgColor:'#ffffff'},
|
|
{name:'Dot Matrix', desc:'Punkte, runde Eyes', bodyShape:'dots', eyeFrame:'circle', eyeBall:'circle', colorMode:'solid', color1:'#0f172a', color2:'#0f172a', bgColor:'#ffffff'},
|
|
{name:'Violett-Türkis Fluss', desc:'Classy + linearer Verlauf', bodyShape:'classy', eyeFrame:'rounded', eyeBall:'circle', colorMode:'linear', color1:'#7c5cff', color2:'#00d4b5', gradAngle:45, bgColor:'#ffffff'},
|
|
{name:'Sunset Dots', desc:'Punkte + radialer Verlauf', bodyShape:'dots', eyeFrame:'leaf', eyeBall:'circle', colorMode:'radial', color1:'#ff5f6d', color2:'#ffc371', bgColor:'#fff8f0'},
|
|
{name:'Diamant Grid', desc:'Rautenförmige Module', bodyShape:'diamond', eyeFrame:'square', eyeBall:'diamond', colorMode:'solid', color1:'#111827', color2:'#111827', bgColor:'#ffffff'},
|
|
{name:'Classy Rounded Blue', desc:'Fließend, blauer Verlauf', bodyShape:'classy-rounded', eyeFrame:'rounded', eyeBall:'rounded', colorMode:'linear', color1:'#0ea5e9', color2:'#1e3a8a', gradAngle:90, bgColor:'#ffffff'},
|
|
{name:'Star Burst', desc:'Sterne als Module', bodyShape:'star', eyeFrame:'circle', eyeBall:'star', colorMode:'solid', color1:'#f59e0b', color2:'#f59e0b', bgColor:'#111111'},
|
|
{name:'Mint auf Dunkel', desc:'Dots, dunkler Hintergrund', bodyShape:'dots', eyeFrame:'rounded', eyeBall:'circle', colorMode:'solid', color1:'#00d4b5', color2:'#00d4b5', bgColor:'#0f1115'},
|
|
{name:'Rosa Verlauf Leaf-Eyes', desc:'Blatt-förmige Ecken', bodyShape:'rounded', eyeFrame:'leaf', eyeBall:'circle', colorMode:'linear', color1:'#f472b6', color2:'#a855f7', gradAngle:120, bgColor:'#fdf4ff'},
|
|
{name:'Feuer Diamant', desc:'Diamant + radialer Verlauf', bodyShape:'diamond', eyeFrame:'circle', eyeBall:'diamond', colorMode:'radial', color1:'#f97316', color2:'#7c2d12', bgColor:'#fff7ed'},
|
|
{name:'Monochrome Classy', desc:'Fließende Formen, Grautöne', bodyShape:'classy', eyeFrame:'square', eyeBall:'rounded', colorMode:'linear', color1:'#374151', color2:'#9ca3af', gradAngle:60, bgColor:'#ffffff'},
|
|
{name:'Ocean Dots', desc:'Punkte, Kreis-Eyes, Blauverlauf', bodyShape:'dots', eyeFrame:'circle', eyeBall:'circle', colorMode:'linear', color1:'#0369a1', color2:'#06b6d4', gradAngle:30, bgColor:'#f0f9ff'},
|
|
{name:'Neon auf Schwarz', desc:'Rounded, grelle Farben', bodyShape:'rounded', eyeFrame:'rounded', eyeBall:'circle', colorMode:'linear', color1:'#39ff14', color2:'#00e5ff', gradAngle:15, bgColor:'#000000'},
|
|
{name:'Klassisch mit Kreis-Eyes', desc:'Eckige Module, runde Ecken-Kerne', bodyShape:'square', eyeFrame:'square', eyeBall:'circle', colorMode:'solid', color1:'#1e293b', color2:'#1e293b', bgColor:'#ffffff'},
|
|
{name:'Gold Classy Rounded', desc:'Elegant, goldener Verlauf', bodyShape:'classy-rounded', eyeFrame:'leaf', eyeBall:'circle', colorMode:'linear', color1:'#b45309', color2:'#fde68a', gradAngle:200, bgColor:'#1c1917'},
|
|
{name:'Pastell Diamant', desc:'Diamant-Module, sanfte Farben', bodyShape:'diamond', eyeFrame:'rounded', eyeBall:'rounded', colorMode:'linear', color1:'#a78bfa', color2:'#f9a8d4', gradAngle:80, bgColor:'#fdf2ff'},
|
|
{name:'Sterne Regenbogen', desc:'Sterne + Multicolor-Verlauf', bodyShape:'star', eyeFrame:'rounded', eyeBall:'rounded', colorMode:'radial', color1:'#ef4444', color2:'#3b82f6', bgColor:'#ffffff'},
|
|
{name:'Minimal Dots Grau', desc:'Sehr dezent, für Print', bodyShape:'dots', eyeFrame:'square', eyeBall:'square', colorMode:'solid', color1:'#4b5563', color2:'#4b5563', bgColor:'#ffffff'},
|
|
{name:'Cyberpunk Classy', desc:'Fließend, Pink/Cyan auf Schwarz', bodyShape:'classy', eyeFrame:'circle', eyeBall:'diamond', colorMode:'linear', color1:'#ff00c8', color2:'#00fff7', gradAngle:135, bgColor:'#0a0a0a'},
|
|
{name:'Hexagon Grid', desc:'Sechseckige Module', bodyShape:'hexagon', eyeFrame:'hexagon', eyeBall:'hexagon', colorMode:'solid', color1:'#0f172a', color2:'#0f172a', bgColor:'#ffffff'},
|
|
{name:'Plus Muster', desc:'Kreuz-Module, Retro-Look', bodyShape:'plus', eyeFrame:'square', eyeBall:'square', colorMode:'solid', color1:'#dc2626', color2:'#dc2626', bgColor:'#fff1f2'},
|
|
{name:'Liquid Blob Lila', desc:'Fließende Blob-Module', bodyShape:'liquid', eyeFrame:'flower', eyeBall:'circle', colorMode:'linear', color1:'#8b5cf6', color2:'#ec4899', gradAngle:45, bgColor:'#ffffff'},
|
|
{name:'Mosaic Textur', desc:'Gemischte Formen pro Modul', bodyShape:'mosaic', eyeFrame:'rounded', eyeBall:'circle', colorMode:'solid', color1:'#1e293b', color2:'#1e293b', bgColor:'#f8fafc'},
|
|
{name:'Karte mit Schatten', desc:'Weiße Card, klassisch', bodyShape:'rounded', eyeFrame:'rounded', eyeBall:'circle', colorMode:'solid', color1:'#111111', color2:'#111111', bgColor:'#ffffff', frameStyle:'card'},
|
|
{name:'Scan-Mich Banner', desc:'Label unten, Call-to-Action', bodyShape:'dots', eyeFrame:'circle', eyeBall:'circle', colorMode:'linear', color1:'#7c5cff', color2:'#00d4b5', gradAngle:45, bgColor:'#ffffff', frameStyle:'label-bottom', labelText:'SCAN MICH', labelColor:'#111111'},
|
|
{name:'Logo Herz', desc:'Zentrales Herz-Logo', bodyShape:'rounded', eyeFrame:'rounded', eyeBall:'circle', colorMode:'linear', color1:'#f43f5e', color2:'#fb923c', gradAngle:45, bgColor:'#ffffff', logoShape:'heart', logoColor:'#f43f5e', logoBg:'#ffffff'},
|
|
{name:'WLAN-Logo Business', desc:'Wifi-Symbol, separate Eye-Farbe', bodyShape:'classy-rounded', eyeFrame:'rounded', eyeBall:'circle', colorMode:'solid', color1:'#1e3a8a', color2:'#1e3a8a', bgColor:'#ffffff', logoShape:'wifi', logoColor:'#1e3a8a', logoBg:'#ffffff', eyeColorMode:true, eyeColor1:'#f97316', eyeColor2:'#f97316'},
|
|
{name:'Blitz Neon', desc:'Bolt-Logo auf Neon-Verlauf', bodyShape:'dots', eyeFrame:'circle', eyeBall:'circle', colorMode:'linear', color1:'#facc15', color2:'#f97316', gradAngle:60, bgColor:'#111111', logoShape:'bolt', logoColor:'#111111', logoBg:'#facc15'},
|
|
{name:'Gedrehtes Muster', desc:'45° Rotation, Diamant-Module', bodyShape:'diamond', eyeFrame:'square', eyeBall:'diamond', colorMode:'solid', color1:'#0f172a', color2:'#0f172a', bgColor:'#ffffff', rotation:45},
|
|
{name:'Zweifarbige Eyes', desc:'Body schwarz, Eyes im Verlauf', bodyShape:'rounded', eyeFrame:'rounded', eyeBall:'circle', colorMode:'solid', color1:'#111111', color2:'#111111', bgColor:'#ffffff', eyeColorMode:true, eyeColor1:'#7c5cff', eyeColor2:'#00d4b5'},
|
|
{name:'Business Karte Top-Label', desc:'Label oben, dezentes Business-Design', bodyShape:'square', eyeFrame:'rounded', eyeBall:'square', colorMode:'solid', color1:'#111827', color2:'#111827', bgColor:'#ffffff', frameStyle:'label-top', labelText:'JETZT SCANNEN', labelColor:'#111827'}
|
|
];
|
|
|
|
/* ---------- wiring ---------- */
|
|
const dataInput = document.getElementById('dataInput');
|
|
const bodyShapeSel = document.getElementById('bodyShape');
|
|
const eyeFrameSel = document.getElementById('eyeFrame');
|
|
const eyeBallSel = document.getElementById('eyeBall');
|
|
const colorModeSel = document.getElementById('colorMode');
|
|
const color1Input = document.getElementById('color1');
|
|
const color2Input = document.getElementById('color2');
|
|
const gradAngleInput = document.getElementById('gradAngle');
|
|
const angleVal = document.getElementById('angleVal');
|
|
const angleField = document.getElementById('angleField');
|
|
const eyeColorToggle = document.getElementById('eyeColorToggle');
|
|
const eyeColorFields = document.getElementById('eyeColorFields');
|
|
const eyeColor1Input = document.getElementById('eyeColor1');
|
|
const eyeColor2Input = document.getElementById('eyeColor2');
|
|
const bgColorInput = document.getElementById('bgColor');
|
|
const ecLevelSel = document.getElementById('ecLevel');
|
|
const frameStyleSel = document.getElementById('frameStyle');
|
|
const labelFields = document.getElementById('labelFields');
|
|
const labelTextInput = document.getElementById('labelText');
|
|
const labelColorInput = document.getElementById('labelColor');
|
|
const logoShapeSel = document.getElementById('logoShape');
|
|
const logoColorInput = document.getElementById('logoColor');
|
|
const logoBgInput = document.getElementById('logoBg');
|
|
const moduleSizeInput = document.getElementById('moduleSize');
|
|
const moduleVal = document.getElementById('moduleVal');
|
|
const quietZoneInput = document.getElementById('quietZone');
|
|
const quietVal = document.getElementById('quietVal');
|
|
const rotationInput = document.getElementById('rotation');
|
|
const rotVal = document.getElementById('rotVal');
|
|
const liveCanvas = document.getElementById('liveCanvas');
|
|
const downloadBtn = document.getElementById('downloadBtn');
|
|
const randomBtn = document.getElementById('randomBtn');
|
|
const galleryCount = document.getElementById('galleryCount');
|
|
|
|
function currentOpts(){
|
|
return {
|
|
text: dataInput.value || 'https://qrmaster.com',
|
|
ec: ecLevelSel.value,
|
|
module: parseInt(moduleSizeInput.value,10),
|
|
quiet: parseInt(quietZoneInput.value,10),
|
|
bodyShape: bodyShapeSel.value,
|
|
eyeFrame: eyeFrameSel.value,
|
|
eyeBall: eyeBallSel.value,
|
|
colorMode: colorModeSel.value,
|
|
color1: color1Input.value,
|
|
color2: color2Input.value,
|
|
gradAngle: parseInt(gradAngleInput.value,10),
|
|
eyeColorMode: eyeColorToggle.checked,
|
|
eyeColor1: eyeColor1Input.value,
|
|
eyeColor2: eyeColor2Input.value,
|
|
bgColor: bgColorInput.value,
|
|
rotation: parseInt(rotationInput.value,10),
|
|
frameStyle: frameStyleSel.value,
|
|
labelText: labelTextInput.value,
|
|
labelColor: labelColorInput.value,
|
|
logoShape: logoShapeSel.value,
|
|
logoColor: logoColorInput.value,
|
|
logoBg: logoBgInput.value
|
|
};
|
|
}
|
|
|
|
function refreshLive(){
|
|
angleVal.textContent = gradAngleInput.value + '°';
|
|
moduleVal.textContent = moduleSizeInput.value;
|
|
quietVal.textContent = quietZoneInput.value;
|
|
rotVal.textContent = rotationInput.value + '°';
|
|
angleField.style.display = colorModeSel.value === 'linear' ? '' : 'none';
|
|
eyeColorFields.style.display = eyeColorToggle.checked ? 'grid' : 'none';
|
|
labelFields.style.display = (frameStyleSel.value === 'label-bottom' || frameStyleSel.value === 'label-top') ? 'grid' : 'none';
|
|
renderQR(liveCanvas, currentOpts());
|
|
}
|
|
|
|
[dataInput, bodyShapeSel, eyeFrameSel, eyeBallSel, colorModeSel, color1Input, color2Input,
|
|
gradAngleInput, eyeColorToggle, eyeColor1Input, eyeColor2Input, bgColorInput, ecLevelSel,
|
|
frameStyleSel, labelTextInput, labelColorInput, logoShapeSel, logoColorInput, logoBgInput,
|
|
moduleSizeInput, quietZoneInput, rotationInput]
|
|
.forEach(el => el.addEventListener('input', refreshLive));
|
|
|
|
downloadBtn.addEventListener('click', ()=>{
|
|
const link = document.createElement('a');
|
|
link.download = 'qr-code.png';
|
|
link.href = liveCanvas.toDataURL('image/png');
|
|
link.click();
|
|
});
|
|
|
|
function rand(arr){ return arr[Math.floor(Math.random()*arr.length)]; }
|
|
function randColor(){
|
|
const h = Math.floor(Math.random()*360);
|
|
const s = 60 + Math.floor(Math.random()*30);
|
|
const l = 35 + Math.floor(Math.random()*25);
|
|
return `hsl(${h} ${s}% ${l}%)`;
|
|
}
|
|
function hslToHex(hsl){
|
|
// fallback: use a canvas trick to normalize any CSS color to hex
|
|
const d = document.createElement('canvas').getContext('2d');
|
|
d.fillStyle = hsl;
|
|
return d.fillStyle;
|
|
}
|
|
|
|
randomBtn.addEventListener('click', ()=>{
|
|
const bodyShapes = Array.from(bodyShapeSel.options).map(o=>o.value);
|
|
const eyeFrames = Array.from(eyeFrameSel.options).map(o=>o.value);
|
|
const eyeBalls = Array.from(eyeBallSel.options).map(o=>o.value);
|
|
const colorModes = ['solid','linear','radial'];
|
|
const frameStyles = Array.from(frameStyleSel.options).map(o=>o.value);
|
|
const logoShapes = Array.from(logoShapeSel.options).map(o=>o.value);
|
|
|
|
bodyShapeSel.value = rand(bodyShapes);
|
|
eyeFrameSel.value = rand(eyeFrames);
|
|
eyeBallSel.value = rand(eyeBalls);
|
|
colorModeSel.value = rand(colorModes);
|
|
color1Input.value = hslToHex(randColor());
|
|
color2Input.value = hslToHex(randColor());
|
|
gradAngleInput.value = Math.floor(Math.random()*360);
|
|
eyeColorToggle.checked = Math.random() < 0.35;
|
|
eyeColor1Input.value = hslToHex(randColor());
|
|
eyeColor2Input.value = hslToHex(randColor());
|
|
frameStyleSel.value = rand(frameStyles);
|
|
logoShapeSel.value = Math.random() < 0.3 ? rand(logoShapes) : 'none';
|
|
moduleSizeInput.value = 6 + Math.floor(Math.random()*6);
|
|
rotationInput.value = Math.random() < 0.2 ? Math.floor(Math.random()*4)*90 : 0;
|
|
refreshLive();
|
|
});
|
|
|
|
function applyPresetToEditor(p){
|
|
bodyShapeSel.value = p.bodyShape;
|
|
eyeFrameSel.value = p.eyeFrame;
|
|
eyeBallSel.value = p.eyeBall;
|
|
colorModeSel.value = p.colorMode;
|
|
color1Input.value = p.color1;
|
|
color2Input.value = p.color2;
|
|
gradAngleInput.value = p.gradAngle || 45;
|
|
eyeColorToggle.checked = !!p.eyeColorMode;
|
|
eyeColor1Input.value = p.eyeColor1 || p.color1;
|
|
eyeColor2Input.value = p.eyeColor2 || p.color2;
|
|
bgColorInput.value = p.bgColor;
|
|
frameStyleSel.value = p.frameStyle || 'none';
|
|
labelTextInput.value = p.labelText || 'SCAN MICH';
|
|
labelColorInput.value = p.labelColor || '#111111';
|
|
logoShapeSel.value = p.logoShape || 'none';
|
|
logoColorInput.value = p.logoColor || '#111111';
|
|
logoBgInput.value = p.logoBg || '#ffffff';
|
|
rotationInput.value = p.rotation || 0;
|
|
refreshLive();
|
|
document.querySelector('.customizer').scrollIntoView({behavior:'smooth', block:'center'});
|
|
}
|
|
|
|
function buildGallery(){
|
|
const gallery = document.getElementById('gallery');
|
|
galleryCount.textContent = presets.length + ' Varianten — anklicken zum Übernehmen';
|
|
presets.forEach(p=>{
|
|
const card = document.createElement('div');
|
|
card.className = 'card';
|
|
|
|
const swatch = document.createElement('div');
|
|
swatch.className = 'swatch';
|
|
const canvas = document.createElement('canvas');
|
|
swatch.appendChild(canvas);
|
|
card.appendChild(swatch);
|
|
|
|
const name = document.createElement('div');
|
|
name.className = 'name';
|
|
name.textContent = p.name;
|
|
card.appendChild(name);
|
|
|
|
const desc = document.createElement('div');
|
|
desc.className = 'desc';
|
|
desc.textContent = p.desc;
|
|
card.appendChild(desc);
|
|
|
|
card.addEventListener('click', ()=> applyPresetToEditor(p));
|
|
gallery.appendChild(card);
|
|
|
|
renderQR(canvas, {
|
|
text: 'https://qrmaster.com',
|
|
ec: p.logoShape && p.logoShape !== 'none' ? 'H' : 'M',
|
|
module: 5,
|
|
quiet: 2,
|
|
bodyShape: p.bodyShape,
|
|
eyeFrame: p.eyeFrame,
|
|
eyeBall: p.eyeBall,
|
|
colorMode: p.colorMode,
|
|
color1: p.color1,
|
|
color2: p.color2,
|
|
gradAngle: p.gradAngle || 45,
|
|
eyeColorMode: !!p.eyeColorMode,
|
|
eyeColor1: p.eyeColor1 || p.color1,
|
|
eyeColor2: p.eyeColor2 || p.color2,
|
|
bgColor: p.bgColor,
|
|
rotation: p.rotation || 0,
|
|
frameStyle: p.frameStyle || 'none',
|
|
labelText: p.labelText || 'SCAN MICH',
|
|
labelColor: p.labelColor || '#111111',
|
|
logoShape: p.logoShape || 'none',
|
|
logoColor: p.logoColor || '#111111',
|
|
logoBg: p.logoBg || '#ffffff'
|
|
});
|
|
});
|
|
}
|
|
|
|
refreshLive();
|
|
buildGallery();
|
|
</script>
|
|
</body>
|
|
</html>
|