Ham HTML
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- ================= SEO META ================= -->
<title>Görsel Boyutu Küçültücü – Ücretsiz Online Resim Sıkıştırma (JPG, PNG, WebP)</title>
<meta name="description" content="Fotoğraflarınızı kalite kaybı hissettirmeden saniyeler içinde küçültün. JPG, PNG ve WebP resimleri tarayıcınızda sıkıştırın; dosyalarınız hiçbir sunucuya yüklenmez. Ücretsiz ve üyeliksiz." />
<meta name="keywords" content="görsel boyutu küçültme, resim sıkıştırma, fotoğraf küçültme, jpg küçültme, png sıkıştırma, webp dönüştürme, online resim boyutu azaltma, kb düşürme" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://www.SITENIZ.com/gorsel-boyutu-kucultucu" /> <!-- TODO: kendi alan adınızla değiştirin -->
<!-- Open Graph / Sosyal paylaşım -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Görsel Boyutu Küçültücü – Ücretsiz Online Resim Sıkıştırma" />
<meta property="og:description" content="JPG, PNG ve WebP görsellerini tarayıcınızda, gizlilikten ödün vermeden küçültün." />
<meta property="og:locale" content="tr_TR" />
<meta name="theme-color" content="#6366f1" />
<!-- ================= GOOGLE ADSENSE =================
Onay aldıktan sonra aşağıdaki satırın yorumunu kaldırın ve ca-pub kimliğinizi yazın.
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX" crossorigin="anonymous"></script>
-->
<!-- ================= YAPILANDIRILMIŞ VERİ (Schema.org) ================= -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Görsel Boyutu Küçültücü",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Tüm tarayıcılar",
"inLanguage": "tr",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "TRY" },
"description": "JPG, PNG ve WebP görsellerini tarayıcıda sıkıştıran ve yeniden boyutlandıran ücretsiz online araç."
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{ "@type": "Question", "name": "Görsel boyutu küçültücü ücretli mi?",
"acceptedAnswer": { "@type": "Answer", "text": "Hayır. Araç tamamen ücretsizdir; üyelik, kayıt veya dosya sayısı için ödeme istenmez." } },
{ "@type": "Question", "name": "Yüklediğim fotoğraflar güvende mi?",
"acceptedAnswer": { "@type": "Answer", "text": "Evet. Tüm sıkıştırma işlemi tarayıcınızda yapılır; görselleriniz hiçbir sunucuya gönderilmez ve sayfayı kapattığınızda bellekten silinir." } },
{ "@type": "Question", "name": "Resim kalitesi bozulur mu?",
"acceptedAnswer": { "@type": "Answer", "text": "%70-80 kalite aralığında yapılan sıkıştırmada fark gözle neredeyse görülmez, dosya boyutu ise genellikle %50-80 oranında azalır." } },
{ "@type": "Question", "name": "Bir fotoğrafı belirli bir KB değerinin altına düşürebilir miyim?",
"acceptedAnswer": { "@type": "Answer", "text": "Evet. Hedef boyut alanına örneğin 200 KB yazdığınızda araç kaliteyi ve gerekirse çözünürlüğü otomatik ayarlayarak bu sınırın altına inmeye çalışır." } },
{ "@type": "Question", "name": "Hangi dosya formatları destekleniyor?",
"acceptedAnswer": { "@type": "Answer", "text": "JPG/JPEG, PNG, WebP, GIF (ilk kare) ve BMP dosyaları desteklenir. Çıktı olarak JPG, PNG veya WebP seçebilirsiniz." } }
]
}
</script>
<style>
/* =========================================================
1) TASARIM DEĞİŞKENLERİ (Açık / Koyu tema)
========================================================= */
:root {
--bg: #f5f6fb;
--surface: #ffffff;
--surface-2: #f0f2f9;
--border: #e3e6f0;
--text: #1d2033;
--muted: #6b7089;
--primary: #6366f1;
--primary-600: #4f46e5;
--primary-soft: #eef0ff;
--success: #10b981;
--success-soft: #e7f8f1;
--danger: #ef4444;
--warning: #f59e0b;
--radius: 16px;
--shadow: 0 10px 30px -12px rgba(30, 35, 90, .18);
}
:root[data-theme="dark"] {
--bg: #0e1020;
--surface: #171a2e;
--surface-2: #1f2340;
--border: #2a2f52;
--text: #e8eaf6;
--muted: #9aa0c3;
--primary: #818cf8;
--primary-600: #6366f1;
--primary-soft: #23284d;
--success: #34d399;
--success-soft: #133329;
--shadow: 0 10px 30px -12px rgba(0, 0, 0, .6);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
transition: background .25s, color .25s;
}
a { color: var(--primary); }
.container { width: 100%; max-width: 980px; margin: 0 auto; padding: 0 16px; }
/* =========================================================
2) ÜST MENÜ
========================================================= */
.site-header {
position: sticky; top: 0; z-index: 40;
background: color-mix(in srgb, var(--surface) 85%, transparent);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
}
.site-header .container { display: flex; align-items: center; justify-content: space-between; height: 60px; }
.logo { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 1.1rem; color: var(--text); text-decoration: none; }
.logo-mark {
width: 32px; height: 32px; border-radius: 9px;
background: linear-gradient(135deg, var(--primary), #22d3ee);
display: grid; place-items: center; color: #fff; font-size: 16px;
}
.icon-btn {
border: 1px solid var(--border); background: var(--surface); color: var(--text);
width: 40px; height: 40px; border-radius: 10px; cursor: pointer; font-size: 18px;
display: grid; place-items: center; transition: transform .15s;
}
.icon-btn:hover { transform: translateY(-1px); }
/* =========================================================
3) BAŞLIK
========================================================= */
.hero { text-align: center; padding: 36px 0 18px; }
.hero h1 { font-size: clamp(1.6rem, 4.5vw, 2.4rem); line-height: 1.2; letter-spacing: -.02em; }
.hero h1 span {
background: linear-gradient(90deg, var(--primary), #22d3ee);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p { color: var(--muted); margin-top: 10px; font-size: 1.02rem; }
.badges { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 14px; }
.badge {
font-size: .8rem; padding: 4px 10px; border-radius: 999px;
background: var(--primary-soft); color: var(--primary); font-weight: 600;
}
/* =========================================================
4) REKLAM ALANLARI (AdSense yer tutucuları)
========================================================= */
.ad-slot {
display: flex; align-items: center; justify-content: center; flex-direction: column;
border: 1px dashed var(--border); border-radius: 12px;
background: repeating-linear-gradient(45deg, transparent 0 10px, var(--surface-2) 10px 20px);
color: var(--muted); font-size: .78rem; text-align: center; overflow: hidden;
margin: 18px auto;
}
.ad-slot small { opacity: .7; }
.ad-header { width: 100%; max-width: 728px; min-height: 90px; }
.ad-process { width: 100%; max-width: 336px; min-height: 280px; margin-top: 22px; }
.ad-footer { width: 100%; max-width: 336px; min-height: 280px; }
@media (max-width: 600px) { .ad-header { min-height: 100px; } }
/* =========================================================
5) ANA ARAÇ KARTI
========================================================= */
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); box-shadow: var(--shadow); padding: 22px;
}
.dropzone {
border: 2px dashed var(--border); border-radius: 14px; padding: 38px 16px;
text-align: center; cursor: pointer; transition: border-color .2s, background .2s;
background: var(--surface-2); outline: none;
}
.dropzone:hover, .dropzone:focus-visible, .dropzone.drag {
border-color: var(--primary); background: var(--primary-soft);
}
.dz-icon { font-size: 44px; line-height: 1; }
.dropzone h2 { font-size: 1.15rem; margin: 10px 0 4px; }
.dropzone p { color: var(--muted); font-size: .9rem; }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
border: none; border-radius: 10px; padding: 11px 18px; font-weight: 700; font-size: .95rem;
cursor: pointer; transition: transform .12s, background .2s, opacity .2s; font-family: inherit;
}
.btn:active { transform: scale(.98); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--primary-600); }
.btn-success { background: var(--success); color: #fff; }
.btn-ghost { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
.btn-sm { padding: 7px 12px; font-size: .82rem; border-radius: 8px; }
.dropzone .btn { margin-top: 14px; }
/* Ayarlar */
.settings { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-top: 20px; }
.field label { display: block; font-size: .85rem; font-weight: 700; margin-bottom: 6px; }
.field .hint { font-size: .75rem; color: var(--muted); margin-top: 4px; }
.field select, .field input[type="number"] {
width: 100%; padding: 10px 12px; border-radius: 10px; font-size: .95rem; font-family: inherit;
border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
}
.field select:focus, .field input:focus { outline: 2px solid var(--primary); outline-offset: 1px; }
.range-row { display: flex; align-items: center; gap: 10px; }
.range-row input[type="range"] { flex: 1; accent-color: var(--primary); }
.range-val { min-width: 48px; text-align: right; font-weight: 800; color: var(--primary); }
.dims { display: flex; gap: 8px; }
.presets { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.chip {
border: 1px solid var(--border); background: var(--surface); color: var(--text);
padding: 6px 12px; border-radius: 999px; font-size: .8rem; cursor: pointer; font-family: inherit;
}
.chip:hover { border-color: var(--primary); color: var(--primary); }
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; }
.actions .btn-primary { flex: 1 1 220px; }
/* Özet */
.summary {
display: none; margin-top: 18px; padding: 14px 16px; border-radius: 12px;
background: var(--success-soft); color: var(--text); font-size: .92rem;
}
.summary.show { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center; }
.summary strong { color: var(--success); }
/* Dosya listesi */
.file-list { list-style: none; margin-top: 18px; display: grid; gap: 10px; }
.file-item {
display: grid; grid-template-columns: 56px 1fr auto; gap: 12px; align-items: center;
padding: 10px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface-2);
animation: fadeIn .25s ease;
}
.thumb { width: 56px; height: 56px; border-radius: 8px; object-fit: cover; background: var(--border); }
.f-name { font-weight: 700; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.f-meta { font-size: .78rem; color: var(--muted); }
.f-meta b { color: var(--text); }
.saving { display: inline-block; font-weight: 800; color: var(--success); }
.saving.neutral { color: var(--warning); }
.f-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.remove { background: transparent; border: none; color: var(--muted); font-size: 18px; cursor: pointer; padding: 4px 6px; }
.remove:hover { color: var(--danger); }
@media (max-width: 560px) {
.file-item { grid-template-columns: 48px 1fr; }
.thumb { width: 48px; height: 48px; }
.f-actions { grid-column: 1 / -1; justify-content: stretch; }
.f-actions .btn { flex: 1; }
}
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
/* =========================================================
6) İŞLEM BEKLEME EKRANI (Overlay + Reklam Alanı 2)
========================================================= */
.overlay {
position: fixed; inset: 0; z-index: 100; display: none;
align-items: center; justify-content: center; flex-direction: column;
background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(6px);
padding: 16px; overflow-y: auto;
}
.overlay.show { display: flex; animation: fadeIn .2s ease; }
.loader-box {
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: var(--shadow); padding: 28px 24px; text-align: center; width: 100%; max-width: 360px;
}
.spinner {
width: 64px; height: 64px; margin: 0 auto 16px; border-radius: 50%;
border: 6px solid var(--primary-soft); border-top-color: var(--primary);
animation: spin .9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loader-box h3 { font-size: 1.1rem; }
.loader-box p { color: var(--muted); font-size: .88rem; margin-top: 4px; }
.progress { height: 8px; background: var(--surface-2); border-radius: 99px; overflow: hidden; margin-top: 16px; }
.progress > div { height: 100%; width: 0; background: linear-gradient(90deg, var(--primary), #22d3ee); transition: width .3s; }
/* =========================================================
7) KARŞILAŞTIRMA PENCERESİ (Önce / Sonra)
========================================================= */
.modal {
position: fixed; inset: 0; z-index: 90; display: none; align-items: center; justify-content: center;
background: rgba(8, 10, 25, .7); padding: 16px;
}
.modal.show { display: flex; }
.modal-box {
background: var(--surface); border-radius: var(--radius); width: 100%; max-width: 820px;
max-height: 92vh; overflow: auto; padding: 16px;
}
.modal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 10px; }
.modal-head h3 { font-size: 1rem; }
.compare { position: relative; width: 100%; border-radius: 10px; overflow: hidden; background: var(--surface-2); user-select: none; }
.compare img { display: block; width: 100%; height: auto; }
.compare .after { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.compare .divider { position: absolute; top: 0; bottom: 0; width: 3px; background: #fff; box-shadow: 0 0 6px rgba(0,0,0,.5); pointer-events: none; }
.compare .tag { position: absolute; top: 10px; padding: 3px 8px; border-radius: 6px; background: rgba(0,0,0,.6); color: #fff; font-size: .75rem; font-weight: 700; }
.compare-range { width: 100%; margin-top: 12px; accent-color: var(--primary); }
/* =========================================================
8) TOAST BİLDİRİMLERİ
========================================================= */
.toasts { position: fixed; right: 16px; bottom: 16px; z-index: 200; display: grid; gap: 10px; max-width: calc(100% - 32px); width: 340px; }
.toast {
display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px; border-radius: 12px;
background: var(--surface); color: var(--text); border: 1px solid var(--border); border-left: 5px solid var(--primary);
box-shadow: var(--shadow); font-size: .88rem; animation: slideIn .25s ease;
}
.toast.error { border-left-color: var(--danger); }
.toast.success { border-left-color: var(--success); }
.toast.warn { border-left-color: var(--warning); }
.toast.hide { opacity: 0; transform: translateX(20px); transition: all .3s; }
@keyframes slideIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: none; } }
/* =========================================================
9) SEO İÇERİK BÖLÜMÜ
========================================================= */
.content { margin-top: 28px; display: grid; gap: 18px; }
.content h2 { font-size: 1.3rem; margin-bottom: 10px; }
.content p { color: var(--muted); margin-bottom: 10px; }
.content ol { padding-left: 22px; color: var(--muted); }
.content ol li { margin-bottom: 8px; }
.content ol li strong { color: var(--text); }
.faq details { border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; margin-bottom: 10px; background: var(--surface-2); }
.faq summary { cursor: pointer; font-weight: 700; list-style: none; display: flex; justify-content: space-between; gap: 10px; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; font-size: 1.2rem; color: var(--primary); transition: transform .2s; }
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details p { margin: 10px 0 0; }
.site-footer { margin-top: 30px; padding: 24px 0; border-top: 1px solid var(--border); color: var(--muted); font-size: .85rem; }
.site-footer .container { display: flex; flex-wrap: wrap; gap: 10px 20px; justify-content: space-between; }
.site-footer nav { display: flex; flex-wrap: wrap; gap: 14px; }
.site-footer a { color: var(--muted); text-decoration: none; }
.site-footer a:hover { color: var(--primary); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
</style>
</head>
<body>
<!-- ================= ÜST MENÜ ================= -->
<header class="site-header">
<div class="container">
<a href="/" class="logo" aria-label="Ana sayfa"><span class="logo-mark">⚡</span> MikroAraçlar</a>
<button class="icon-btn" id="themeToggle" aria-label="Temayı değiştir" title="Açık / Koyu tema">🌙</button>
</div>
</header>
<main class="container">
<!-- ================= BAŞLIK ================= -->
<section class="hero">
<h1>Görsel Boyutu <span>Küçültücü</span></h1>
<p>JPG, PNG ve WebP resimlerinizi saniyeler içinde sıkıştırın. Dosyalarınız cihazınızdan asla çıkmaz.</p>
<div class="badges">
<span class="badge">🔒 %100 Tarayıcıda</span>
<span class="badge">🆓 Ücretsiz</span>
<span class="badge">📦 Toplu İşlem</span>
<span class="badge">🎯 Hedef KB</span>
</div>
</section>
<!-- ================= [REKLAM ALANI 1] – Header Banner ================= -->
<!-- [reklam:arac-header] -->
<!-- ================= ANA ARAÇ ================= -->
<section class="card" aria-labelledby="toolTitle">
<h2 id="toolTitle" class="sr-only">Görsel sıkıştırma aracı</h2>
<!-- Dosya yükleme alanı -->
<div class="dropzone" id="dropzone" tabindex="0" role="button" aria-label="Görsel seçin veya sürükleyip bırakın">
<div class="dz-icon">🖼️</div>
<h2>Görselleri buraya sürükleyin</h2>
<p>veya tıklayıp seçin · Ctrl+V ile yapıştırabilirsiniz</p>
<p style="font-size:.78rem;margin-top:4px">JPG, PNG, WebP, GIF, BMP · Dosya başına en fazla 25 MB · Tek seferde 20 dosya</p>
<span class="btn btn-primary">📂 Görsel Seç</span>
<input type="file" id="fileInput" accept="image/jpeg,image/png,image/webp,image/gif,image/bmp" multiple hidden />
</div>
<!-- Hazır ayarlar -->
<div class="presets" aria-label="Hazır ayarlar">
<button class="chip" data-preset="web">🌐 Web sitesi (1920px · %75)</button>
<button class="chip" data-preset="mail">✉️ E-posta (1280px · %70)</button>
<button class="chip" data-preset="social">📱 Sosyal medya (1080px · %80)</button>
<button class="chip" data-preset="form">📄 Başvuru formu (≤ 200 KB)</button>
<button class="chip" data-preset="max">🗜️ Maksimum sıkıştırma</button>
</div>
<!-- Ayarlar -->
<div class="settings">
<div class="field">
<label for="quality">Kalite</label>
<div class="range-row">
<input type="range" id="quality" min="10" max="100" value="75" />
<span class="range-val" id="qualityVal">%75</span>
</div>
<div class="hint" id="qualityHint">Önerilen: %70 – %80</div>
</div>
<div class="field">
<label for="format">Çıktı Formatı</label>
<select id="format">
<option value="original">Orijinal formatı koru</option>
<option value="image/jpeg">JPG (en uyumlu)</option>
<option value="image/webp">WebP (en küçük boyut)</option>
<option value="image/png">PNG (kayıpsız)</option>
</select>
<div class="hint">PNG'de sadece çözünürlük küçültme etkilidir.</div>
</div>
<div class="field">
<label>Maksimum Boyut (px)</label>
<div class="dims">
<input type="number" id="maxW" placeholder="Genişlik" min="16" max="10000" aria-label="Maksimum genişlik" />
<input type="number" id="maxH" placeholder="Yükseklik" min="16" max="10000" aria-label="Maksimum yükseklik" />
</div>
<div class="hint">Boş bırakırsanız çözünürlük korunur. Oran bozulmaz.</div>
</div>
<div class="field">
<label for="targetKB">Hedef Dosya Boyutu (KB)</label>
<input type="number" id="targetKB" placeholder="Örn: 200 (isteğe bağlı)" min="5" max="50000" />
<div class="hint">Doldurulursa kalite otomatik ayarlanır.</div>
</div>
</div>
<!-- Butonlar -->
<div class="actions">
<button class="btn btn-primary" id="compressBtn" disabled>⚡ Görselleri Küçült</button>
<button class="btn btn-success" id="downloadAllBtn" disabled>⬇️ Tümünü İndir</button>
<button class="btn btn-ghost" id="clearBtn" disabled>🗑️ Temizle</button>
</div>
<!-- Toplam tasarruf özeti -->
<div class="summary" id="summary" aria-live="polite"></div>
<!-- Dosya listesi -->
<ul class="file-list" id="fileList"></ul>
</section>
<!-- ================= [REKLAM ALANI 3] – Araç Sonu (Kare) ================= -->
<!-- [reklam:arac-alt] -->
<!-- ================= SEO İÇERİK ================= -->
<article class="content">
<section class="card">
<h2>Görsel Boyutu Küçültücü Nedir ve Ne İşe Yarar?</h2>
<p>Görsel Boyutu Küçültücü, fotoğraflarınızın ve resimlerinizin dosya boyutunu (KB/MB) gözle fark edilir bir kalite kaybı yaşatmadan azaltan ücretsiz bir online araçtır. Telefonla çekilen bir fotoğraf çoğu zaman 3–8 MB arasındadır; bu boyut e-posta eklerinde, resmi başvuru formlarında, e-ticaret ürün sayfalarında veya WhatsApp gibi uygulamalarda sorun çıkarır. Araç, JPG, PNG ve WebP görsellerini akıllı sıkıştırma ve yeniden boyutlandırma ile genellikle %50–%90 oranında küçültür.</p>
<p>Diğer birçok online sıkıştırma sitesinin aksine, bu araç dosyalarınızı hiçbir sunucuya yüklemez. Tüm işlem tarayıcınızın kendi işlem gücüyle, doğrudan cihazınızda yapılır. Bu sayede kimlik fotoğrafı, belge taraması veya kişisel fotoğraflar gibi hassas görselleriniz tamamen gizli kalır. Ayrıca sıkıştırma sırasında konum bilgisi gibi EXIF meta verileri de temizlenir; web siteniz daha hızlı açılır ve Google PageSpeed puanınız yükselir.</p>
</section>
<section class="card">
<h2>Görsel Boyutu Küçültücü Nasıl Kullanılır?</h2>
<ol>
<li><strong>Görsellerinizi ekleyin:</strong> "Görsel Seç" butonuna tıklayın, dosyaları kutuya sürükleyip bırakın ya da panodaki bir resmi Ctrl+V ile yapıştırın. Tek seferde 20 görsel ekleyebilirsiniz.</li>
<li><strong>Hazır ayar seçin veya ayarları düzenleyin:</strong> Web, e-posta, sosyal medya gibi hazır ayarlardan birini seçebilir ya da kalite, format ve maksimum genişlik/yükseklik değerlerini kendiniz belirleyebilirsiniz.</li>
<li><strong>Gerekirse hedef boyut girin:</strong> Bir başvuru formu "en fazla 200 KB" istiyorsa Hedef Dosya Boyutu alanına 200 yazmanız yeterlidir.</li>
<li><strong>"Görselleri Küçült" butonuna basın:</strong> Araç tüm görselleri birkaç saniye içinde işler ve her dosya için ne kadar tasarruf sağlandığını gösterir.</li>
<li><strong>Karşılaştırın ve indirin:</strong> "Karşılaştır" ile önce/sonra halini inceleyin; beğendiyseniz tek tek veya "Tümünü İndir" ile hepsini birden indirin.</li>
</ol>
</section>
<section class="card faq">
<h2>Sıkça Sorulan Sorular (SSS)</h2>
<details>
<summary>Görsel boyutu küçültücü ücretli mi?</summary>
<p>Hayır. Araç tamamen ücretsizdir. Üyelik, kayıt, e-posta adresi veya dosya sayısı için herhangi bir ödeme istenmez ve çıktılara filigran eklenmez.</p>
</details>
<details>
<summary>Yüklediğim fotoğraflar güvende mi? Verilerim bir sunucuya gönderiliyor mu?</summary>
<p>Verileriniz tamamen güvendedir. Resim sıkıştırma işlemi tarayıcınızda (istemci tarafında) gerçekleşir; görselleriniz internete yüklenmez, saklanmaz ve üçüncü kişilerle paylaşılmaz. Sayfayı kapattığınızda tüm veriler bellekten silinir. İnternet bağlantınız kesilse bile araç çalışmaya devam eder.</p>
</details>
<details>
<summary>Fotoğraf küçültünce kalite bozulur mu?</summary>
<p>%70–%80 kalite aralığında yapılan JPG veya WebP sıkıştırmasında fark gözle neredeyse görülmez, dosya boyutu ise büyük oranda azalır. "Karşılaştır" özelliği ile sonucu indirmeden önce orijinal görselle yan yana inceleyebilirsiniz.</p>
</details>
<details>
<summary>Bir resmi 100 KB veya 200 KB altına nasıl düşürürüm?</summary>
<p>Hedef Dosya Boyutu alanına istediğiniz değeri (örneğin 100) yazın ve "Görselleri Küçült" butonuna basın. Araç kaliteyi, gerekirse de çözünürlüğü otomatik olarak ayarlayarak görseli bu sınırın altına indirir. E-Devlet, üniversite ve iş başvurusu formları için idealdir.</p>
</details>
<details>
<summary>JPG mi, PNG mi, WebP mi seçmeliyim?</summary>
<p>Fotoğraflar için JPG en yaygın ve uyumlu formattır. WebP, aynı kalitede JPG'ye göre %25–%35 daha küçük dosya üretir ve web siteleri için en iyi tercihtir. PNG ise logo, ekran görüntüsü ve şeffaf arka planlı görseller için uygundur ancak fotoğraflarda dosya boyutu büyük kalır.</p>
</details>
<details>
<summary>Mobil cihazlarda çalışıyor mu?</summary>
<p>Evet. Araç iPhone, Android telefon, tablet ve bilgisayarlardaki tüm modern tarayıcılarda (Chrome, Safari, Edge, Firefox) herhangi bir uygulama yüklemeden çalışır.</p>
</details>
</section>
</article>
</main>
<footer class="site-footer">
<div class="container">
<span>© <span id="year"></span> MikroAraçlar · Tüm işlemler tarayıcınızda yapılır.</span>
<nav>
<a href="/hakkimizda">Hakkımızda</a>
<a href="/gizlilik-politikasi">Gizlilik Politikası</a>
<a href="/kullanim-kosullari">Kullanım Koşulları</a>
<a href="/iletisim">İletişim</a>
</nav>
</div>
</footer>
<!-- ================= İŞLEM BEKLEME EKRANI ================= -->
<div class="overlay" id="overlay" role="dialog" aria-modal="true" aria-labelledby="loaderTitle">
<div class="loader-box">
<div class="spinner"></div>
<h3 id="loaderTitle">Lütfen bekleyiniz...</h3>
<p id="loaderText">Görselleriniz optimize ediliyor</p>
<div class="progress"><div id="progressBar"></div></div>
</div>
</div>
<!-- ================= KARŞILAŞTIRMA PENCERESİ ================= -->
<div class="modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<div class="modal-box">
<div class="modal-head">
<h3 id="modalTitle">Önce / Sonra</h3>
<button class="icon-btn" id="modalClose" aria-label="Kapat">✕</button>
</div>
<div class="compare" id="compare">
<img id="cmpBefore" alt="Orijinal görsel" />
<img id="cmpAfter" class="after" alt="Sıkıştırılmış görsel" />
<div class="divider" id="cmpDivider"></div>
<span class="tag" style="left:10px" id="tagBefore">Orijinal</span>
<span class="tag" style="right:10px" id="tagAfter">Sıkıştırılmış</span>
</div>
<input type="range" class="compare-range" id="cmpRange" min="0" max="100" value="50" aria-label="Karşılaştırma kaydırıcısı" />
</div>
</div>
<!-- Toast bildirim kapsayıcısı -->
<div class="toasts" id="toasts" aria-live="assertive"></div>
<script>
(() => {
'use strict';
/* =========================================================
SABİTLER
========================================================= */
const MAX_FILES = 20;
const MAX_SIZE = 25 * 1024 * 1024; // 25 MB
const MIN_WAIT_MS = 3000; // Bekleme ekranı en az 3 sn görünür (Reklam Alanı 2)
const ACCEPTED = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/bmp'];
const EXT = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp' };
const PRESETS = {
web: { q: 75, format: 'image/webp', w: 1920, h: '', kb: '' },
mail: { q: 70, format: 'image/jpeg', w: 1280, h: '', kb: '' },
social: { q: 80, format: 'image/jpeg', w: 1080, h: '', kb: '' },
form: { q: 80, format: 'image/jpeg', w: '', h: '', kb: 200 },
max: { q: 50, format: 'image/webp', w: 1280, h: '', kb: '' }
};
/* =========================================================
DOM
========================================================= */
const $ = (id) => document.getElementById(id);
const dropzone = $('dropzone'), fileInput = $('fileInput'), fileList = $('fileList');
const quality = $('quality'), qualityVal = $('qualityVal'), formatSel = $('format');
const maxW = $('maxW'), maxH = $('maxH'), targetKB = $('targetKB');
const compressBtn = $('compressBtn'), downloadAllBtn = $('downloadAllBtn'), clearBtn = $('clearBtn');
const overlay = $('overlay'), loaderText = $('loaderText'), progressBar = $('progressBar');
const summary = $('summary');
/** @type {Array<{id:string,file:File,srcUrl:string,outBlob:Blob|null,outUrl:string|null,outName:string,w:number,h:number,ow:number,oh:number,kept:boolean}>} */
let items = [];
let busy = false;
/* =========================================================
YARDIMCI FONKSİYONLAR
========================================================= */
const fmtSize = (b) => b < 1024 ? b + ' B' : b < 1048576 ? (b / 1024).toFixed(1) + ' KB' : (b / 1048576).toFixed(2) + ' MB';
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const uid = () => Math.random().toString(36).slice(2, 10);
const esc = (s) => s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
// Tarayıcının WebP üretip üretemediğini kontrol et (eski Safari desteklemez)
const WEBP_OK = (() => {
try { const c = document.createElement('canvas'); c.width = c.height = 1; return c.toDataURL('image/webp').startsWith('data:image/webp'); }
catch { return false; }
})();
if (!WEBP_OK) {
[...formatSel.options].forEach(o => { if (o.value === 'image/webp') { o.disabled = true; o.textContent += ' – tarayıcınız desteklemiyor'; } });
}
/* ---------- Toast ---------- */
function toast(msg, type = 'info', ms = 4000) {
const icons = { info: 'ℹ️', success: '✅', error: '⛔', warn: '⚠️' };
const el = document.createElement('div');
el.className = 'toast ' + type;
el.innerHTML = `<span>${icons[type] || ''}</span><div>${msg}</div>`;
$('toasts').appendChild(el);
setTimeout(() => { el.classList.add('hide'); setTimeout(() => el.remove(), 300); }, ms);
}
/* ---------- Tema ---------- */
const themeBtn = $('themeToggle');
function applyTheme(t) {
document.documentElement.setAttribute('data-theme', t);
themeBtn.textContent = t === 'dark' ? '☀️' : '🌙';
}
let savedTheme = null;
try { savedTheme = localStorage.getItem('mt-theme'); } catch {}
applyTheme(savedTheme || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
themeBtn.addEventListener('click', () => {
const next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
applyTheme(next);
try { localStorage.setItem('mt-theme', next); } catch {}
});
/* =========================================================
DOSYA EKLEME
========================================================= */
function addFiles(fileArr) {
if (busy) return;
let added = 0;
for (const file of fileArr) {
if (items.length >= MAX_FILES) { toast(`En fazla ${MAX_FILES} görsel ekleyebilirsiniz.`, 'warn'); break; }
const name = file.name || 'yapistirilan-gorsel.png';
if (!ACCEPTED.includes(file.type)) {
const hint = /heic|heif/i.test(name) ? ' (HEIC formatı tarayıcılarda desteklenmez; iPhone\'da Ayarlar › Kamera › Formatlar › "En Uyumlu" seçeneğini kullanın.)' : '';
toast(`<b>${esc(name)}</b> desteklenmeyen bir dosya türü.${hint}`, 'error', 6000);
continue;
}
if (file.size > MAX_SIZE) { toast(`<b>${esc(name)}</b> 25 MB sınırını aşıyor.`, 'error'); continue; }
items.push({ id: uid(), file, name, srcUrl: URL.createObjectURL(file), outBlob: null, outUrl: null, outName: '', w: 0, h: 0, ow: 0, oh: 0, kept: false });
added++;
}
if (added) toast(`${added} görsel eklendi. Ayarları seçip "Görselleri Küçült" butonuna basın.`, 'info', 3000);
render();
}
dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fileInput.click(); } });
fileInput.addEventListener('change', () => { addFiles([...fileInput.files]); fileInput.value = ''; });
['dragenter', 'dragover'].forEach(ev => dropzone.addEventListener(ev, (e) => { e.preventDefault(); dropzone.classList.add('drag'); }));
['dragleave', 'drop'].forEach(ev => dropzone.addEventListener(ev, (e) => { e.preventDefault(); dropzone.classList.remove('drag'); }));
dropzone.addEventListener('drop', (e) => addFiles([...e.dataTransfer.files]));
// Sayfa geneline bırakılan dosyaların tarayıcıda açılmasını engelle
window.addEventListener('dragover', (e) => e.preventDefault());
window.addEventListener('drop', (e) => e.preventDefault());
// Panodan yapıştırma (Ctrl+V)
window.addEventListener('paste', (e) => {
const files = [...(e.clipboardData?.items || [])].filter(i => i.kind === 'file').map(i => i.getAsFile()).filter(Boolean);
if (files.length) addFiles(files);
});
/* =========================================================
AYARLAR
========================================================= */
function syncQualityUI() {
qualityVal.textContent = '%' + quality.value;
const isPng = formatSel.value === 'image/png';
quality.disabled = isPng;
$('qualityHint').textContent = isPng ? 'PNG kayıpsızdır, kalite ayarı uygulanmaz.' : 'Önerilen: %70 – %80';
}
quality.addEventListener('input', syncQualityUI);
formatSel.addEventListener('change', syncQualityUI);
document.querySelectorAll('[data-preset]').forEach(btn => btn.addEventListener('click', () => {
const p = PRESETS[btn.dataset.preset];
quality.value = p.q;
formatSel.value = (p.format === 'image/webp' && !WEBP_OK) ? 'image/jpeg' : p.format;
maxW.value = p.w; maxH.value = p.h; targetKB.value = p.kb;
syncQualityUI();
toast(`"${btn.textContent.trim()}" ayarı uygulandı.`, 'success', 2200);
}));
function readOptions() {
const w = parseInt(maxW.value, 10), h = parseInt(maxH.value, 10), kb = parseFloat(targetKB.value);
if ((maxW.value && (!(w >= 16) || w > 10000)) || (maxH.value && (!(h >= 16) || h > 10000))) {
throw new Error('Maksimum boyut 16 ile 10.000 piksel arasında olmalıdır.');
}
if (targetKB.value && (!(kb >= 5) || kb > 50000)) throw new Error('Hedef boyut 5 KB ile 50.000 KB arasında olmalıdır.');
return { q: +quality.value / 100, format: formatSel.value, maxW: w || 0, maxH: h || 0, targetBytes: kb ? kb * 1024 : 0 };
}
/* =========================================================
SIKIŞTIRMA MOTORU (Canvas API)
========================================================= */
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('Görsel okunamadı (dosya bozuk olabilir).'));
img.src = url;
});
}
function canvasToBlob(canvas, mime, q) {
return new Promise((resolve, reject) => {
canvas.toBlob(b => b ? resolve(b) : reject(new Error('Görsel dönüştürülemedi (bellek yetersiz olabilir).')), mime, q);
});
}
function draw(img, width, height, mime) {
const c = document.createElement('canvas');
c.width = width; c.height = height;
const ctx = c.getContext('2d');
if (mime === 'image/jpeg') { ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, width, height); } // JPG şeffaflığı desteklemez
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, width, height);
return c;
}
function resolveMime(fileType, choice) {
if (choice !== 'original') return choice;
if (fileType === 'image/webp') return WEBP_OK ? 'image/webp' : 'image/jpeg';
if (fileType === 'image/png' || fileType === 'image/gif') return 'image/png';
return 'image/jpeg'; // jpeg, bmp
}
async function compress(item, opt) {
const img = await loadImage(item.srcUrl);
const ow = img.naturalWidth, oh = img.naturalHeight;
if (!ow || !oh) throw new Error('Görsel boyutları okunamadı.');
let scale = 1;
if (opt.maxW) scale = Math.min(scale, opt.maxW / ow);
if (opt.maxH) scale = Math.min(scale, opt.maxH / oh);
const mime = resolveMime(item.file.type, opt.format);
const lossy = mime !== 'image/png';
let blob, w, h;
const render = async (s, q) => {
w = Math.max(1, Math.round(ow * s)); h = Math.max(1, Math.round(oh * s));
return canvasToBlob(draw(img, w, h, mime), mime, q);
};
if (opt.targetBytes) {
// Hedef boyuta ulaşmak için önce kaliteyi (ikili arama), sonra çözünürlüğü düşür
for (let step = 0; step < 8; step++) {
if (lossy) {
let lo = 0.05, hi = Math.min(opt.q, 0.95), best = null;
for (let i = 0; i < 7; i++) {
const mid = (lo + hi) / 2;
const b = await render(scale, mid);
if (b.size <= opt.targetBytes) { best = b; lo = mid; } else { hi = mid; }
}
blob = best || await render(scale, hi);
} else {
blob = await render(scale);
}
if (blob.size <= opt.targetBytes) break;
scale *= 0.8;
}
} else {
blob = await render(scale, lossy ? opt.q : undefined);
}
// Sonuç orijinalden büyükse ve format/çözünürlük değişmediyse orijinali koru
let kept = false;
if (blob.size >= item.file.size && mime === item.file.type && w === ow && h === oh) {
blob = item.file; kept = true;
}
const base = item.name.replace(/\.[^.]+$/, '') || 'gorsel';
return { blob, w, h, ow, oh, kept, outName: `${base}-kucuk.${EXT[mime]}` };
}
/* =========================================================
İŞLEMİ BAŞLAT
========================================================= */
compressBtn.addEventListener('click', async () => {
if (!items.length || busy) return;
let opt;
try { opt = readOptions(); } catch (err) { toast(err.message, 'error'); return; }
busy = true; setButtons();
progressBar.style.width = '0%';
overlay.classList.add('show');
// (adsbygoogle = window.adsbygoogle || []).push({}); // İşlem esnası reklamını burada tetikleyin
const work = (async () => {
let done = 0, failed = 0;
for (const item of items) {
loaderText.textContent = `${done + 1} / ${items.length} görsel işleniyor...`;
try {
const r = await compress(item, opt);
if (item.outUrl) URL.revokeObjectURL(item.outUrl);
Object.assign(item, { outBlob: r.blob, outUrl: URL.createObjectURL(r.blob), outName: r.outName, w: r.w, h: r.h, ow: r.ow, oh: r.oh, kept: r.kept, error: '' });
} catch (err) {
failed++; item.error = err.message || 'Bilinmeyen hata';
toast(`<b>${esc(item.name)}</b>: ${esc(item.error)}`, 'error', 6000);
}
done++;
progressBar.style.width = Math.round(done / items.length * 100) + '%';
await sleep(0); // Arayüzün güncellenmesine izin ver
}
return failed;
})();
const [failed] = await Promise.all([work, sleep(MIN_WAIT_MS)]);
overlay.classList.remove('show');
busy = false;
render();
if (failed < items.length) toast('Görselleriniz hazır! Aşağıdan indirebilirsiniz.', 'success');
if (opt.targetBytes && items.some(i => i.outBlob && i.outBlob.size > opt.targetBytes)) {
toast('Bazı görseller hedef boyutun altına indirilemedi. Hedefi biraz artırın veya PNG dosyalar için format olarak JPG/WebP seçin.', 'warn', 6000);
}
});
/* =========================================================
İNDİRME
========================================================= */
function download(item) {
const a = document.createElement('a');
a.href = item.outUrl; a.download = item.outName;
document.body.appendChild(a); a.click(); a.remove();
}
downloadAllBtn.addEventListener('click', async () => {
const ready = items.filter(i => i.outBlob);
if (!ready.length) return;
if (ready.length > 1) toast('Tarayıcınız birden fazla indirme için izin isteyebilir; lütfen "İzin ver" deyin.', 'info', 5000);
for (const it of ready) { download(it); await sleep(350); }
});
clearBtn.addEventListener('click', () => {
if (busy) return;
items.forEach(i => { URL.revokeObjectURL(i.srcUrl); if (i.outUrl) URL.revokeObjectURL(i.outUrl); });
items = []; render();
});
/* =========================================================
LİSTEYİ ÇİZ
========================================================= */
function setButtons() {
const hasItems = items.length > 0, hasOut = items.some(i => i.outBlob);
compressBtn.disabled = !hasItems || busy;
clearBtn.disabled = !hasItems || busy;
downloadAllBtn.disabled = !hasOut || busy;
compressBtn.textContent = hasOut ? '🔁 Yeni Ayarlarla Tekrar Küçült' : '⚡ Görselleri Küçült';
}
function render() {
fileList.innerHTML = items.map(it => {
let meta = `Orijinal: <b>${fmtSize(it.file.size)}</b>`;
let btns = '';
if (it.error) {
meta += ` · <span style="color:var(--danger)">Hata: ${esc(it.error)}</span>`;
} else if (it.outBlob) {
const pct = Math.round((1 - it.outBlob.size / it.file.size) * 100);
const dims = (it.w !== it.ow || it.h !== it.oh) ? ` · ${it.ow}×${it.oh} → ${it.w}×${it.h}` : ` · ${it.w}×${it.h}`;
meta += ` → Yeni: <b>${fmtSize(it.outBlob.size)}</b> `;
meta += it.kept ? `<span class="saving neutral">Zaten optimize</span>`
: pct > 0 ? `<span class="saving">−%${pct}</span>` : `<span class="saving neutral">+%${Math.abs(pct)}</span>`;
meta += dims;
btns = `<button class="btn btn-ghost btn-sm" data-act="compare" data-id="${it.id}">👁️ Karşılaştır</button>
<button class="btn btn-success btn-sm" data-act="dl" data-id="${it.id}">⬇️ İndir</button>`;
} else {
meta += ' · <span>İşlem bekliyor</span>';
}
return `<li class="file-item">
<img class="thumb" src="${it.srcUrl}" alt="" loading="lazy" />
<div style="min-width:0">
<div class="f-name" title="${esc(it.name)}">${esc(it.name)}</div>
<div class="f-meta">${meta}</div>
</div>
<div class="f-actions">${btns}<button class="remove" data-act="rm" data-id="${it.id}" aria-label="Kaldır" title="Kaldır">✕</button></div>
</li>`;
}).join('');
// Toplam özet
const done = items.filter(i => i.outBlob);
if (done.length) {
const before = done.reduce((s, i) => s + i.file.size, 0), after = done.reduce((s, i) => s + i.outBlob.size, 0);
const pct = Math.max(0, Math.round((1 - after / before) * 100));
summary.innerHTML = `🎉 <span>${done.length} görsel</span><span>${fmtSize(before)} → <b>${fmtSize(after)}</b></span><span>Toplam tasarruf: <strong>%${pct}</strong> (${fmtSize(Math.max(0, before - after))})</span>`;
summary.classList.add('show');
} else {
summary.classList.remove('show');
}
setButtons();
}
fileList.addEventListener('click', (e) => {
const btn = e.target.closest('[data-act]'); if (!btn || busy) return;
const idx = items.findIndex(i => i.id === btn.dataset.id); if (idx < 0) return;
const it = items[idx];
if (btn.dataset.act === 'dl') download(it);
if (btn.dataset.act === 'compare') openCompare(it);
if (btn.dataset.act === 'rm') {
URL.revokeObjectURL(it.srcUrl); if (it.outUrl) URL.revokeObjectURL(it.outUrl);
items.splice(idx, 1); render();
}
});
/* =========================================================
ÖNCE / SONRA KARŞILAŞTIRMA
========================================================= */
const modal = $('modal'), cmpRange = $('cmpRange'), cmpAfter = $('cmpAfter'), cmpDivider = $('cmpDivider');
function setSplit(v) {
cmpAfter.style.clipPath = `inset(0 0 0 ${v}%)`;
cmpDivider.style.left = `calc(${v}% - 1.5px)`;
}
function openCompare(it) {
$('cmpBefore').src = it.srcUrl;
cmpAfter.src = it.outUrl;
$('tagBefore').textContent = 'Orijinal · ' + fmtSize(it.file.size);
$('tagAfter').textContent = 'Yeni · ' + fmtSize(it.outBlob.size);
cmpRange.value = 50; setSplit(50);
modal.classList.add('show');
}
cmpRange.addEventListener('input', () => setSplit(cmpRange.value));
// Görsel üzerinde sürükleyerek de karşılaştırma yapılabilsin
$('compare').addEventListener('pointermove', (e) => {
if (e.buttons !== 1 && e.pointerType === 'mouse') return;
const r = e.currentTarget.getBoundingClientRect();
const v = Math.min(100, Math.max(0, (e.clientX - r.left) / r.width * 100));
cmpRange.value = v; setSplit(v);
});
const closeModal = () => modal.classList.remove('show');
$('modalClose').addEventListener('click', closeModal);
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
/* ---------- Başlangıç ---------- */
$('year').textContent = new Date().getFullYear();
syncQualityUI();
setButtons();
})();
</script>
</body>
</html>