| <!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"); |
| } |
| |
| |
| .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-wrap { |
| position: relative; |
| z-index: 10; |
| min-height: 100vh; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| |
| .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%); |
| } |
| |
| |
| .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); |
| } |
| |
| |
| .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 { |
| 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; |
| } |
| |
| |
| .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-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); |
| } |
| |
| |
| ::-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); |
| } |
| |
| |
| .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 { |
| 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 { |
| 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 { |
| height: 1px; |
| background: linear-gradient(90deg, transparent, rgba(168, 182, 214, 0.08), transparent); |
| } |
| |
| |
| .app-footer { |
| border-top: 1px solid rgba(168, 182, 214, 0.06); |
| backdrop-filter: blur(20px); |
| -webkit-backdrop-filter: blur(20px); |
| } |
| |
| |
| @media (max-width: 768px) { |
| .editor-box textarea { |
| min-height: 160px; |
| } |
| .canvas-card { |
| border-radius: 16px; |
| } |
| } |
| |
| |
| .fade-in { |
| animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; |
| opacity: 0; |
| } |
| |
| @keyframes fadeIn { |
| to { opacity: 1; } |
| } |
| |
| |
| .anycoder-link { |
| color: #A8B6D6; |
| transition: color 0.2s; |
| } |
| .anycoder-link:hover { |
| color: #3742FA; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| |
| <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 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 class="flex-1 max-w-7xl mx-auto w-full px-5 sm:px-8 py-8 sm:py-12"> |
|
|
| |
| <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> |
|
|
| |
| <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... Ö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> |
|
|
| |
| <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> |
|
|
| |
| <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"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| </main> |
|
|
| |
| <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> |
| |
| |
| |
| 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', |
| ]); |
| |
| |
| |
| |
| 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; |
| |
| |
| 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' }, |
| ]; |
| |
| |
| textInput.addEventListener('input', () => { |
| charCount.textContent = `${textInput.value.length} karakter`; |
| }); |
| |
| |
| generateBtn.addEventListener('click', generateWordCloud); |
| downloadBtn.addEventListener('click', downloadCloud); |
| |
| function generateWordCloud() { |
| const rawText = textInput.value.trim(); |
| if (!rawText) { |
| textInput.focus(); |
| return; |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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; |
| |
| |
| 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)); |
| }); |
| |
| |
| 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]; }); |
| |
| |
| const shuffled = shuffleArray([...cloudWords]); |
| |
| |
| 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); |
| |
| |
| 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; |
| } |
| }); |
| |
| |
| wordsData = [...placedWords]; |
| |
| |
| canvasSection.classList.remove('hidden'); |
| canvasSection.classList.add('fade-in'); |
| statsSection.classList.remove('hidden'); |
| statsSection.classList.add('fade-in'); |
| |
| |
| 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'); |
| |
| |
| 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); |
| }); |
| |
| |
| 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); |
| |
| ctx.clearRect(0, 0, cw, ch); |
| |
| |
| 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 = |