@mhmttosun/dnd 0.0.0-stage → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mhmttosun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,328 @@
1
- # Temporary Holding Version
1
+ # HTML Tabanlı Görsel Sürükle-Bırak Etkinlik Editörü
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Öğretmenlerin kod yazmadan etkileşimli eğitim etkinlikleri tasarlayabildiği, görsel cevap anahtarı oluşturabildiği, anında önizleme yapabildiği ve **tek tıkla tamamen bağımsız, çevrimdışı çalışan tek parça HTML etkinlikleri üretebildiği** profesyonel açık kaynak web platformudur.
4
+
5
+ [![Lisans: MIT](https://img.shields.io/badge/Lisans-MIT-blue.svg)](LICENSE)
6
+ ![Testler](https://img.shields.io/badge/Testler-27%2F27%20Ge%C3%A7ti%20(%25100)-success.svg)
7
+ ![Sıfır Bağımlılık](https://img.shields.io/badge/Frameworks-Pure%20Vanilla%20JS-emerald.svg)
8
+ ![Canvas Kullanımı](https://img.shields.io/badge/HTML%20Canvas-YOK%20(%25100%20DOM%2FSVG)-blue.svg)
9
+ ![Çevrimdışı Çalışma](https://img.shields.io/badge/Offline-Haz%C4%B1r-orange.svg)
10
+
11
+ ---
12
+
13
+ ## 🌟 Temel Özellikler
14
+
15
+ * **Canva Benzeri Görsel Tasarım Deneyimi:** Nesneleri serbestçe sürükleme, 8 tutamaçla boyutlandırma, döndürme, katman sıralaması, hizalama kılavuzları ve ızgara desteği.
16
+ * **Kesinlikle HTML Canvas Kullanmaz:** Bütün nesneler DOM hiyerarşisi, CSS Transforms (`translate`, `scale`, `rotate`) ve gerektiğinde SVG ile yönetilir.
17
+ * **Kontekstüel Mini Araç Çubuğu (Mini Toolbar):**
18
+ * Nesne seçildiğinde veya seçim alanı (marquee) bırakıldığında doğrudan nesnenin üzerinde beliren dinamik araç çubuğu.
19
+ * **Metin Ayarları:** Yazı tipi, boyutu, kalınlık, yatay ve dikey hizalama, 14 renkli açılır palet.
20
+ * **Nesne Görünümü:** Kompakt kart tasarımı, açılır renk seçimi, `+` ve `−` butonlarıyla hassas çizgi kalınlığı ve köşe yuvarlama (border-radius) ayarları.
21
+ * **Katman ve Kilit Menüsü:** Minimalist tasarımda *Öne*, *En öne*, *Arkaya*, *En arkaya* ve *Kilit* seçenekleri.
22
+ * **Kilitli Nesne Desteği:** Tuval üzerinden kilitli nesne seçildiğinde tek tıkla **"Kilidi Aç"** aksiyonu.
23
+ * **Sahne Rolü:** *Taşınan*, *Hedef* ve *Arka Plan* rolleri arasında hızlı geçiş.
24
+ * **Hizalama ve Boyutlandırma:** Sola, ortaya, sağa, üste, merkeze, alta hizalama ile çoklu seçimde *En Kısa* ve *En Uzun* genişliğe eşitleme.
25
+ * **Çizgi Nesnesi ve Özel Uç Tutamaçları:** Yalnızca başlangıç ve bitiş noktalarında beliren minimalist tutamaçlarla ok ve çizgi oluşturma.
26
+ * **Görsel Cevap Anahtarı Sistemi:** Kod veya ID yazmaya gerek olmadan, sürükle-bırak nesnesine ve hedef alana tıklayarak karşılıklı kenarlardan bağlanan dinamik SVG çizgileriyle eşleştirme.
27
+ * **Tek Parça Bağımsız HTML Dışa Aktarma:** Dışa aktarılan etkinlik; harici kütüphane, CDN veya internet bağlantısı gerektirmez. Dosyaya çift tıklanarak akıllı tahta, tablet veya bilgisayarlarda doğrudan çalışır.
28
+ * **Pointer Events API:** Masaüstü fare, tablet ve dokunmatik ekranlı mobil telefonlarda kusursuz sürükleme performansı.
29
+ * **Web Audio API Entegre Ses Sentezleyici:** Harici ses dosyalarına ihtiyaç duymadan, tarayıcı içinde dinamik doğru/yanlış melodileri ve kutlama fanfarı üretir.
30
+ * **Klavye ile Erişilebilirlik ve Kısayollar:** Geri Al (`Ctrl+Z`), İleri Al (`Ctrl+Y`), Kopyala/Yapıştır (`Ctrl+C`/`Ctrl+V`), Gruplama (`Ctrl+G`), Grubu Çözme (`Ctrl+Shift+G`), Çift tıklama ile yerinde metin düzenleme (inline editing).
31
+
32
+ ---
33
+
34
+ ## 🎯 Desteklenen 4 Etkinlik Türü
35
+
36
+ | Etkinlik Türü | Açıklama | Cevap Anahtarı Mantığı |
37
+ | :--- | :--- | :--- |
38
+ | **1. Görsel Üzerinde Etiketleme** | Ana görsel (örn: İnsan Anatomisi) üzerindeki hedef noktalara doğru etiketleri yerleştirme. | Sürüklenebilir Nesne ID ➔ Hedef ID (1'e 1) |
39
+ | **2. Sürükle-Bırak Eşleştirme** | İki sütun halindeki nesne veya kavramları (örn: Hayvanlar ve Doğal Yaşam Alanları) eşleştirme. | Kaynak Nesne ID ➔ Hedef Nesne ID (1'e 1) |
40
+ | **3. Kategorilere Ayırma** | Nesneleri (örn: Meyveler ve Sebzeler) ilgili kategori kutularına yerleştirme. | Nesne ID ➔ Kategori ID (Çoklu kabul) |
41
+ | **4. Sürükleyerek Sıralama** | Süreç adımlarını (örn: Kelebeğin Yaşam Döngüsü) doğru sıraya dizme. | Nesne ID ➔ Sıra Numarası / Slot ID |
42
+
43
+ ---
44
+
45
+ ## 🏗️ Modüler Yazılım Mimarisi
46
+
47
+ ```
48
+ dnd/
49
+ ├── index.html # Ana görsel etkinlik editörü
50
+ ├── README.md # Proje dokümantasyonu ve kullanım kılavuzu
51
+ ├── LICENSE # MIT Açık Kaynak Lisansı
52
+ ├── package.json # NPM betikleri ve paket metaverileri
53
+ ├── .gitignore # Sürüm kontrolünden hariç tutulan dosyalar
54
+ ├── css/
55
+ │ ├── editor.css # Editör arayüzü, mini toolbar ve açılır popup kartları
56
+ │ ├── stage.css # 1200x675 tuval, 8 boyutlandırma tutamacı, çizgi tutamaçları
57
+ │ ├── answer-key.css # Cevap anahtarı modu SVG bağlantı çizgileri ve vurgular
58
+ │ └── player.css # Önizleme ve dışa aktarılan etkinlik oynatıcısının stilleri
59
+ ├── js/
60
+ │ ├── core/
61
+ │ │ ├── EditorCore.js # Merkezi veri modeli (Single Source of Truth) ve durum yönetimi
62
+ │ │ ├── StageManager.js # 1200x675 mantıksal koordinat sistemi, ölçekleme ve ızgara
63
+ │ │ ├── ObjectManager.js # Nesne CRUD, z-index ve DOM oluşturma işlemleri
64
+ │ │ ├── SelectionManager.js# Tekli/çoklu seçim (Marquee), kopyala/yapıştır, kısayollar
65
+ │ │ ├── HistoryManager.js # Sınırsız Geri Al (Ctrl+Z) ve İleri Al (Ctrl+Y) yığını
66
+ │ │ ├── LayerManager.js # Katman yönetimi (Öne Getir, Arkaya Gönder)
67
+ │ │ └── AnswerKeyManager.js# Görsel SVG eşleştirme ve cevap anahtarı yönetimi
68
+ │ ├── engine/
69
+ │ │ ├── TransformEngine.js # Pointer Events ile taşıma, boyutlandırma ve döndürme
70
+ │ │ └── ValidationEngine.js# JSON şema ve oynanabilirlik doğrulaması, XSS sanitizasyonu
71
+ │ ├── player/
72
+ │ │ ├── ActivityEngine.js # Gerçek zamanlı öğrenci oyun motoru (Preview ve Export için)
73
+ │ │ └── ExportManager.js # Tek parça, bağımsız HTML dosyası derleyici
74
+ │ ├── utils/
75
+ │ │ └── AudioSynthesizer.js# Web Audio API ile sıfır harici dosya ses motoru
76
+ │ ├── ui/
77
+ │ │ ├── MiniToolbar.js # Kontekstüel yüzen mini araç çubuğu ve popup kontrolleri
78
+ │ │ ├── PropertyPanel.js # Seçili nesne ve oyun ayarları paneli
79
+ │ │ └── UIManager.js # Araç çubukları, menüler ve kullanıcı bildirimleri
80
+ │ ├── data/
81
+ │ │ └── SampleProjects.js # 4 etkinlik türü için zengin, hazır şablonlar
82
+ │ └── app.js # Uygulama başlatıcı ve modül bağlayıcı
83
+ ├── samples/ # Hazır etkinliklerin bağımsız HTML ve JSON dosyaları
84
+ │ ├── 1_organlari_etiketleme.html / .json
85
+ │ ├── 2_hayvanlar_eslestirme.html / .json
86
+ │ ├── 2_kavram_tanim_eslestirme.html / .json
87
+ │ ├── 3_meyveler_sebzeler_kategori.html / .json
88
+ │ └── 4_kelebek_yasam_dongusu_siralama.html / .json
89
+ └── tests/
90
+ └── run_tests.py # 27 aşamalı otomatik kabul ve entegrasyon test paketi
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 📐 Mantıksal Koordinat Sistemi ve Ölçekleme
96
+
97
+ Editör, **1200 x 675 piksel (16:9 sabit en-boy oranı)** mantıksal koordinat sistemi üzerinde çalışır:
98
+ * Tüm nesne konumları `(x, y)` ve boyutları `(width, height)` bu mantıksal sistemde saklanır.
99
+ * Ekran boyutu veya tarayıcı yakınlaştırması değiştiğinde `CSS transform: scale(...)` kullanılarak orantılı ölçekleme yapılır.
100
+ * Pointer koordinatları aşağıdaki formülle mantıksal sahne koordinatına dönüştürülür:
101
+ $$\text{stageX} = \frac{\text{clientX} - \text{stageRect.left}}{\text{scale}}$$
102
+ $$\text{stageY} = \frac{\text{clientY} - \text{stageRect.top}}{\text{scale}}$$
103
+ * Bu sayede mobilde veya 4K ekranda açıldığında hedefler ve nesneler milimetrik olarak aynı yerde kalır.
104
+
105
+ ---
106
+
107
+ ## 🚀 Kurulum ve Çalıştırma
108
+
109
+ Proje hiçbir backend, derleyici (build tool) veya harici npm paketi bağımlılığı gerektirmez.
110
+
111
+ ### 1. Yöntem: Doğrudan Çift Tıklama (En Hızlı)
112
+ Herhangi bir kurulum yapmadan `index.html` dosyasına çift tıklayarak tarayıcınızda açabilirsiniz.
113
+
114
+ ### 2. Yöntem: Yerel Sunucu ile (Tavsiye Edilen)
115
+ Terminalden proje klasöründe yerel bir HTTP sunucu başlatabilirsiniz:
116
+ ```bash
117
+ # Python ile:
118
+ python3 -m http.server 8000
119
+
120
+ # veya NPM betiği ile:
121
+ npm start
122
+ ```
123
+ Ardından tarayıcınızdan `http://localhost:8000` adresine gidin.
124
+
125
+ ---
126
+
127
+ ## 📦 Başka JS Projelerine Entegrasyon (Kütüphane Kullanımı)
128
+
129
+ Bu proje hem görsel bir editör hem de diğer JavaScript projelerine (React, Vue, Angular, Svelte, Next.js veya düz Vanilla JS) **kolayca eklenebilen bağımsız bir etkinlik oynatıcısı (Activity Player)** olarak tasarlanmıştır.
130
+
131
+ ### Editörü doğrudan gömme
132
+
133
+ Editör iframe gerektirmeden bir DOM elemanına bağlanabilir. İç arayüz Shadow DOM
134
+ içinde tutulur; görsel yükleme, bekleyen işlerin tamamlanması ve statik SVG
135
+ önizleme üretimi host uygulamaya açık bir API üzerinden yürütülür.
136
+
137
+ ```js
138
+ import { initializeDndEditor, createDefaultActivity } from '@mhmttosun/dnd/editor';
139
+
140
+ const editor = initializeDndEditor({
141
+ container: document.querySelector('#editor'),
142
+ activity: createDefaultActivity('matching', 'Eşleştirme'),
143
+ uploadImage: async (file, { signal }) => {
144
+ const url = await uploadToYourStorage(file, { signal });
145
+ return { url };
146
+ },
147
+ ui: { fileActions: false },
148
+ });
149
+
150
+ await editor.flush();
151
+ const activity = editor.getActivity();
152
+ const previewSvg = await editor.exportPreview();
153
+ editor.destroy();
154
+ ```
155
+
156
+ `ActivityPlayer` önceki bir denemeyi `initialState` ile geri yükleyebilir; güncel
157
+ durum `getState()` ile alınır ve gerekirse `setState()` ile değiştirilebilir.
158
+
159
+ Gömülü arayüzlerde sahneyi kapsayıcı kenarlarına kadar ölçeklemek için
160
+ `stagePadding: 0` verilebilir. Seçenek belirtilmezse bağımsız oynatıcı mevcut
161
+ duyarlı 16/32 px boşluğunu korur.
162
+
163
+ ### 1. Kurulum (NPM ile)
164
+ ```bash
165
+ npm install @mhmttosun/dnd
166
+ ```
167
+
168
+ ---
169
+
170
+ ### 2. Kullanım Senaryoları
171
+
172
+ #### A. W3C Standart Web Component (`<dnd-player>`)
173
+ Hiçbir framework bağımlılığı olmadan, HTML'e doğrudan standart etiket olarak eklenir:
174
+
175
+ ```html
176
+ <!-- CSS ve Script dahil edilir -->
177
+ <link rel="stylesheet" href="node_modules/@mhmttosun/dnd/dist/player.css">
178
+ <script src="node_modules/@mhmttosun/dnd/dist/dnd-player-element.js"></script>
179
+
180
+ <!-- Standart HTML Etiketi -->
181
+ <dnd-player
182
+ src="/path/to/activity.json"
183
+ sound="true"
184
+ style="height: 600px;">
185
+ </dnd-player>
186
+
187
+ <script>
188
+ const playerEl = document.querySelector('dnd-player');
189
+ playerEl.addEventListener('dnd:complete', (e) => {
190
+ console.log('Etkinlik Bitti! Skor:', e.detail.score, 'Deneme:', e.detail.attempts);
191
+ });
192
+ </script>
193
+ ```
194
+
195
+ #### B. React Entegrasyonu (React Component & Lifecycle)
196
+ ```jsx
197
+ import React, { useEffect, useRef } from 'react';
198
+ import { ActivityPlayer } from '@mhmttosun/dnd/player';
199
+ import '@mhmttosun/dnd/player.css';
200
+
201
+ export function ActivityView({ activityData, onFinished }) {
202
+ const containerRef = useRef(null);
203
+
204
+ useEffect(() => {
205
+ // 1. Oynatıcıyı Başlat (Mount)
206
+ const player = new ActivityPlayer({
207
+ target: containerRef.current,
208
+ data: activityData,
209
+ sound: true,
210
+ onComplete: (stats) => onFinished(stats)
211
+ });
212
+
213
+ // 2. Bellek Temizliği (Unmount Destroy)
214
+ return () => player.destroy();
215
+ }, [activityData]);
216
+
217
+ return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />;
218
+ }
219
+ ```
220
+
221
+ #### C. Vue 3 (Composition API)
222
+ ```vue
223
+ <template>
224
+ <div ref="container" style="width: 100%; height: 600px;"></div>
225
+ </template>
226
+
227
+ <script setup>
228
+ import { ref, onMounted, onUnmounted } from 'vue';
229
+ import { ActivityPlayer } from '@mhmttosun/dnd/player';
230
+ import '@mhmttosun/dnd/player.css';
231
+
232
+ const props = defineProps({ activityData: Object });
233
+ const emit = defineEmits(['complete']);
234
+ const container = ref(null);
235
+ let player = null;
236
+
237
+ onMounted(() => {
238
+ player = new ActivityPlayer({
239
+ target: container.value,
240
+ data: props.activityData,
241
+ onComplete: (stats) => emit('complete', stats)
242
+ });
243
+ });
244
+
245
+ onUnmounted(() => {
246
+ if (player) player.destroy();
247
+ });
248
+ </script>
249
+ ```
250
+
251
+ #### D. Vanilla JS (Düz Web Sayfası)
252
+ ```javascript
253
+ import { ActivityPlayer } from '@mhmttosun/dnd/player';
254
+ import '@mhmttosun/dnd/player.css';
255
+
256
+ const player = new ActivityPlayer({
257
+ target: '#my-container',
258
+ src: 'samples/2_hayvanlar_eslestirme.json',
259
+ sound: true,
260
+ onComplete: (stats) => alert(`Tebrikler! Puan: %${stats.score}`)
261
+ });
262
+
263
+ // İstenildiğinde:
264
+ // player.reset();
265
+ // player.destroy();
266
+ ```
267
+
268
+ #### E. Editörün Başka Uygulamalara Gömülmesi (Embedded Editor) & Canlı JSON Export
269
+
270
+ Editör bir CMS, LMS (Moodle, Canvas vb.), öğretmen yönetim paneli veya React/Vue uygulamasının içerisine `<iframe>` veya bileşen olarak gömüldüğünde:
271
+ * **Üst Başlık (Header) Otomatik Gizlenir:** Sayfa bir `<iframe>` içinde açıldığında veya URL'e `?embedded=true` (veya `?header=false`) eklendiğinde editörün üst başlık çubuğu (`.app-top-header`) gizlenir ve çalışma alanı tüm ekranı kaplar.
272
+ * **Sahnenin JSON Verisini Kolayca Alma:** Host uygulama tek satırda güncel sahne JSON'ını alabilir.
273
+
274
+ ```html
275
+ <!-- Host Uygulama (LMS / Portal / React) -->
276
+ <iframe id="dnd-editor" src="/dnd/index.html?embedded=true" style="width: 100%; height: 750px; border: none;"></iframe>
277
+
278
+ <script>
279
+ const editorFrame = document.getElementById('dnd-editor');
280
+
281
+ // 1. YÖNTEM: Doğrudan JavaScript Kısayolu (Same-Origin)
282
+ function saveFromEditor() {
283
+ // Tek satırda güncel sahnenin JSON nesnesi:
284
+ const projectData = editorFrame.contentWindow.dndExport();
285
+ console.log('Sahne JSON:', projectData);
286
+ return projectData;
287
+ }
288
+
289
+ // 2. YÖNTEM: postMessage API (Cross-Origin / Güvenli İletişim)
290
+ function requestEditorData() {
291
+ editorFrame.contentWindow.postMessage({ type: 'DND_GET_DATA' }, '*');
292
+ }
293
+
294
+ // Editörden gelen yanıtları ve anlık değişiklikleri dinleme:
295
+ window.addEventListener('message', (event) => {
296
+ // İstek üzerine dönen JSON
297
+ if (event.data.type === 'DND_DATA_RESPONSE') {
298
+ console.log('Alınan Proje JSON:', event.data.data);
299
+ }
300
+ // Sahnede kullanıcı nesne taşıdığında / düzenlediğinde canlı fırlatılan bildirim
301
+ if (event.data.type === 'DND_DATA_CHANGED') {
302
+ console.log('Canlı Değişiklik:', event.data.data);
303
+ }
304
+ });
305
+
306
+ // İstenirse ebeveynden yeni proje verisi yükleme:
307
+ // editorFrame.contentWindow.postMessage({ type: 'DND_LOAD_DATA', data: newProjectJson }, '*');
308
+ </script>
309
+ ```
310
+
311
+ ---
312
+
313
+ ## 🧪 Otomatik Testleri Çalıştırma
314
+
315
+ Proje; mimari bütünlüğü, Canvas kullanılmama kuralını, matematiksel koordinat dönüşümlerini, mini araç çubuğunun tüm popup ve stepper özelliklerini doğrulayan 27 aşamalı test paketine sahiptir.
316
+
317
+ Testleri çalıştırmak için:
318
+ ```bash
319
+ python3 tests/run_tests.py
320
+ # veya
321
+ npm test
322
+ ```
323
+
324
+ ---
325
+
326
+ ## 📄 Lisans
327
+
328
+ Bu proje [MIT Lisansı](LICENSE) kapsamında açık kaynak olarak sunulmaktadır.
@@ -0,0 +1,200 @@
1
+ /* ==========================================================================
2
+ ANSWER KEY CSS - Cevap Anahtarı Modu Görsel Eşleştirme Stilleri
3
+ ========================================================================== */
4
+
5
+ /* Cevap Anahtarı Modunda Nesneler */
6
+ .mode-answer-key .stage-object {
7
+ cursor: default;
8
+ }
9
+
10
+ .mode-answer-key .stage-object[data-role="draggable"] {
11
+ cursor: pointer !important;
12
+ }
13
+
14
+ /* Seçilen Kaynak Sürüklenebilir Öğe (Sadece seçili nesne vurgulanır, konumu korunur) */
15
+ .mode-answer-key .stage-object.ak-source-active {
16
+ outline: 3px solid #6366f1 !important;
17
+ outline-offset: 3px !important;
18
+ box-shadow: 0 0 0 2px #ffffff, 0 8px 24px rgba(99, 102, 241, 0.35) !important;
19
+ z-index: 1000 !important;
20
+ cursor: pointer !important;
21
+ }
22
+
23
+ /* ==========================================================================
24
+ Hedef Seçim Aşaması: Sürüklenecek nesne seçildikten sonra
25
+ Sadece sahne rolü "Hedef" olan nesneler tıklanabilir olmalıdır.
26
+ ========================================================================== */
27
+ .mode-answer-key.ak-selecting-target .stage-object {
28
+ cursor: default;
29
+ pointer-events: none; /* Hedef olmayan (dekoratif vb.) nesneler tıklanamaz */
30
+ }
31
+
32
+ /* Kaynak nesnenin kendisi tıklanarak seçim iptal edilebilir */
33
+ .mode-answer-key.ak-selecting-target .stage-object.ak-source-active {
34
+ cursor: pointer !important;
35
+ pointer-events: auto !important;
36
+ }
37
+
38
+ /* Başka bir sürüklenebilir nesneye tıklanarak kaynak değiştirilebilir */
39
+ .mode-answer-key.ak-selecting-target .stage-object[data-role="draggable"] {
40
+ cursor: pointer !important;
41
+ pointer-events: auto !important;
42
+ }
43
+
44
+ /* Sahne rolü "Hedef" olan nesneler tıklanabilir */
45
+ .mode-answer-key.ak-selecting-target .stage-object[data-role="target"],
46
+ .mode-answer-key.ak-selecting-target .stage-object[data-role="category"],
47
+ .mode-answer-key.ak-selecting-target .stage-object[data-role="sequence_slot"] {
48
+ cursor: pointer !important;
49
+ pointer-events: auto !important;
50
+ }
51
+
52
+ /* SVG Bağlantı Çizgileri */
53
+ .ak-connection-group {
54
+ cursor: pointer;
55
+ pointer-events: auto;
56
+ }
57
+
58
+ .ak-connection-hitbox {
59
+ fill: none;
60
+ stroke: transparent;
61
+ stroke-width: 24;
62
+ pointer-events: stroke;
63
+ }
64
+
65
+ .ak-connection-path {
66
+ fill: none;
67
+ stroke: #6366f1;
68
+ stroke-width: 3.5;
69
+ stroke-linecap: round;
70
+ stroke-dasharray: 6 4;
71
+ animation: dashAnimation 1s linear infinite;
72
+ filter: drop-shadow(0 2px 4px rgba(99, 102, 241, 0.3));
73
+ }
74
+
75
+ @keyframes dashAnimation {
76
+ to {
77
+ stroke-dashoffset: -20;
78
+ }
79
+ }
80
+
81
+ .ak-node-circle {
82
+ fill: #4f46e5;
83
+ stroke: #ffffff;
84
+ stroke-width: 2;
85
+ }
86
+
87
+ .ak-node-circle.start {
88
+ fill: #ec4899;
89
+ }
90
+
91
+ .ak-node-circle.end {
92
+ fill: #10b981;
93
+ }
94
+
95
+ /* Bağlantı Kaldırma (Unlink) Butonu */
96
+ .ak-unlink-btn {
97
+ width: 26px;
98
+ height: 26px;
99
+ border-radius: 50%;
100
+ background: #ef4444;
101
+ color: #ffffff;
102
+ border: 2px solid #ffffff;
103
+ font-size: 12px;
104
+ font-weight: 800;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ cursor: pointer;
109
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
110
+ transition: transform 0.15s ease, background 0.15s ease;
111
+ pointer-events: auto;
112
+ padding: 0;
113
+ margin: 0;
114
+ }
115
+
116
+ .ak-unlink-btn:hover {
117
+ background: #dc2626;
118
+ transform: scale(1.2);
119
+ }
120
+
121
+ /* ==========================================================================
122
+ Cevap Anahtarı Toolbar İçi Rehber Bloğu (Answer Key Toolbar Guide)
123
+ Toolbar yüksekliğini değiştirmeden sol mod kümesinden sonra dikey ayraçla yerleşir
124
+ ========================================================================== */
125
+ .answer-key-toolbar-guide {
126
+ display: flex;
127
+ align-items: center;
128
+ height: 100%;
129
+ user-select: none;
130
+ animation: akFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
131
+ }
132
+
133
+ @keyframes akFadeIn {
134
+ from {
135
+ opacity: 0;
136
+ transform: translateX(-4px);
137
+ }
138
+ to {
139
+ opacity: 1;
140
+ transform: translateX(0);
141
+ }
142
+ }
143
+
144
+ .ak-toolbar-divider {
145
+ width: 1px;
146
+ height: 24px;
147
+ background-color: var(--border-color, #e2e8f0);
148
+ margin: 0 14px 0 10px;
149
+ flex-shrink: 0;
150
+ }
151
+
152
+ .ak-toolbar-content {
153
+ display: flex;
154
+ flex-direction: column;
155
+ justify-content: center;
156
+ gap: 1px;
157
+ }
158
+
159
+ .ak-toolbar-title-row {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 6px;
163
+ line-height: 1.15;
164
+ }
165
+
166
+ .ak-toolbar-status-icon {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+ flex-shrink: 0;
171
+ }
172
+
173
+ .ak-guide-title {
174
+ font-size: 0.88rem;
175
+ font-weight: 700;
176
+ color: #0f172a;
177
+ letter-spacing: -0.01em;
178
+ white-space: nowrap;
179
+ }
180
+
181
+ .ak-guide-instruction {
182
+ font-size: 0.76rem;
183
+ font-weight: 500;
184
+ color: #64748b;
185
+ line-height: 1.15;
186
+ white-space: nowrap;
187
+ }
188
+
189
+ /* Cevap Anahtarı Modunda Orta (Nesne Ekleme), Sağ (Geçmiş & Zoom) ve Şablon Kümelerini Gizle */
190
+ .app-sub-toolbar.mode-answer-key .cluster-add-objects,
191
+ .app-sub-toolbar.mode-answer-key .cluster-history-zoom,
192
+ .app-sub-toolbar.mode-answer-key .template-dropdown-wrapper,
193
+ body.mode-answer-key .app-sub-toolbar .cluster-add-objects,
194
+ body.mode-answer-key .app-sub-toolbar .cluster-history-zoom,
195
+ body.mode-answer-key .app-sub-toolbar .template-dropdown-wrapper,
196
+ .dnd-editor-root.mode-answer-key .app-sub-toolbar .cluster-add-objects,
197
+ .dnd-editor-root.mode-answer-key .app-sub-toolbar .cluster-history-zoom,
198
+ .dnd-editor-root.mode-answer-key .app-sub-toolbar .template-dropdown-wrapper {
199
+ display: none !important;
200
+ }