<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generator Stempel V7 | VisualTeknik</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@700&family=Rajdhani:wght@500;600;700&display=swap" rel="stylesheet">
<style>
/* CSS DIBATASI HANYA UNTUK WRAPPER MESIN AGAR TIDAK BOCOR KE THEME WORDPRESS */
:root {
  --green: #0F6E56; --green-d: #085041; --green-p: #E1F5EE; --green-b: #9FE1CB;
  --bg: #f4f6f4; --surface: #ffffff; --border: #e0e4e0;
  --text: #1a1a1a; --text2: #555; --text3: #888;
  --radius: 12px; --radius-s: 8px;
}

.vst-stamp-wrapper {
  font-family: 'Rajdhani', sans-serif;
  background: var(--bg);
  color: var(--text);
  padding: 1.5rem 1rem;
  border-radius: var(--radius);
  box-sizing: border-box;
}

.vst-stamp-wrapper * {
  box-sizing: border-box;
}

.vst-app-title {
  font-family: 'Cinzel', serif;
  font-size: 24px;
  color: var(--green-d);
  text-align: center;
  margin-bottom: 4px;
  font-weight: 700;
}

.vst-stamp-wrapper .sub { text-align: center; font-size: 14px; color: var(--text3); margin-bottom: 1.5rem; margin-top: 0; }
.vst-stamp-wrapper .layout { display: grid; grid-template-columns: 300px 1fr; gap: 18px; max-width: 900px; margin: 0 auto; }

@media(max-width: 680px) { .vst-stamp-wrapper .layout { grid-template-columns: 1fr; } }

.vst-stamp-wrapper .panel { background: var(--surface); border: 0.5px solid var(--border); border-radius: var(--radius); padding: 1.1rem; }
.vst-stamp-wrapper .pt { font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--text3); margin-bottom: .8rem; display: block; }
.vst-stamp-wrapper .field { margin-bottom: 10px; }
.vst-stamp-wrapper .field label { display: block; font-size: 12px; font-weight: 600; color: var(--text2); margin-bottom: 3px; }
.vst-stamp-wrapper .field input[type=text], .vst-stamp-wrapper .field select { width: 100%; padding: 7px 9px; border: 0.5px solid var(--border); border-radius: var(--radius-s); font-family: 'Rajdhani', sans-serif; font-size: 14px; color: var(--text); outline: none; transition: .15s; margin: 0; }
.vst-stamp-wrapper .field input[type=text]:focus, .vst-stamp-wrapper .field select:focus { border-color: var(--green); }
.vst-stamp-wrapper .field input[type=color] { width: 100%; height: 34px; padding: 2px 3px; border: 0.5px solid var(--border); border-radius: var(--radius-s); cursor: pointer; }
.vst-stamp-wrapper .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.vst-stamp-wrapper .range-row { display: flex; align-items: center; gap: 8px; }
.vst-stamp-wrapper .range-row input[type=range] { flex: 1; accent-color: var(--green); }
.vst-stamp-wrapper .rv { font-size: 12px; color: var(--text2); min-width: 36px; text-align: right; font-weight: 600; }
.vst-stamp-wrapper hr.sep { border: none; border-top: 0.5px solid var(--border); margin: 15px 0; }

.vst-stamp-wrapper .upload-zone { border: 1.5px dashed var(--border); border-radius: var(--radius-s); padding: 14px 10px; text-align: center; cursor: pointer; transition: .15s; position: relative; }
.vst-stamp-wrapper .upload-zone:hover { border-color: var(--green); background: var(--green-p); }
.vst-stamp-wrapper .upload-zone input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.vst-stamp-wrapper .upload-zone p { font-size: 12px; color: var(--text3); pointer-events: none; margin: 0; line-height: 1.4; }
.vst-stamp-wrapper .upload-zone .uz-icon { font-size: 20px; margin-bottom: 4px; }
.vst-stamp-wrapper .img-ctrl { display: none; margin-top: 10px; }
.vst-stamp-wrapper .img-ctrl.show { display: block; }
.vst-stamp-wrapper .pos-box { background: #f0f4f0; border-radius: var(--radius-s); padding: 8px 10px; margin-bottom: 8px; }

.vst-stamp-wrapper .preview-col { display: flex; flex-direction: column; gap: 12px; }
.vst-stamp-wrapper .checker { background: repeating-conic-gradient(#ccc 0% 25%, #fff 0% 50%) 0 0/20px 20px; border: 0.5px solid var(--border); border-radius: var(--radius); display: flex; align-items: center; justify-content: center; padding: 1.5rem; min-height: 320px; }
.vst-stamp-wrapper canvas { max-width: 100%; height: auto; display: block; }

.vst-stamp-wrapper .dl-btn { width: 100%; padding: 12px; background: var(--green); color: #fff; border: none; border-radius: var(--radius-s); font-family: 'Rajdhani', sans-serif; font-size: 15px; font-weight: 700; cursor: pointer; letter-spacing: .04em; transition: .15s; }
.vst-stamp-wrapper .dl-btn:hover { background: var(--green-d); }
.vst-stamp-wrapper .tip { font-size: 12px; color: var(--text3); text-align: center; line-height: 1.5; margin: 0; }
.vst-stamp-wrapper .guide { background: var(--green-p); border: 0.5px solid var(--green-b); border-radius: var(--radius-s); padding: 9px 12px; }
.vst-stamp-wrapper .guide p { font-size: 12px; color: var(--green-d); line-height: 1.6; margin: 0; }
</style>
</head>
<body>

<div class="vst-stamp-wrapper">
  
  <div class="vst-app-title">Generator Stempel Perusahaan</div>
  <p class="sub">Buat cap digital PNG transparan — gratis, langsung di browser</p>

  <div class="layout">
    <div class="panel">
      <span class="pt">Teks Stempel</span>
      <div class="field"><label>Nama Perusahaan (atas)</label><input type="text" id="textTop" value="PT. VISUAL TEKNIK" maxlength="40"></div>
      <div class="field"><label>Nama Divisi (bawah)</label><input type="text" id="textBottom" value="DOCUMENT CONTROL" maxlength="40"></div>
      <div class="field"><label>Teks / Kata Tengah</label><input type="text" id="textCenter" value="APPROVED" maxlength="20"></div>
      <div class="field"><label>Simbol pemisah</label><input type="text" id="symbolText" value="★" maxlength="5"></div>

      <hr class="sep">
      <span class="pt">Tampilan</span>
      <div class="row2">
        <div class="field"><label>Warna garis</label><input type="color" id="circleColor" value="#0022aa"></div>
        <div class="field"><label>Warna teks</label><input type="color" id="stampColor" value="#0022aa"></div>
      </div>
      <div class="field">
        <label>Font</label>
        <select id="fontSel">
          <option value="Arial">Arial (standar)</option>
          <option value="Cinzel">Cinzel (klasik)</option>
          <option value="Rajdhani">Rajdhani (modern)</option>
          <option value="Georgia">Georgia (formal)</option>
          <option value="Courier New">Courier New (mesin ketik)</option>
        </select>
      </div>
      <div class="field">
        <label>Ketebalan garis luar</label>
        <select id="lineW">
          <option value="3">Tipis (3px)</option>
          <option value="5" selected>Normal (5px)</option>
          <option value="8">Tebal (8px)</option>
        </select>
      </div>
      <div class="field">
        <label>Opasitas stempel <span class="rv" id="opVal">90%</span></label>
        <div class="range-row"><input type="range" id="opacity" min="20" max="100" value="90" step="5"></div>
      </div>

      <hr class="sep">
      <span class="pt">Upload Gambar / Logo (opsional)</span>
      <div class="upload-zone" id="dropZone">
        <div class="uz-icon">🖼️</div>
        <p>Klik atau seret file gambar ke sini<br><small>PNG / JPG / SVG — background putih otomatis transparan</small></p>
        <input type="file" id="imgUpload" accept="image/*">
      </div>

      <div class="img-ctrl" id="imgCtrl">
        <div class="field" style="margin-top:8px">
          <label>Ukuran gambar <span class="rv" id="imgSzVal">60%</span></label>
          <div class="range-row"><input type="range" id="imgSize" min="10" max="95" value="60" step="1"></div>
        </div>

        <div class="pos-box">
          <div class="field">
            <label>Posisi kiri ↔ kanan <span class="rv" id="imgPosXVal">50%</span></label>
            <div class="range-row"><input type="range" id="imgPosX" min="0" max="100" value="50" step="1"></div>
          </div>
          <div class="field" style="margin-bottom:0">
            <label>Posisi atas ↕ bawah <span class="rv" id="imgPosVal">50%</span></label>
            <div class="range-row"><input type="range" id="imgPos" min="0" max="100" value="50" step="1"></div>
          </div>
        </div>

        <div class="field">
          <label>Toleransi hapus BG <span class="rv" id="tolVal">30</span></label>
          <div class="range-row"><input type="range" id="bgTol" min="0" max="120" value="30" step="5"></div>
        </div>
        <button onclick="clearImage()" style="width:100%;padding:6px;border:0.5px solid var(--border);border-radius:var(--radius-s);background:transparent;font-family:'Rajdhani',sans-serif;font-size:13px;cursor:pointer;color:var(--text2);margin-top:2px">✕ Hapus gambar</button>
      </div>

      <hr class="sep">
      <div class="field">
        <label>Ukuran output (px)</label>
        <select id="canvasSize">
          <option value="300">300×300</option>
          <option value="500" selected>500×500 ✓</option>
          <option value="800">800×800 (HD)</option>
        </select>
      </div>
    </div>

    <div class="preview-col">
      <div class="panel" style="padding:.65rem 1rem"><span class="pt" style="margin:0">Preview — kotak = area transparan</span></div>
      <div class="checker"><canvas id="stampCanvas"></canvas></div>
      <button class="dl-btn" onclick="downloadPNG()">⬇ Download PNG Transparan</button>
      <p class="tip">File siap ditempel di Word, Excel, PowerPoint, atau PDF.<br>Background transparan — tidak ada kotak putih.</p>
      <div class="guide"><p><strong>Cara pakai di Word:</strong> Insert → Pictures → pilih file → klik kanan → Wrap Text: In Front of Text → atur posisi &amp; ukuran.</p></div>
    </div>
  </div>

</div>
<script>
let uploadedImage = null;

function removeBgWhite(img, tolerance) {
  const off = document.createElement('canvas');
  off.width = img.naturalWidth || img.width;
  off.height = img.naturalHeight || img.height;
  const c = off.getContext('2d');
  c.drawImage(img, 0, 0);
  const d = c.getImageData(0, 0, off.width, off.height);
  const px = d.data;
  for (let i = 0; i < px.length; i += 4) {
    const r = px[i], g = px[i+1], b = px[i+2];
    if (r >= 255 - tolerance && g >= 255 - tolerance && b >= 255 - tolerance) {
      px[i+3] = 0;
    } else {
      const whiteness = Math.min(r, g, b);
      const threshold = 255 - tolerance;
      if (whiteness > threshold * 0.7) {
        px[i+3] = Math.round(px[i+3] * (1 - (whiteness - threshold * 0.7) / (255 - threshold * 0.7)));
      }
    }
  }
  c.putImageData(d, 0, 0);
  return off;
}

document.getElementById('imgUpload').addEventListener('change', function() {
  const file = this.files[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onload = e => {
    const img = new Image();
    img.onload = () => {
      uploadedImage = img;
      document.getElementById('imgCtrl').classList.add('show');
      draw();
    };
    img.src = e.target.result;
  };
  reader.readAsDataURL(file);
});

function clearImage() {
  uploadedImage = null;
  document.getElementById('imgUpload').value = '';
  document.getElementById('imgCtrl').classList.remove('show');
  draw();
}

function draw() {
  const canvas = document.getElementById('stampCanvas');
  const ctx    = canvas.getContext('2d');
  const SIZE   = parseInt(document.getElementById('canvasSize').value);
  canvas.width  = SIZE;
  canvas.height = SIZE;
  ctx.clearRect(0, 0, SIZE, SIZE);

  const cx = SIZE / 2, cy = SIZE / 2;
  const scale = SIZE / 300;

  const circleColor = document.getElementById('circleColor').value;
  const stampColor  = document.getElementById('stampColor').value;
  const lineW       = parseInt(document.getElementById('lineW').value) * scale;
  const alpha       = parseInt(document.getElementById('opacity').value) / 100;
  const fontName    = document.getElementById('fontSel').value;
  const textTop     = document.getElementById('textTop').value.trim().toUpperCase();
  const textBottom  = document.getElementById('textBottom').value.trim().toUpperCase();
  const textCenter  = document.getElementById('textCenter').value.trim().toUpperCase();
  const symbol      = document.getElementById('symbolText').value.trim();

  ctx.globalAlpha = alpha;

  const outerR = 128 * scale;
  const innerR = 95  * scale;
  const arcR   = (outerR + innerR) / 2;

  ctx.strokeStyle = circleColor;
  ctx.lineWidth   = lineW;
  ctx.beginPath(); ctx.arc(cx, cy, outerR, 0, Math.PI * 2); ctx.stroke();
  ctx.lineWidth   = lineW * 0.5;
  ctx.beginPath(); ctx.arc(cx, cy, innerR, 0, Math.PI * 2); ctx.stroke();

  const fontSize = Math.round(15 * scale);
  if (textTop)    drawArcText(ctx, textTop,    symbol, cx, cy, arcR, true,  fontName, fontSize, stampColor);
  if (textBottom) drawArcText(ctx, textBottom, symbol, cx, cy, arcR, false, fontName, fontSize, stampColor);

  const sepHalfW = innerR - 10 * scale;
  ctx.strokeStyle = stampColor;
  ctx.lineWidth   = scale;
  ctx.beginPath(); ctx.moveTo(cx - sepHalfW, cy - 20 * scale); ctx.lineTo(cx + sepHalfW, cy - 20 * scale); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(cx - sepHalfW, cy + 20 * scale); ctx.lineTo(cx + sepHalfW, cy + 20 * scale); ctx.stroke();

  if (textCenter) {
    ctx.font = 'bold ' + Math.round(22 * scale) + 'px \'' + fontName + '\', Arial, serif';
    ctx.textAlign    = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillStyle    = stampColor;
    ctx.fillText(textCenter, cx, cy);
  }

  if (uploadedImage) {
    const tol    = parseInt(document.getElementById('bgTol').value);
    const pct    = parseInt(document.getElementById('imgSize').value) / 100;
    const posX   = parseInt(document.getElementById('imgPosX').value) / 100; 
    const posY   = parseInt(document.getElementById('imgPos').value)  / 100; 

    const iw     = uploadedImage.naturalWidth  || uploadedImage.width;
    const ih     = uploadedImage.naturalHeight || uploadedImage.height;
    const zone   = innerR * 1.8 * pct;
    const ratio  = Math.min(zone / iw, zone / ih);
    const dw     = iw * ratio;
    const dh     = ih * ratio;

    const cleaned = removeBgWhite(uploadedImage, tol);

    const zoneW  = innerR * 2 - dw;
    const zoneH  = innerR * 2 - dh;
    const drawX  = (cx - innerR) + zoneW * posX;
    const drawY  = (cy - innerR) + zoneH * posY;

    ctx.save();
    ctx.beginPath();
    ctx.arc(cx, cy, innerR - lineW, 0, Math.PI * 2);
    ctx.clip();
    ctx.drawImage(cleaned, drawX, drawY, dw, dh);
    ctx.restore();
  }
}

function drawArcText(ctx, text, symbol, cx, cy, radius, isTop, fontName, fontSize, color) {
  ctx.fillStyle    = color;
  ctx.textAlign    = 'center';
  ctx.textBaseline = 'middle';
  ctx.font = 'bold ' + fontSize + 'px \'' + fontName + '\', Arial, serif';

  const measure    = s => ctx.measureText(s).width;
  const charWidths = text.split('').map(c => measure(c));
  const symWidth   = symbol ? measure(symbol) : 0;
  const gap        = measure(' ') * 0.8;
  const availArc   = Math.PI * radius * 0.85;
  const totalTextLen = charWidths.reduce((a, b) => a + b, 0);
  const totalWithSym = totalTextLen + (symbol ? (symWidth + gap) * 2 : 0);
  const scaleFactor  = totalWithSym > availArc ? availArc / totalWithSym : 1;
  const toAngle      = px => (px * scaleFactor) / radius;
  const totalAngle   = toAngle(totalWithSym);

  const drawChar = (char, angle) => {
    ctx.save();
    ctx.translate(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
    ctx.rotate(isTop ? angle + Math.PI / 2 : angle - Math.PI / 2);
    ctx.fillText(char, 0, 0);
    ctx.restore();
  };

  if (isTop) {
    let a = -Math.PI / 2 - totalAngle / 2;
    if (symbol) { drawChar(symbol, a + toAngle(symWidth / 2)); a += toAngle(symWidth + gap); }
    text.split('').forEach((c, i) => { drawChar(c, a + toAngle(charWidths[i] / 2)); a += toAngle(charWidths[i]); });
    if (symbol) { a += toAngle(gap); drawChar(symbol, a + toAngle(symWidth / 2)); }
  } else {
    let b = Math.PI / 2 + totalAngle / 2;
    if (symbol) { drawChar(symbol, b - toAngle(symWidth / 2)); b -= toAngle(symWidth + gap); }
    text.split('').forEach((c, i) => { drawChar(c, b - toAngle(charWidths[i] / 2)); b -= toAngle(charWidths[i]); });
    if (symbol) { b -= toAngle(gap); drawChar(symbol, b - toAngle(symWidth / 2)); }
  }
}

function downloadPNG() {
  draw();
  const canvas = document.getElementById('stampCanvas');
  const name   = (document.getElementById('textTop').value || 'stempel').replace(/[^a-zA-Z0-9]/g, '_');
  const a = document.createElement('a');
  a.download = 'stempel_' + name + '.png';
  a.href = canvas.toDataURL('image/png');
  a.click();
}

const ids = ['textTop','textBottom','textCenter','symbolText','stampColor','circleColor',
             'lineW','opacity','canvasSize','imgSize','imgPos','imgPosX','bgTol','fontSel'];
ids.forEach(id => {
  const el = document.getElementById(id);
  if (!el) return;
  el.addEventListener('input', () => {
    if (id === 'opacity')  document.getElementById('opVal').textContent      = el.value + '%';
    if (id === 'imgSize')  document.getElementById('imgSzVal').textContent   = el.value + '%';
    if (id === 'imgPos')   document.getElementById('imgPosVal').textContent  = el.value + '%';
    if (id === 'imgPosX')  document.getElementById('imgPosXVal').textContent = el.value + '%';
    if (id === 'bgTol')    document.getElementById('tolVal').textContent     = el.value;
    draw();
  });
});

window.addEventListener('load', () => setTimeout(draw, 300));
</script>
</body>
</html>