anycoder-8dd8adfe / index.html
kdemirwarehouse's picture
Upload folder using huggingface_hub
86bd130 verified
Raw
History Blame Contribute Delete
35.7 kB
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kelime Bulutu Oluşturucu | Word Cloud Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,700;1,9..40,400&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['DM Sans', 'sans-serif'],
display: ['Space Grotesk', 'sans-serif'],
},
colors: {
ink: {
50: '#F0F4FF',
100: '#D4DDF5',
200: '#A8B6D6',
300: '#7D8FB8',
400: '#52679A',
500: '#3B507C',
600: '#2A3A5C',
700: '#1C273D',
800: '#111827',
900: '#0B0F19',
},
cloud: {
red: '#FF4757',
orange: '#FFA502',
amber: '#FFC312',
green: '#2ED573',
teal: '#1DD1A1',
blue: '#3742FA',
indigo: '#5352ED',
purple: '#A55EEA',
magenta: '#FF0099',
cyan: '#00D2D3',
}
}
}
}
}
</script>
<style>
:root {
--spotlight-size: 500px;
--spotlight-blur: 80px;
--spotlight-opacity: 0.12;
}
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
overflow-x: hidden;
}
body {
font-family: 'DM Sans', sans-serif;
background: #0B0F19;
color: #E8ECF4;
min-height: 100vh;
position: relative;
}
body::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 120% 80% at 20% 0%, rgba(55, 66, 250, 0.15) 0%, transparent 60%),
radial-gradient(ellipse 100% 100% at 80% 100%, rgba(165, 94, 234, 0.12) 0%, transparent 55%),
radial-gradient(ellipse 80% 50% at 50% 50%, rgba(29, 209, 161, 0.06) 0%, transparent 50%);
z-index: 0;
pointer-events: none;
}
body::after {
content: '';
position: fixed;
inset: 0;
background-image:
radial-gradient(circle at 2px 2px, rgba(168, 182, 214, 0.04) 1px, transparent 0);
background-size: 40px 40px;
z-index: 0;
pointer-events: none;
}
.noise-overlay {
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
}
/* Animated mesh background */
.mesh-bg {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.mesh-bg .orb {
position: absolute;
border-radius: 50%;
filter: blur(var(--spotlight-blur));
animation: orbFloat 20s ease-in-out infinite alternate;
}
.mesh-bg .orb-1 {
width: 600px;
height: 600px;
top: -10%;
left: -10%;
background: rgba(55, 66, 250, var(--spotlight-opacity));
animation-delay: 0s;
}
.mesh-bg .orb-2 {
width: 500px;
height: 500px;
bottom: -15%;
right: -5%;
background: rgba(165, 94, 234, var(--spotlight-opacity));
animation-delay: -5s;
}
.mesh-bg .orb-3 {
width: 400px;
height: 400px;
top: 50%;
left: 60%;
background: rgba(29, 209, 161, calc(var(--spotlight-opacity) * 0.7));
animation-delay: -10s;
}
@keyframes orbFloat {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(30px, -40px) scale(1.05); }
66% { transform: translate(-20px, 20px) scale(0.95); }
}
/* App container */
.app-wrap {
position: relative;
z-index: 10;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.app-header {
border-bottom: 1px solid rgba(168, 182, 214, 0.06);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.logo-icon {
width: 42px;
height: 42px;
background: linear-gradient(135deg, #3742FA 0%, #A55EEA 100%);
border-radius: 14px;
display: grid;
place-items: center;
box-shadow: 0 4px 20px rgba(55, 66, 250, 0.3);
position: relative;
overflow: hidden;
}
.logo-icon::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, transparent 40%, rgba(255,255,255,0.15) 100%);
}
/* Textarea */
.editor-box {
position: relative;
}
.editor-box textarea {
width: 100%;
min-height: 220px;
padding: 20px 24px;
border: 1.5px solid rgba(168, 182, 214, 0.10);
border-radius: 20px;
background: rgba(17, 24, 39, 0.5);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: #E8ECF4;
font-family: 'DM Sans', sans-serif;
font-size: 15px;
line-height: 1.75;
resize: vertical;
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
box-shadow:
inset 0 1px 1px rgba(255,255,255,0.03),
0 1px 2px rgba(0,0,0,0.1);
}
.editor-box textarea::placeholder {
color: rgba(168, 182, 214, 0.3);
}
.editor-box textarea:hover {
border-color: rgba(168, 182, 214, 0.18);
background: rgba(17, 24, 39, 0.6);
}
.editor-box textarea:focus {
border-color: rgba(55, 66, 250, 0.4);
background: rgba(17, 24, 39, 0.7);
box-shadow:
inset 0 1px 1px rgba(255,255,255,0.03),
0 0 0 3px rgba(55, 66, 250, 0.08),
0 1px 2px rgba(0,0,0,0.1);
}
/* Action button */
.btn-generate {
position: relative;
padding: 14px 32px;
border-radius: 14px;
border: none;
font-family: 'Space Grotesk', sans-serif;
font-weight: 600;
font-size: 15px;
letter-spacing: 0.02em;
cursor: pointer;
overflow: hidden;
color: white;
background: linear-gradient(135deg, #3742FA 0%, #5352ED 50%, #A55EEA 100%);
background-size: 200% 200%;
animation: gradientShift 5s ease infinite;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 4px 20px rgba(55, 66, 250, 0.25),
0 1px 3px rgba(0,0,0,0.15);
display: inline-flex;
align-items: center;
gap: 10px;
}
.btn-generate::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, transparent 40%, rgba(255,255,255,0.1) 100%);
opacity: 0;
transition: opacity 0.3s;
}
.btn-generate:hover {
transform: translateY(-2px) scale(1.01);
box-shadow:
0 8px 30px rgba(55, 66, 250, 0.35),
0 2px 6px rgba(0,0,0,0.15);
}
.btn-generate:hover::before {
opacity: 1;
}
.btn-generate:active {
transform: translateY(0) scale(0.98);
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Canvas card */
.canvas-card {
border: 1.5px solid rgba(168, 182, 214, 0.08);
border-radius: 24px;
background: rgba(17, 24, 39, 0.45);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 4px 24px rgba(0,0,0,0.15),
inset 0 1px 0 rgba(255,255,255,0.03);
}
.canvas-card:hover {
border-color: rgba(168, 182, 214, 0.14);
box-shadow:
0 8px 40px rgba(0,0,0,0.2),
inset 0 1px 0 rgba(255,255,255,0.03);
}
canvas {
display: block;
cursor: grab;
border-radius: 4px;
}
canvas:active {
cursor: grabbing;
}
/* Stats grid */
.stat-card {
border: 1px solid rgba(168, 182, 214, 0.08);
border-radius: 16px;
background: rgba(17, 24, 39, 0.4);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, currentColor, transparent);
opacity: 0;
transition: opacity 0.3s;
}
.stat-card:hover {
transform: translateY(-3px);
border-color: rgba(168, 182, 214, 0.16);
}
.stat-card:hover::before {
opacity: 0.5;
}
/* Word list */
.word-list-item {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
cursor: default;
}
.word-list-item:hover {
background: rgba(168, 182, 214, 0.06);
transform: translateX(4px);
}
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(168, 182, 214, 0.15);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(168, 182, 214, 0.25);
}
/* Secondary button */
.btn-secondary {
padding: 10px 20px;
border-radius: 12px;
border: 1.5px solid rgba(168, 182, 214, 0.12);
background: rgba(17, 24, 39, 0.4);
color: #A8B6D6;
font-family: 'DM Sans', sans-serif;
font-weight: 500;
font-size: 13px;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-secondary:hover {
border-color: rgba(168, 182, 214, 0.25);
background: rgba(168, 182, 214, 0.08);
color: #D4DDF5;
transform: translateY(-1px);
}
.btn-secondary:active {
transform: translateY(0);
}
/* Input card */
.input-card {
border: 1.5px solid rgba(168, 182, 214, 0.08);
border-radius: 24px;
background: rgba(17, 24, 39, 0.35);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow:
0 4px 24px rgba(0,0,0,0.1),
inset 0 1px 0 rgba(255,255,255,0.02);
}
/* Badge */
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 6px 14px;
border-radius: 100px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
border: 1px solid;
}
/* Divider */
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, rgba(168, 182, 214, 0.08), transparent);
}
/* Footer */
.app-footer {
border-top: 1px solid rgba(168, 182, 214, 0.06);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
/* Responsive */
@media (max-width: 768px) {
.editor-box textarea {
min-height: 160px;
}
.canvas-card {
border-radius: 16px;
}
}
/* Fade in */
.fade-in {
animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
opacity: 0;
}
@keyframes fadeIn {
to { opacity: 1; }
}
/* AnyCoder link */
.anycoder-link {
color: #A8B6D6;
transition: color 0.2s;
}
.anycoder-link:hover {
color: #3742FA;
}
</style>
</head>
<body>
<!-- Background effects -->
<div class="noise-overlay"></div>
<div class="mesh-bg">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
</div>
<div class="app-wrap">
<!-- Header -->
<header class="app-header sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-5 sm:px-8 py-4 flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="logo-icon">
<i class="fa-solid fa-cloud text-white text-base"></i>
</div>
<div>
<h1 class="font-display text-lg font-bold text-white tracking-tight leading-tight">Kelime Bulutu</h1>
<p class="text-[11px] text-ink-300 font-medium tracking-wide uppercase">Word Cloud Generator</p>
</div>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link text-xs font-medium hidden sm:flex items-center gap-1.5">
<span>Built with anycoder</span>
<i class="fa-solid fa-arrow-up-right-from-square text-[10px]"></i>
</a>
</div>
</header>
<!-- Main -->
<main class="flex-1 max-w-7xl mx-auto w-full px-5 sm:px-8 py-8 sm:py-12">
<!-- Top info -->
<div class="mb-10 sm:mb-14 text-center max-w-2xl mx-auto">
<div class="inline-flex items-center gap-2 mb-5">
<span class="badge border-indigo-500/20 text-indigo-300 bg-indigo-500/5">
<i class="fa-solid fa-sparkles text-[10px]"></i>
Türkçe Metin Analizi
</span>
</div>
<h2 class="font-display text-3xl sm:text-4xl md:text-[2.75rem] font-bold text-white leading-tight mb-4">
Metninizi görselleştirin
</h2>
<p class="text-ink-300 text-[15px] leading-relaxed max-w-lg mx-auto">
Yapıştırdığınız metni analiz eder, en sık geçen kelimeleri büyük ve renkli bir buluta dönüştürür.
</p>
</div>
<!-- Input Section -->
<div class="input-card p-6 sm:p-8 mb-8 sm:mb-10">
<div class="flex items-center justify-between mb-4">
<label class="text-sm font-semibold text-white flex items-center gap-2">
<i class="fa-regular fa-pen-to-square text-indigo-400"></i>
Metninizi Yapıştırın
</label>
<span class="text-[11px] text-ink-300 font-medium tabular-nums" id="charCount">0 karakter</span>
</div>
<div class="editor-box">
<textarea id="textInput" placeholder="Kelime bulutu oluşturmak için metninizi buraya yapıştırın...&#10;&#10;Örnek: Tarih boyunca insanlar yıldızlara baktılar ve evrenin sırlarını çözmeye çalıştılar. Bilim, sanat ve felsefe ile bu büyük sorulara yanıt aradılar. Günümüzde teknoloji sayesinde uzaya gidiyoruz ve yeni keşifler yapıyoruz. Her yeni bilgi bizi daha da meraklandırıyor ve bilinmeyene olan tutkumuzu artırıyor." spellcheck="false"></textarea>
</div>
<div class="flex items-center justify-between mt-5 flex-wrap gap-3">
<span class="text-xs text-ink-300/60">
<i class="fa-solid fa-shield-halved mr-1.5 text-[10px]"></i>
Stopwords otomatik filtrelenir
</span>
<button id="generateBtn" class="btn-generate">
<i class="fa-solid fa-wand-magic-sparkles"></i>
Bulut Oluştur
</button>
</div>
</div>
<!-- Canvas Section -->
<div id="canvasSection" class="canvas-card p-4 sm:p-6 mb-8 sm:mb-10 hidden">
<div class="flex items-center justify-between mb-5 flex-wrap gap-3">
<div>
<h3 class="font-display text-lg font-bold text-white mb-0.5">Kelime Bulutu</h3>
<p class="text-xs text-ink-300">Kelime üzerine gelerek frekansı görün</p>
</div>
<div class="flex gap-2">
<button id="downloadBtn" class="btn-secondary">
<i class="fa-solid fa-download text-xs"></i>
İndir (PNG)
</button>
</div>
</div>
<div class="relative rounded-2xl overflow-hidden border border-ink-700/30">
<canvas id="wordCloudCanvas"></canvas>
</div>
<p class="text-[11px] text-ink-300/50 mt-3 text-center">
<i class="fa-solid fa-mouse-pointer mr-1"></i>
Kelimelerin üzerine tıklayarak konumlarını keşfedin
</p>
</div>
<!-- Stats Section -->
<div id="statsSection" class="hidden">
<div class="flex items-center gap-3 mb-6">
<div class="h-px flex-1 divider"></div>
<span class="text-xs font-semibold text-ink-300/60 uppercase tracking-widest">Analiz Sonuçları</span>
<div class="h-px flex-1 divider"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div class="stat-card" style="color: #3742FA;">
<div class="flex items-center gap-3 mb-3">
<div class="w-9 h-9 rounded-xl bg-indigo-500/10 flex items-center justify-center">
<i class="fa-solid fa-align-left text-indigo-400 text-sm"></i>
</div>
<span class="text-xs font-semibold text-ink-200 uppercase tracking-wide">Toplam Kelime</span>
</div>
<div id="totalWords" class="font-display text-3xl font-bold text-white">0</div>
</div>
<div class="stat-card" style="color: #A55EEA;">
<div class="flex items-center gap-3 mb-3">
<div class="w-9 h-9 rounded-xl bg-purple-500/10 flex items-center justify-center">
<i class="fa-solid fa-fingerprint text-purple-400 text-sm"></i>
</div>
<span class="text-xs font-semibold text-ink-200 uppercase tracking-wide">Benzersiz Kelime</span>
</div>
<div id="uniqueWords" class="font-display text-3xl font-bold text-white">0</div>
</div>
<div class="stat-card" style="color: #1DD1A1;">
<div class="flex items-center gap-3 mb-3">
<div class="w-9 h-9 rounded-xl bg-teal-500/10 flex items-center justify-center">
<i class="fa-solid fa-layer-group text-teal-400 text-sm"></i>
</div>
<span class="text-xs font-semibold text-ink-200 uppercase tracking-wide">Bulutta Kelime</span>
</div>
<div id="cloudWords" class="font-display text-3xl font-bold text-white">0</div>
</div>
</div>
<div class="canvas-card p-5 sm:p-6">
<h3 class="font-display text-base font-bold text-white mb-5 flex items-center gap-2">
<i class="fa-solid fa-chart-simple text-magenta"></i>
En Sık Geçen Kelimeler
</h3>
<div id="wordList" class="space-y-1">
<!-- JS fills this -->
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="app-footer mt-auto">
<div class="max-w-7xl mx-auto px-5 sm:px-8 py-5 flex items-center justify-between">
<p class="text-xs text-ink-300/40">
Kelime Bulutu Oluşturucu v1.0
</p>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link text-xs font-medium flex items-center gap-1.5">
<span>Built with anycoder</span>
<i class="fa-solid fa-arrow-up-right-from-square text-[10px]"></i>
</a>
</div>
</footer>
</div>
<script>
// ============================================================
// TURKISH STOPWORDS
// ============================================================
const STOPWORDS = new Set([
've','ile','bu','şu','bir','için','gibi','çok','daha','ama','ise','ki','her',
'bütün','tüm','bazı','diğer','kadar','göre','yerine','karşı','rağmen','sonra',
'önce','arası','arasında','üzere','beri','sadece','hala','henüz','artık',
'yine','yok','evet','hayır','tamam','tamamen','öyle','böyle','şöyle','nasıl',
'ne','kim','nerede','nereye','nereden','hangi','kaç','mi','mı','mu','mü',
'da','de','ta','te','ya','ya da','veya','fakat','lakin','çünkü','eğer','ise',
'ki','şayet','oysa','oysa ki','halbuki','bile','sanki','adeta','meğer','üstelik',
'belki','sanırım','galiba','acaba','herhalde','kesinlikle','asla','hiç','çoğu',
'hepsi','biri','kimse','hiçbiri','kimi','bazısı','birkaç','birçok','biraz',
'az','fazla','azıcık','çoğunlukla','çoğu','bazen','sürekli','daima','asla',
'hiçbir zaman','genellikle','bazan','nadiren','yakında','hemen','şimdi','şu an',
'bugün','yarın','dün','bu gece','her zaman','bazen','yine de','yine','tekrar',
'bir kez','iki kez','üç kez','defa','kere','kez','ilk','son','ikinci','üçüncü',
'dördüncü','beşinci','altıncı','yedinci','sekizinci','dokuzuncu','onuncu','o',
'ben','sen','o','biz','siz','onlar','benim','senin','onun','bizim','sizin',
'onların','bana','sana','ona','bize','size','onlara','beni','seni','onu',
'bizi','sizi','onları','kendi','kendim','kendin','kendisi','kendileri',
'burada','orada','şurada','nerede','her yerde','hiçbir yerde','başka yerde',
'içeri','dışarı','yukarı','aşağı','ileri','geri','sağa','sola','öne','arkaya',
'şimdi','hemen','birazdan','sonradan','önceden','daha sonra','daha önce',
'artık','hâlâ','zaten','bile','öyleyse','yoksa','dolayısıyla','buna göre',
'bunun için','bunun üzerine','bunun dışında','bunun yanı sıra','yani',
'başka bir deyişle','diğer bir deyişle','yani ki','yoksa ki','bildiğiniz gibi',
'kuşkusuz','elbette','tabii','tabi','tabii ki','kesinlikle','mutlaka',
'özellikle','özellikle de','özellikle ise','özellikle değil','ama','fakat',
'ancak','buna karşın','buna rağmen','yine de','her ne kadar','ne de olsa',
'sonuç olarak','sonuç itibarıyla','nihayetinde','en nihayetinde','özetle',
'kısaca','yani özetle','böylece','bu şekilde','bu yolla','bu yöntemle',
'bu arada','bu sırada','bu esnada','şu sıralar','bu günlerde','gelecekte',
'geçmişte','şimdiki zamanda','geçen','gelecek','geçtiğimiz','gelecekteki',
'geçmişteki','şimdiki','o zamanki','şu zamanki','bu zamanki',
]);
// ============================================================
// APP
// ============================================================
const textInput = document.getElementById('textInput');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const canvas = document.getElementById('wordCloudCanvas');
const canvasSection = document.getElementById('canvasSection');
const statsSection = document.getElementById('statsSection');
const charCount = document.getElementById('charCount');
const ctx = canvas.getContext('2d');
let wordsData = [];
let placedWords = [];
let animationFrame = null;
let isAnimating = false;
// Color scheme
const COLOR_PALETTE = [
{ name: 'Kırmızı', color: '#FF4757' },
{ name: 'Turuncu', color: '#FFA502' },
{ name: 'Sarı', color: '#FFC312' },
{ name: 'Yeşil', color: '#2ED573' },
{ name: 'Turkuaz', color: '#1DD1A1' },
{ name: 'Mavi', color: '#3742FA' },
{ name: 'İndigo', color: '#5352ED' },
{ name: 'Mor', color: '#A55EEA' },
{ name: 'Pembe', color: '#FF0099' },
{ name: 'Cyan', color: '#00D2D3' },
];
// Character count
textInput.addEventListener('input', () => {
charCount.textContent = `${textInput.value.length} karakter`;
});
// Generate
generateBtn.addEventListener('click', generateWordCloud);
downloadBtn.addEventListener('click', downloadCloud);
function generateWordCloud() {
const rawText = textInput.value.trim();
if (!rawText) {
textInput.focus();
return;
}
// Parse words
const tokens = rawText.toLowerCase()
.replace(/[^a-zçğıöşü\s]/gi, ' ')
.split(/\s+/)
.filter(w => w.length > 1 && !STOPWORDS.has(w));
if (tokens.length === 0) {
alert('Geçerli kelime bulunamadı. Daha uzun bir metin girin.');
return;
}
// Count frequencies
const freqMap = {};
tokens.forEach(t => { freqMap[t] = (freqMap[t] || 0) + 1; });
const allWords = Object.entries(freqMap)
.map(([word, count]) => ({ word, count, display: capitalize(word) }))
.sort((a, b) => b.count - a.count);
const cloudWords = allWords.slice(0, 80);
const maxCount = cloudWords[0].count;
const minCount = cloudWords[cloudWords.length - 1].count;
// Assign sizes
cloudWords.forEach(w => {
const ratio = maxCount === minCount ? 1 : (w.count - minCount) / (maxCount - minCount);
const minSize = 14, maxSize = 78;
w.fontSize = Math.round(minSize + ratio * (maxSize - minSize));
});
// Assign colors
const colorAssignments = {};
const wordList = cloudWords.map(w => w.word);
const sortedByLen = [...wordList].sort((a, b) => b.length - a.length);
sortedByLen.forEach((word, i) => {
colorAssignments[word] = COLOR_PALETTE[i % COLOR_PALETTE.length].color;
});
cloudWords.forEach(w => { w.color = colorAssignments[w.word]; });
// Shuffle
const shuffled = shuffleArray([...cloudWords]);
// Canvas setup
const containerWidth = Math.min(canvas.parentElement.clientWidth - 16, 1100);
const aspectRatio = window.innerWidth < 768 ? 1.0 : 0.55;
const cw = containerWidth;
const ch = cw * aspectRatio;
const dpr = window.devicePixelRatio || 1;
canvas.width = cw * dpr;
canvas.height = ch * dpr;
canvas.style.width = cw + 'px';
canvas.style.height = ch + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
// Cloud layout (Spiral-based placement)
placedWords = [];
const cx = cw / 2, cy = ch / 2;
shuffled.forEach(item => {
ctx.font = `700 ${item.fontSize}px "DM Sans", "Segoe UI", sans-serif`;
const metrics = ctx.measureText(item.display);
const w = metrics.width + 12;
const h = item.fontSize + 8;
const area = w * h;
item.width = w;
item.height = h;
item.area = area;
let placed = false;
let angle = 0;
let radius = 5;
const maxRadius = Math.min(cx, cy) * 0.92;
const step = 2 + (80 - item.fontSize) * 0.04;
while (!placed && radius < maxRadius) {
const x = cx + Math.cos(angle) * radius - w / 2;
const y = cy + Math.sin(angle) * radius * 0.7 - h / 2;
if (x >= 5 && x + w <= cw - 5 && y >= 5 && y + h <= ch - 5) {
if (!checkCollision(x, y, w, h)) {
item.x = x;
item.y = y;
placedWords.push(item);
placed = true;
}
}
angle += 0.18;
radius += step * 0.04;
}
});
// Store for animation
wordsData = [...placedWords];
// Show sections
canvasSection.classList.remove('hidden');
canvasSection.classList.add('fade-in');
statsSection.classList.remove('hidden');
statsSection.classList.add('fade-in');
// Update stats
document.getElementById('totalWords').textContent = tokens.length.toLocaleString('tr-TR');
document.getElementById('uniqueWords').textContent = allWords.length.toLocaleString('tr-TR');
document.getElementById('cloudWords').textContent = placedWords.length.toLocaleString('tr-TR');
// Render word list
const listEl = document.getElementById('wordList');
listEl.innerHTML = '';
allWords.slice(0, 20).forEach((w, i) => {
const ratio = maxCount === minCount ? 1 : (w.count - minCount) / (maxCount - minCount);
const barW = Math.max(4, Math.round(ratio * 100));
const row = document.createElement('div');
row.className = 'word-list-item flex items-center gap-4 px-3 py-2.5 rounded-xl';
row.innerHTML = `
<span class="text-xs font-mono text-ink-400 w-6 text-right font-medium">${i + 1}</span>
<span class="text-sm font-semibold text-white w-36 sm:w-44 truncate">${w.display}</span>
<div class="flex-1 h-2 rounded-full bg-ink-700/40 overflow-hidden">
<div class="h-full rounded-full transition-all duration-700 ease-out" style="width:${barW}%; background:${w.color || '#A8B6D6'}"></div>
</div>
<span class="text-xs font-mono font-bold text-ink-300 w-8 text-right">${w.count}</span>
`;
listEl.appendChild(row);
});
// Animate
animateCloud(cw, ch);
canvas.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
function checkCollision(x, y, w, h) {
const gap = 6;
for (const p of placedWords) {
if (!(x + w + gap < p.x || x > p.x + p.width + gap ||
y + h + gap < p.y || y > p.y + p.height + gap)) {
return true;
}
}
return false;
}
function animateCloud(cw, ch) {
if (animationFrame) cancelAnimationFrame(animationFrame);
isAnimating = true;
let progress = 0;
const duration = 900;
const startTime = performance.now();
function frame(now) {
progress = Math.min((now - startTime) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 4); // easeOutQuart
ctx.clearRect(0, 0, cw, ch);
// Subtle background highlight
const cx = cw / 2, cy = ch / 2;
const maxR = Math.max(cx, cy);
const bgGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, maxR);
bgGrad.addColorStop(0, 'rgba(55, 66, 250, 0.02)');
bgGrad.addColorStop(0.6, 'rgba(165, 94, 234, 0.015)');
bgGrad.addColorStop(1, 'transparent');
ctx.fillStyle =