soiz1 commited on
Commit
3fd2d7c
·
verified ·
1 Parent(s): 3247276

Update dev-tools.js

Browse files
Files changed (1) hide show
  1. dev-tools.js +449 -668
dev-tools.js CHANGED
@@ -1,134 +1,101 @@
1
  (function() {
2
  // グローバル変数宣言
3
- let contextMenu = null;
4
- let selectedElement = null;
5
- let selectedDOMNode = null;
6
- let activeEditElement = null;
7
- let networkRequests = [];
8
- let selectedRequest = null;
9
- let vitalsData = {
10
- CLS: null,
11
- FCP: null,
12
- FID: null,
13
- LCP: null,
14
- TTFB: null
15
- };
16
- let observer = null;
17
- let refreshElementsPanel = null;
18
-
19
- // Web Vitalsスクリプトを動的に読み込み
20
- const loadWebVitals = () => {
21
- return new Promise((resolve) => {
22
- if (window.webVitals) {
23
- resolve();
24
- return;
25
  }
26
-
27
- const webVitalsScript = document.createElement('script');
28
- webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
29
- webVitalsScript.onload = resolve;
30
- document.head.appendChild(webVitalsScript);
 
 
31
  });
32
  };
33
 
34
- // スタイルの動的追加 (ダークテーマ)
35
  const style = document.createElement('style');
36
  style.textContent = `
37
- :root {
38
- --bg-color: #1e1e1e;
39
- --panel-bg: #252526;
40
- --border-color: #3c3c3c;
41
- --text-color: #e0e0e0;
42
- --text-muted: #a0a0a0;
43
- --primary-color: #007acc;
44
- --primary-hover: #3e9fda;
45
- --success-color: #4caf50;
46
- --error-color: #f44336;
47
- --warning-color: #ff9800;
48
- --info-color: #2196f3;
49
- --highlight-bg: rgba(0, 122, 204, 0.2);
50
- --tab-bg: #2d2d2d;
51
- --tab-active-bg: #1e1e1e;
52
- --console-log-color: #e0e0e0;
53
- --console-error-color: #f44336;
54
- --console-warn-color: #ff9800;
55
- --console-info-color: #4fc3f7;
56
- --json-key: #9cdcfe;
57
- --json-string: #ce9178;
58
- --json-number: #b5cea8;
59
- --json-boolean: #569cd6;
60
- --json-null: #569cd6;
61
- --dom-tag: #569cd6;
62
- --dom-attr: #9cdcfe;
63
- --dom-text: #d4d4d4;
64
- }
65
-
66
- .devtools-container {
67
  position: fixed;
68
  bottom: 0;
69
  left: 0;
70
  width: 100%;
71
  height: 300px;
72
- background-color: var(--panel-bg);
73
- border-top: 1px solid var(--border-color);
74
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
75
  z-index: 9999;
76
  display: flex;
77
  flex-direction: column;
78
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
79
- color: var(--text-color);
80
  }
81
 
82
- .devtools-header {
83
  display: flex;
84
  justify-content: space-between;
85
  align-items: center;
86
  padding: 5px 10px;
87
- background-color: var(--tab-bg);
88
- border-bottom: 1px solid var(--border-color);
89
  }
90
 
91
- .devtools-tabs {
92
  display: flex;
93
  gap: 5px;
94
  }
95
 
96
- .devtools-tab {
97
  padding: 5px 10px;
98
  cursor: pointer;
99
  border-radius: 3px 3px 0 0;
100
- background-color: var(--tab-bg);
101
- border: 1px solid var(--border-color);
102
  border-bottom: none;
103
  font-size: 12px;
104
- color: var(--text-muted);
105
  }
106
 
107
- .devtools-tab.active {
108
- background-color: var(--tab-active-bg);
109
- color: var(--text-color);
110
- border-bottom: 1px solid var(--tab-active-bg);
111
  margin-bottom: -1px;
112
  font-weight: bold;
113
  }
114
 
115
- .devtools-close {
116
  background: none;
117
  border: none;
118
  font-size: 16px;
119
  cursor: pointer;
120
  padding: 0 5px;
121
- color: var(--text-color);
122
  }
123
 
124
- .devtools-content {
125
  flex: 1;
126
  overflow: auto;
127
  position: relative;
128
- background-color: var(--panel-bg);
129
  }
130
 
131
- .devtools-panel {
132
  position: absolute;
133
  top: 0;
134
  left: 0;
@@ -137,45 +104,45 @@
137
  padding: 10px;
138
  overflow: auto;
139
  display: none;
140
- background-color: var(--panel-bg);
141
  }
142
 
143
- .devtools-panel.active {
144
  display: block;
145
  }
146
 
147
  /* Console スタイル */
148
- #console-log {
149
  white-space: pre-wrap;
150
  margin: 0;
151
  line-height: 1.4;
152
  flex: 1;
153
- color: var(--console-log-color);
154
  font-family: 'Consolas', 'Monaco', monospace;
155
  font-size: 13px;
156
  }
157
 
158
- .console-log {
159
- color: var(--console-log-color);
160
  }
161
 
162
- .console-error {
163
- color: var(--console-error-color);
164
  }
165
 
166
- .console-warn {
167
- color: var(--console-warn-color);
168
  }
169
 
170
- .console-info {
171
- color: var(--console-info-color);
172
  }
173
 
174
- .console-input {
175
  width: calc(100% - 16px);
176
- background: var(--tab-bg);
177
- border: 1px solid var(--border-color);
178
- color: var(--text-color);
179
  padding: 8px;
180
  margin-top: 10px;
181
  font-family: monospace;
@@ -183,117 +150,117 @@
183
  }
184
 
185
  /* Elements スタイル */
186
- .elements-container {
187
  display: flex;
188
  flex: 1;
189
  overflow: hidden;
190
  }
191
 
192
- .dom-tree {
193
  font-family: 'Consolas', 'Monaco', monospace;
194
  flex: 1;
195
  overflow: auto;
196
- border-right: 1px solid var(--border-color);
197
  padding-right: 10px;
198
- color: var(--dom-text);
199
  font-size: 13px;
200
  }
201
 
202
- .dom-node {
203
  margin-left: 15px;
204
  position: relative;
205
  line-height: 1.4;
206
  transition: background-color 0.3s;
207
  }
208
 
209
- .dom-node.selected {
210
- background: var(--highlight-bg);
211
  }
212
 
213
- .dom-node.highlight {
214
- animation: highlight-fade 1.5s;
215
  }
216
 
217
- @keyframes highlight-fade {
218
  0% { background-color: rgba(79, 195, 247, 0.5); }
219
  100% { background-color: transparent; }
220
  }
221
 
222
- .dom-tag {
223
- color: var(--dom-tag);
224
  font-weight: bold;
225
  }
226
 
227
- .dom-attr {
228
- color: var(--dom-attr);
229
  }
230
 
231
- .dom-attr.editable:hover {
232
  text-decoration: underline;
233
  cursor: pointer;
234
  }
235
 
236
- .dom-text {
237
- color: var(--dom-text);
238
  }
239
 
240
- .dom-edit-input {
241
- background: var(--panel-bg);
242
- border: 1px solid var(--primary-color);
243
  padding: 0 2px;
244
  margin: -1px 0;
245
  font-family: monospace;
246
  min-width: 50px;
247
- color: var(--text-color);
248
  }
249
 
250
- .css-panel {
251
  flex: 1;
252
  overflow: auto;
253
  padding-left: 10px;
254
  font-size: 13px;
255
  }
256
 
257
- .css-rule {
258
  margin-bottom: 15px;
259
- border: 1px solid var(--border-color);
260
  padding: 8px;
261
- background-color: var(--tab-bg);
262
  border-radius: 3px;
263
  }
264
 
265
- .css-selector {
266
- color: var(--primary-color);
267
  margin-bottom: 5px;
268
  font-weight: bold;
269
  }
270
 
271
- .css-property {
272
  display: flex;
273
  margin-bottom: 3px;
274
  }
275
 
276
- .css-property-name {
277
- color: var(--dom-attr);
278
  min-width: 120px;
279
  }
280
 
281
- .css-property-value {
282
- color: var(--dom-text);
283
  flex: 1;
284
  }
285
 
286
- .css-toggle {
287
  margin-left: 10px;
288
- color: var(--error-color);
289
  cursor: pointer;
290
  }
291
 
292
  /* Context Menu */
293
- .context-menu {
294
  position: absolute;
295
- background: var(--panel-bg);
296
- border: 1px solid var(--border-color);
297
  z-index: 10000;
298
  min-width: 200px;
299
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
@@ -302,43 +269,43 @@
302
  overflow: hidden;
303
  }
304
 
305
- .context-menu-item {
306
  padding: 8px 15px;
307
  cursor: pointer;
308
- color: var(--text-color);
309
  font-size: 13px;
310
  }
311
 
312
- .context-menu-item:hover {
313
- background: var(--primary-color);
314
  color: #000;
315
  }
316
 
317
  /* Storage スタイル */
318
- .storage-table {
319
  width: 100%;
320
  border-collapse: collapse;
321
  margin-bottom: 10px;
322
  font-size: 13px;
323
  }
324
 
325
- .storage-table th, .storage-table td {
326
- border: 1px solid var(--border-color);
327
  padding: 5px;
328
  text-align: left;
329
  }
330
 
331
- .storage-table th {
332
- background: var(--tab-bg);
333
  }
334
 
335
- .storage-actions {
336
  display: flex;
337
  gap: 5px;
338
  }
339
 
340
- .storage-btn {
341
- background: var(--primary-color);
342
  border: none;
343
  padding: 2px 5px;
344
  cursor: pointer;
@@ -347,18 +314,18 @@
347
  font-size: 12px;
348
  }
349
 
350
- .editable {
351
  cursor: pointer;
352
  padding: 2px 5px;
353
  border: 1px dashed transparent;
354
  }
355
 
356
- .editable:hover {
357
- border-color: var(--primary-color);
358
  }
359
 
360
- .add-btn {
361
- background: var(--primary-color);
362
  color: #000;
363
  border: none;
364
  padding: 5px 10px;
@@ -368,47 +335,47 @@
368
  font-size: 13px;
369
  }
370
 
371
- .add-btn:hover {
372
- background: var(--primary-hover);
373
  }
374
 
375
  /* Network スタイル */
376
- .network-container {
377
  display: flex;
378
  height: 100%;
379
  overflow: hidden;
380
  }
381
 
382
- .network-requests {
383
  width: 40%;
384
  overflow-y: auto;
385
- border-right: 1px solid var(--border-color);
386
  font-size: 13px;
387
  }
388
 
389
- .network-details {
390
  width: 60%;
391
  overflow-y: auto;
392
  padding-left: 10px;
393
  }
394
 
395
- .network-request {
396
  padding: 8px;
397
- border-bottom: 1px solid var(--border-color);
398
  cursor: pointer;
399
  display: flex;
400
  align-items: center;
401
  }
402
 
403
- .network-request:hover {
404
  background-color: rgba(0, 122, 204, 0.1);
405
  }
406
 
407
- .network-request.selected {
408
- background-color: var(--highlight-bg);
409
  }
410
 
411
- .network-status {
412
  width: 20px;
413
  height: 20px;
414
  border-radius: 50%;
@@ -419,51 +386,51 @@
419
  flex-shrink: 0;
420
  }
421
 
422
- .network-status.success {
423
- background-color: var(--success-color);
424
  color: white;
425
  }
426
 
427
- .network-status.error {
428
- background-color: var(--error-color);
429
  color: white;
430
  }
431
 
432
- .network-method {
433
  font-weight: bold;
434
  margin-right: 8px;
435
- color: var(--primary-color);
436
  min-width: 40px;
437
  }
438
 
439
- .network-url {
440
  flex: 1;
441
  white-space: nowrap;
442
  overflow: hidden;
443
  text-overflow: ellipsis;
444
  }
445
 
446
- .network-time {
447
- color: var(--text-muted);
448
  font-size: 11px;
449
  margin-left: 8px;
450
  }
451
 
452
- .network-detail-section {
453
  margin-bottom: 15px;
454
  }
455
 
456
- .network-detail-title {
457
  font-weight: bold;
458
  margin-bottom: 5px;
459
- color: var(--primary-color);
460
  }
461
 
462
- .network-detail-content {
463
- background: var(--tab-bg);
464
  padding: 8px;
465
  border-radius: 3px;
466
- border: 1px solid var(--border-color);
467
  font-family: monospace;
468
  white-space: pre-wrap;
469
  font-size: 12px;
@@ -471,183 +438,107 @@
471
  overflow-y: auto;
472
  }
473
 
474
- /* Web Vitals スタイル */
475
- .vitals-container {
476
- display: flex;
477
- flex-direction: column;
478
- gap: 15px;
479
- }
480
-
481
- .vital-card {
482
- background: var(--tab-bg);
483
- border: 1px solid var(--border-color);
484
- border-radius: 5px;
485
- padding: 15px;
486
- }
487
-
488
- .vital-title {
489
- font-weight: bold;
490
- margin-bottom: 10px;
491
- color: var(--primary-color);
492
- display: flex;
493
- justify-content: space-between;
494
- align-items: center;
495
- }
496
-
497
- .vital-value {
498
- font-size: 24px;
499
- font-weight: bold;
500
- margin: 10px 0;
501
- }
502
-
503
- .vital-good {
504
- color: var(--success-color);
505
- }
506
-
507
- .vital-needs-improvement {
508
- color: var(--warning-color);
509
- }
510
-
511
- .vital-poor {
512
- color: var(--error-color);
513
- }
514
-
515
- .vital-description {
516
- font-size: 13px;
517
- color: var(--text-muted);
518
- }
519
-
520
- .vital-thresholds {
521
- display: flex;
522
- margin-top: 10px;
523
- font-size: 12px;
524
- }
525
-
526
- .vital-threshold {
527
- flex: 1;
528
- text-align: center;
529
- padding: 5px;
530
- border-radius: 3px;
531
- }
532
-
533
- .vital-threshold.active {
534
- background: rgba(0, 122, 204, 0.2);
535
- }
536
-
537
  /* DOM Tree Toggle */
538
- .dom-toggle {
539
  position: absolute;
540
  left: -12px;
541
  top: 2px;
542
  width: 10px;
543
  height: 10px;
544
  cursor: pointer;
545
- background-color: var(--text-muted);
546
  clip-path: polygon(0 0, 100% 50%, 0 100%);
547
  transition: transform 0.2s;
548
  }
549
 
550
- .dom-toggle.collapsed {
551
  transform: rotate(-90deg);
552
  }
553
 
554
- .dom-children {
555
  overflow: hidden;
556
  transition: max-height 0.3s ease-out;
557
  }
558
 
559
  /* JSON スタイル */
560
- .json-key {
561
- color: var(--json-key);
 
 
 
 
562
  }
563
 
564
- .json-string {
565
- color: var(--json-string);
566
  }
567
 
568
- .json-number {
569
- color: var(--json-number);
570
  }
571
 
572
- .json-boolean {
573
- color: var(--json-boolean);
574
  }
575
 
576
- .json-null {
577
- color: var(--json-null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  }
579
  `;
580
  document.head.appendChild(style);
581
 
582
- // DOM変更を監視するMutationObserver
583
- const setupMutationObserver = () => {
584
- observer = new MutationObserver((mutations) => {
585
- mutations.forEach((mutation) => {
586
- if (mutation.type === 'childList') {
587
- mutation.addedNodes.forEach((node) => {
588
- if (node.nodeType === Node.ELEMENT_NODE) {
589
- highlightNode(node);
590
- }
591
- });
592
- }
593
- });
594
- if (refreshElementsPanel) {
595
- refreshElementsPanel();
596
- }
597
- });
598
-
599
- observer.observe(document.documentElement, {
600
- childList: true,
601
- subtree: true,
602
- attributes: true,
603
- characterData: true
604
- });
605
- };
606
-
607
- // ノードをハイライト表示
608
- function highlightNode(node) {
609
- const elementId = node.id || Math.random().toString(36).substr(2, 9);
610
- node.setAttribute('data-element-id', elementId);
611
-
612
- setTimeout(() => {
613
- const domNode = document.querySelector(`[data-element-id="${elementId}"]`);
614
- if (domNode) {
615
- domNode.classList.add('highlight');
616
- setTimeout(() => {
617
- domNode.classList.remove('highlight');
618
- }, 1500);
619
- }
620
- }, 100);
621
- }
622
-
623
  // 開発者ツールのメイン関数
624
  const createDevTools = () => {
625
  const container = document.createElement('div');
626
- container.className = 'devtools-container';
627
- container.id = 'devtools-container';
628
  container.style.display = 'none';
629
 
630
  // ヘッダー部分
631
  const header = document.createElement('div');
632
- header.className = 'devtools-header';
633
 
634
  const tabs = document.createElement('div');
635
- tabs.className = 'devtools-tabs';
636
 
637
  const consoleTab = createTab('Console', 'console');
638
  const elementsTab = createTab('Elements', 'elements');
639
  const networkTab = createTab('Network', 'network');
640
  const storageTab = createTab('Storage', 'storage');
641
- const vitalsTab = createTab('Web Vitals', 'vitals');
642
 
643
  tabs.appendChild(consoleTab);
644
  tabs.appendChild(elementsTab);
645
  tabs.appendChild(networkTab);
646
  tabs.appendChild(storageTab);
647
- tabs.appendChild(vitalsTab);
648
 
649
  const closeBtn = document.createElement('button');
650
- closeBtn.className = 'devtools-close';
651
  closeBtn.textContent = '×';
652
  closeBtn.onclick = toggleDevTools;
653
 
@@ -656,19 +547,17 @@
656
 
657
  // コンテンツ部分
658
  const content = document.createElement('div');
659
- content.className = 'devtools-content';
660
 
661
  const consolePanel = createConsolePanel();
662
  const elementsPanel = createElementsPanel();
663
  const networkPanel = createNetworkPanel();
664
  const storagePanel = createStoragePanel();
665
- const vitalsPanel = createVitalsPanel();
666
 
667
  content.appendChild(consolePanel);
668
  content.appendChild(elementsPanel);
669
  content.appendChild(networkPanel);
670
  content.appendChild(storagePanel);
671
- content.appendChild(vitalsPanel);
672
 
673
  container.appendChild(header);
674
  container.appendChild(content);
@@ -681,11 +570,11 @@
681
  // タブ切り替え機能
682
  function createTab(name, panelId) {
683
  const tab = document.createElement('div');
684
- tab.className = 'devtools-tab';
685
  tab.textContent = name;
686
  tab.onclick = () => {
687
- document.querySelectorAll('.devtools-tab').forEach(t => t.classList.remove('active'));
688
- document.querySelectorAll('.devtools-panel').forEach(p => p.classList.remove('active'));
689
  tab.classList.add('active');
690
  document.getElementById(panelId + '-panel').classList.add('active');
691
 
@@ -699,188 +588,24 @@
699
  elementsTab.click();
700
  };
701
 
702
- // Web Vitalsパネル���成
703
- function createVitalsPanel() {
704
- const panel = document.createElement('div');
705
- panel.className = 'devtools-panel';
706
- panel.id = 'vitals-panel';
707
-
708
- const container = document.createElement('div');
709
- container.className = 'vitals-container';
710
- panel.appendChild(container);
711
-
712
- // CLS (Cumulative Layout Shift)
713
- const clsCard = document.createElement('div');
714
- clsCard.className = 'vital-card';
715
- clsCard.innerHTML = `
716
- <div class="vital-title">CLS (Cumulative Layout Shift) <span class="vital-description">視覚的な安定性</span></div>
717
- <div class="vital-value" id="cls-value">-</div>
718
- <div class="vital-thresholds">
719
- <div class="vital-threshold">Good: &lt; 0.1</div>
720
- <div class="vital-threshold">Needs Improvement: &lt; 0.25</div>
721
- <div class="vital-threshold">Poor: ≥ 0.25</div>
722
- </div>
723
- `;
724
- container.appendChild(clsCard);
725
-
726
- // FCP (First Contentful Paint)
727
- const fcpCard = document.createElement('div');
728
- fcpCard.className = 'vital-card';
729
- fcpCard.innerHTML = `
730
- <div class="vital-title">FCP (First Contentful Paint) <span class="vital-description">最初のコンテンツ表示</span></div>
731
- <div class="vital-value" id="fcp-value">-</div>
732
- <div class="vital-thresholds">
733
- <div class="vital-threshold">Good: &lt; 1.8s</div>
734
- <div class="vital-threshold">Needs Improvement: &lt; 3s</div>
735
- <div class="vital-threshold">Poor: ≥ 3s</div>
736
- </div>
737
- `;
738
- container.appendChild(fcpCard);
739
-
740
- // FID (First Input Delay)
741
- const fidCard = document.createElement('div');
742
- fidCard.className = 'vital-card';
743
- fidCard.innerHTML = `
744
- <div class="vital-title">FID (First Input Delay) <span class="vital-description">最初の入力遅延</span></div>
745
- <div class="vital-value" id="fid-value">-</div>
746
- <div class="vital-thresholds">
747
- <div class="vital-threshold">Good: &lt; 100ms</div>
748
- <div class="vital-threshold">Needs Improvement: &lt; 300ms</div>
749
- <div class="vital-threshold">Poor: ≥ 300ms</div>
750
- </div>
751
- `;
752
- container.appendChild(fidCard);
753
-
754
- // LCP (Largest Contentful Paint)
755
- const lcpCard = document.createElement('div');
756
- lcpCard.className = 'vital-card';
757
- lcpCard.innerHTML = `
758
- <div class="vital-title">LCP (Largest Contentful Paint) <span class="vital-description">最大のコンテンツ表示</span></div>
759
- <div class="vital-value" id="lcp-value">-</div>
760
- <div class="vital-thresholds">
761
- <div class="vital-threshold">Good: &lt; 2.5s</div>
762
- <div class="vital-threshold">Needs Improvement: &lt; 4s</div>
763
- <div class="vital-threshold">Poor: ≥ 4s</div>
764
- </div>
765
- `;
766
- container.appendChild(lcpCard);
767
-
768
- // TTFB (Time to First Byte)
769
- const ttfbCard = document.createElement('div');
770
- ttfbCard.className = 'vital-card';
771
- ttfbCard.innerHTML = `
772
- <div class="vital-title">TTFB (Time to First Byte) <span class="vital-description">最初のバイト到達時間</span></div>
773
- <div class="vital-value" id="ttfb-value">-</div>
774
- <div class="vital-thresholds">
775
- <div class="vital-threshold">Good: &lt; 800ms</div>
776
- <div class="vital-threshold">Needs Improvement: &lt; 1.8s</div>
777
- <div class="vital-threshold">Poor: ≥ 1.8s</div>
778
- </div>
779
- `;
780
- container.appendChild(ttfbCard);
781
-
782
- // Web Vitalsの計測を開始
783
- loadWebVitals().then(() => {
784
- if (window.webVitals) {
785
- try {
786
- const vitals = window.webVitals;
787
- if (vitals.getCLS) {
788
- vitals.getCLS(updateCLS);
789
- vitals.getFCP(updateFCP);
790
- vitals.getFID(updateFID);
791
- vitals.getLCP(updateLCP);
792
- vitals.getTTFB(updateTTFB);
793
- } else if (vitals.onCLS) {
794
- vitals.onCLS(updateCLS);
795
- vitals.onFCP(updateFCP);
796
- vitals.onFID(updateFID);
797
- vitals.onLCP(updateLCP);
798
- vitals.onTTFB(updateTTFB);
799
- }
800
- } catch (e) {
801
- console.error('Web Vitals error:', e);
802
- }
803
- }
804
- });
805
-
806
- function updateCLS(metric) {
807
- vitalsData.CLS = metric.value;
808
- updateVitalDisplay('cls', metric.value, 0.1, 0.25);
809
- }
810
-
811
- function updateFCP(metric) {
812
- vitalsData.FCP = metric.value;
813
- updateVitalDisplay('fcp', metric.value, 1800, 3000);
814
- }
815
-
816
- function updateFID(metric) {
817
- vitalsData.FID = metric.value;
818
- updateVitalDisplay('fid', metric.value, 100, 300);
819
- }
820
-
821
- function updateLCP(metric) {
822
- vitalsData.LCP = metric.value;
823
- updateVitalDisplay('lcp', metric.value, 2500, 4000);
824
- }
825
-
826
- function updateTTFB(metric) {
827
- vitalsData.TTFB = metric.value;
828
- updateVitalDisplay('ttfb', metric.value, 800, 1800);
829
- }
830
-
831
- function updateVitalDisplay(id, value, goodThreshold, needsImprovementThreshold) {
832
- const element = document.getElementById(`${id}-value`);
833
- if (!element) return;
834
-
835
- const displayValue = id === 'ttfb' ?
836
- `${Math.round(value)}ms` :
837
- `${value.toFixed(2)}${id === 'cls' ? '' : 's'}`;
838
-
839
- element.textContent = displayValue;
840
-
841
- element.className = 'vital-value';
842
- if (value <= goodThreshold) {
843
- element.classList.add('vital-good');
844
- } else if (value <= needsImprovementThreshold) {
845
- element.classList.add('vital-needs-improvement');
846
- } else {
847
- element.classList.add('vital-poor');
848
- }
849
-
850
- const thresholds = element.parentElement.querySelectorAll('.vital-threshold');
851
- thresholds.forEach((threshold, index) => {
852
- threshold.classList.remove('active');
853
- if (
854
- (index === 0 && value <= goodThreshold) ||
855
- (index === 1 && value > goodThreshold && value <= needsImprovementThreshold) ||
856
- (index === 2 && value > needsImprovementThreshold)
857
- ) {
858
- threshold.classList.add('active');
859
- }
860
- });
861
- }
862
-
863
- return panel;
864
- }
865
-
866
  // ネットワークパネル作成
867
  function createNetworkPanel() {
868
  const panel = document.createElement('div');
869
- panel.className = 'devtools-panel';
870
  panel.id = 'network-panel';
871
 
872
  const container = document.createElement('div');
873
- container.className = 'network-container';
874
  panel.appendChild(container);
875
 
876
  // リクエストリスト
877
  const requestsList = document.createElement('div');
878
- requestsList.className = 'network-requests';
879
  container.appendChild(requestsList);
880
 
881
  // 詳細パネル
882
  const detailsPanel = document.createElement('div');
883
- detailsPanel.className = 'network-details';
884
  container.appendChild(detailsPanel);
885
 
886
  // ネットワークリクエストを監視
@@ -912,7 +637,7 @@
912
  error: null
913
  };
914
 
915
- networkRequests.push(requestData);
916
  renderNetworkRequests();
917
 
918
  try {
@@ -989,7 +714,7 @@
989
  error: null
990
  };
991
 
992
- networkRequests.push(requestData);
993
  renderNetworkRequests();
994
 
995
  this.addEventListener('load', function() {
@@ -1041,38 +766,38 @@
1041
  const panel = document.getElementById('network-panel');
1042
  if (!panel || !panel.classList.contains('active')) return;
1043
 
1044
- const requestsList = panel.querySelector('.network-requests');
1045
- const detailsPanel = panel.querySelector('.network-details');
1046
 
1047
  requestsList.innerHTML = '';
1048
 
1049
- networkRequests.forEach(request => {
1050
  const requestElement = document.createElement('div');
1051
- requestElement.className = 'network-request';
1052
- if (selectedRequest && selectedRequest.id === request.id) {
1053
  requestElement.classList.add('selected');
1054
  }
1055
 
1056
  requestElement.onclick = () => {
1057
- selectedRequest = request;
1058
  renderNetworkRequests();
1059
  renderNetworkDetails();
1060
  };
1061
 
1062
  const statusElement = document.createElement('div');
1063
- statusElement.className = `network-status ${request.status}`;
1064
  statusElement.textContent = request.status === 'success' ? '✓' : '✕';
1065
 
1066
  const methodElement = document.createElement('div');
1067
- methodElement.className = 'network-method';
1068
  methodElement.textContent = request.method;
1069
 
1070
  const urlElement = document.createElement('div');
1071
- urlElement.className = 'network-url';
1072
  urlElement.textContent = request.url;
1073
 
1074
  const timeElement = document.createElement('div');
1075
- timeElement.className = 'network-time';
1076
  timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
1077
 
1078
  requestElement.appendChild(statusElement);
@@ -1087,43 +812,43 @@
1087
  // ネットワークリクエストの詳細を表示
1088
  function renderNetworkDetails() {
1089
  const panel = document.getElementById('network-panel');
1090
- if (!panel || !selectedRequest) return;
1091
 
1092
- const detailsPanel = panel.querySelector('.network-details');
1093
  detailsPanel.innerHTML = '';
1094
 
1095
  // 一般情報
1096
  const generalSection = document.createElement('div');
1097
- generalSection.className = 'network-detail-section';
1098
  generalSection.innerHTML = `
1099
- <div class="network-detail-title">一般</div>
1100
- <div class="network-detail-content">
1101
- <strong>URL:</strong> ${selectedRequest.url}<br>
1102
- <strong>メソッド:</strong> ${selectedRequest.method}<br>
1103
- <strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
1104
- <strong>時間:</strong> ${selectedRequest.duration ? Math.round(selectedRequest.duration) + 'ms' : '-'}
1105
  </div>
1106
  `;
1107
  detailsPanel.appendChild(generalSection);
1108
 
1109
  // リクエストヘッダー
1110
- if (selectedRequest.requestHeaders) {
1111
  const headersSection = document.createElement('div');
1112
- headersSection.className = 'network-detail-section';
1113
 
1114
  const headersTitle = document.createElement('div');
1115
- headersTitle.className = 'network-detail-title';
1116
  headersTitle.textContent = 'リクエストヘッダー';
1117
 
1118
  const headersContent = document.createElement('div');
1119
- headersContent.className = 'network-detail-content';
1120
 
1121
- if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
1122
- Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
1123
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
1124
  });
1125
- } else if (selectedRequest.requestHeaders instanceof Headers) {
1126
- selectedRequest.requestHeaders.forEach((value, key) => {
1127
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
1128
  });
1129
  }
@@ -1134,22 +859,22 @@
1134
  }
1135
 
1136
  // リクエストボディ
1137
- if (selectedRequest.requestBody) {
1138
  const bodySection = document.createElement('div');
1139
- bodySection.className = 'network-detail-section';
1140
 
1141
  const bodyTitle = document.createElement('div');
1142
- bodyTitle.className = 'network-detail-title';
1143
  bodyTitle.textContent = 'リクエストボディ';
1144
 
1145
  const bodyContent = document.createElement('div');
1146
- bodyContent.className = 'network-detail-content';
1147
 
1148
  try {
1149
- if (typeof selectedRequest.requestBody === 'string') {
1150
- bodyContent.textContent = selectedRequest.requestBody;
1151
- } else if (typeof selectedRequest.requestBody === 'object') {
1152
- bodyContent.textContent = JSON.stringify(selectedRequest.requestBody, null, 2);
1153
  }
1154
  } catch (e) {
1155
  bodyContent.textContent = 'ボディを表示できません';
@@ -1161,22 +886,22 @@
1161
  }
1162
 
1163
  // レスポンス
1164
- if (selectedRequest.response) {
1165
  const responseSection = document.createElement('div');
1166
- responseSection.className = 'network-detail-section';
1167
 
1168
  const responseTitle = document.createElement('div');
1169
- responseTitle.className = 'network-detail-title';
1170
  responseTitle.textContent = 'レスポンス';
1171
 
1172
  const responseContent = document.createElement('div');
1173
- responseContent.className = 'network-detail-content';
1174
 
1175
- if (selectedRequest.response.body) {
1176
- if (typeof selectedRequest.response.body === 'object') {
1177
- responseContent.textContent = JSON.stringify(selectedRequest.response.body, null, 2);
1178
  } else {
1179
- responseContent.textContent = selectedRequest.response.body;
1180
  }
1181
  } else {
1182
  responseContent.textContent = 'レスポンスボディがありません';
@@ -1188,20 +913,20 @@
1188
  }
1189
 
1190
  // エラー
1191
- if (selectedRequest.error) {
1192
  const errorSection = document.createElement('div');
1193
- errorSection.className = 'network-detail-section';
1194
 
1195
  const errorTitle = document.createElement('div');
1196
- errorTitle.className = 'network-detail-title';
1197
  errorTitle.textContent = 'エラー';
1198
 
1199
  const errorContent = document.createElement('div');
1200
- errorContent.className = 'network-detail-content';
1201
- errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
1202
 
1203
- if (selectedRequest.error.stack) {
1204
- errorContent.innerHTML += `<br><br>${selectedRequest.error.stack.replace(/\n/g, '<br>')}`;
1205
  }
1206
 
1207
  errorSection.appendChild(errorTitle);
@@ -1212,30 +937,31 @@
1212
 
1213
  // コンテキストメニュー作成
1214
  function createContextMenu() {
1215
- contextMenu = document.createElement('div');
1216
- contextMenu.className = 'context-menu';
1217
- contextMenu.innerHTML = `
1218
- <div class="context-menu-item" data-action="edit-html">HTMLとして編集</div>
1219
- <div class="context-menu-item" data-action="add-attribute">属性を追加</div>
1220
- <div class="context-menu-item" data-action="edit-element">要素を編集</div>
1221
- <div class="context-menu-item" data-action="duplicate">要素を複製</div>
1222
- <div class="context-menu-item" data-action="remove">要素を削除</div>
1223
- <div class="context-menu-item" data-action="toggle-visibility">要素を非表示</div>
1224
- <div class="context-menu-item" data-action="force-state">状態を強制</div>
 
1225
  `;
1226
- document.body.appendChild(contextMenu);
1227
 
1228
- contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
1229
  item.addEventListener('click', (e) => {
1230
  const action = e.target.getAttribute('data-action');
1231
  handleContextMenuAction(action);
1232
- contextMenu.style.display = 'none';
1233
  });
1234
  });
1235
 
1236
  document.addEventListener('click', (e) => {
1237
- if (e.target !== contextMenu && !contextMenu.contains(e.target)) {
1238
- contextMenu.style.display = 'none';
1239
  }
1240
  });
1241
  }
@@ -1243,7 +969,7 @@
1243
  // Storageパネル作成
1244
  function createStoragePanel() {
1245
  const panel = document.createElement('div');
1246
- panel.className = 'devtools-panel';
1247
  panel.id = 'storage-panel';
1248
 
1249
  // LocalStorage表示
@@ -1252,11 +978,11 @@
1252
  panel.appendChild(localStorageTitle);
1253
 
1254
  const localStorageTable = document.createElement('table');
1255
- localStorageTable.className = 'storage-table';
1256
  panel.appendChild(localStorageTable);
1257
 
1258
  const addLocalStorageBtn = document.createElement('button');
1259
- addLocalStorageBtn.className = 'add-btn';
1260
  addLocalStorageBtn.textContent = '+ Local Storageに追加';
1261
  addLocalStorageBtn.onclick = () => {
1262
  const key = prompt('キー名を入力');
@@ -1275,11 +1001,11 @@
1275
  panel.appendChild(sessionStorageTitle);
1276
 
1277
  const sessionStorageTable = document.createElement('table');
1278
- sessionStorageTable.className = 'storage-table';
1279
  panel.appendChild(sessionStorageTable);
1280
 
1281
  const addSessionStorageBtn = document.createElement('button');
1282
- addSessionStorageBtn.className = 'add-btn';
1283
  addSessionStorageBtn.textContent = '+ Session Storageに追加';
1284
  addSessionStorageBtn.onclick = () => {
1285
  const key = prompt('キー名を入力');
@@ -1298,11 +1024,11 @@
1298
  panel.appendChild(cookiesTitle);
1299
 
1300
  const cookiesTable = document.createElement('table');
1301
- cookiesTable.className = 'storage-table';
1302
  panel.appendChild(cookiesTable);
1303
 
1304
  const addCookieBtn = document.createElement('button');
1305
- addCookieBtn.className = 'add-btn';
1306
  addCookieBtn.textContent = '+ Cookieに追加';
1307
  addCookieBtn.onclick = () => {
1308
  const name = prompt('Cookie名を入力');
@@ -1343,7 +1069,7 @@
1343
 
1344
  const keyCell = document.createElement('td');
1345
  const keySpan = document.createElement('span');
1346
- keySpan.className = 'editable';
1347
  keySpan.textContent = key;
1348
  keySpan.onclick = () => {
1349
  const newKey = prompt('新しいキー名を入力', key);
@@ -1357,7 +1083,7 @@
1357
 
1358
  const valueCell = document.createElement('td');
1359
  const valueSpan = document.createElement('span');
1360
- valueSpan.className = 'editable';
1361
  valueSpan.textContent = value;
1362
  valueSpan.onclick = () => {
1363
  const newValue = prompt('新しい値を入力', value);
@@ -1369,10 +1095,10 @@
1369
  valueCell.appendChild(valueSpan);
1370
 
1371
  const actionsCell = document.createElement('td');
1372
- actionsCell.className = 'storage-actions';
1373
 
1374
  const deleteBtn = document.createElement('button');
1375
- deleteBtn.className = 'storage-btn';
1376
  deleteBtn.textContent = 'Delete';
1377
  deleteBtn.onclick = () => {
1378
  storage.removeItem(key);
@@ -1414,7 +1140,7 @@
1414
 
1415
  const nameCell = document.createElement('td');
1416
  const nameSpan = document.createElement('span');
1417
- nameSpan.className = 'editable';
1418
  nameSpan.textContent = decodedName;
1419
  nameSpan.onclick = () => {
1420
  const newName = prompt('新しい名前を入力', decodedName);
@@ -1431,7 +1157,7 @@
1431
 
1432
  const valueCell = document.createElement('td');
1433
  const valueSpan = document.createElement('span');
1434
- valueSpan.className = 'editable';
1435
  valueSpan.textContent = decodeURIComponent(value);
1436
  valueSpan.onclick = () => {
1437
  const newValue = prompt('新しい値を入力', decodeURIComponent(value));
@@ -1443,10 +1169,10 @@
1443
  valueCell.appendChild(valueSpan);
1444
 
1445
  const actionsCell = document.createElement('td');
1446
- actionsCell.className = 'storage-actions';
1447
 
1448
  const deleteBtn = document.createElement('button');
1449
- deleteBtn.className = 'storage-btn';
1450
  deleteBtn.textContent = 'Delete';
1451
  deleteBtn.onclick = () => {
1452
  document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
@@ -1471,69 +1197,120 @@
1471
 
1472
  // コンテキストメニューアクション処理
1473
  function handleContextMenuAction(action) {
1474
- if (!selectedElement) return;
1475
 
1476
  switch (action) {
1477
  case 'edit-html':
1478
- if (selectedElement === document.documentElement) {
1479
  alert('ルートHTML要素は直接編集できません');
1480
  return;
1481
  }
1482
- startInlineEdit(selectedDOMNode, selectedElement.outerHTML, (newValue) => {
1483
  try {
1484
- selectedElement.outerHTML = newValue;
1485
- refreshElementsPanel();
1486
  } catch (e) {
1487
  alert('この要素は編集できません: ' + e.message);
1488
  }
1489
  });
1490
  break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1491
  case 'add-attribute':
1492
  const attrName = prompt('属性名を入力');
1493
  if (attrName) {
1494
  const attrValue = prompt('属性値を入力');
1495
- selectedElement.setAttribute(attrName, attrValue || '');
1496
- refreshElementsPanel();
1497
  }
1498
  break;
1499
  case 'edit-element':
1500
- startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
1501
  const newElement = document.createElement(newValue);
1502
- Array.from(selectedElement.attributes).forEach(attr => {
1503
  newElement.setAttribute(attr.name, attr.value);
1504
  });
1505
- newElement.innerHTML = selectedElement.innerHTML;
1506
- selectedElement.parentNode.replaceChild(newElement, selectedElement);
1507
- selectedElement = newElement;
1508
- refreshElementsPanel();
1509
  });
1510
  break;
1511
  case 'duplicate':
1512
- const clone = selectedElement.cloneNode(true);
1513
- selectedElement.parentNode.insertBefore(clone, selectedElement.nextSibling);
1514
- refreshElementsPanel();
1515
  break;
1516
  case 'remove':
1517
  if (confirm('要素を削除しますか?')) {
1518
- selectedElement.parentNode.removeChild(selectedElement);
1519
- refreshElementsPanel();
1520
  }
1521
  break;
1522
  case 'toggle-visibility':
1523
- if (selectedElement.style.display === 'none') {
1524
- selectedElement.style.display = '';
1525
  } else {
1526
- selectedElement.style.display = 'none';
1527
  }
1528
- refreshElementsPanel();
1529
  break;
1530
  case 'force-state':
1531
  const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
1532
  if (state) {
1533
- selectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
1534
  'force-focus-within', 'force-focus-visible', 'force-target');
1535
- selectedElement.classList.add(`force-${state}`);
1536
- refreshElementsPanel();
1537
  }
1538
  break;
1539
  }
@@ -1541,13 +1318,13 @@
1541
 
1542
  // インライン編集関数
1543
  function startInlineEdit(element, initialValue, callback) {
1544
- if (activeEditElement) return;
1545
 
1546
  const originalValue = element.textContent;
1547
  const rect = element.getBoundingClientRect();
1548
 
1549
  const input = document.createElement('input');
1550
- input.className = 'dom-edit-input';
1551
  input.value = initialValue || originalValue;
1552
  input.style.position = 'absolute';
1553
  input.style.left = `${rect.left}px`;
@@ -1558,7 +1335,7 @@
1558
  input.focus();
1559
  input.select();
1560
 
1561
- activeEditElement = {
1562
  element: element,
1563
  input: input,
1564
  callback: callback
@@ -1601,32 +1378,32 @@
1601
  function cleanup() {
1602
  document.removeEventListener('click', clickOutsideHandler);
1603
  input.remove();
1604
- activeEditElement = null;
1605
  }
1606
  }
1607
 
1608
  // Consoleパネル作成
1609
  function createConsolePanel() {
1610
  const panel = document.createElement('div');
1611
- panel.className = 'devtools-panel';
1612
  panel.id = 'console-panel';
1613
 
1614
  const log = document.createElement('div');
1615
- log.id = 'console-log';
1616
 
1617
  const input = document.createElement('input');
1618
- input.className = 'console-input';
1619
  input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
1620
  input.onkeypress = (e) => {
1621
  if (e.key === 'Enter') {
1622
  try {
1623
  const result = eval(e.target.value);
1624
  if (result !== undefined) {
1625
- logMessage('> ' + e.target.value, 'console-log');
1626
- logMessage('← ' + formatOutput(result), 'console-log');
1627
  }
1628
  } catch (err) {
1629
- logMessage(err.message, 'console-error');
1630
  }
1631
  e.target.value = '';
1632
  }
@@ -1645,22 +1422,22 @@
1645
 
1646
  console.log = (...args) => {
1647
  originalConsole.log.apply(console, args);
1648
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-log');
1649
  };
1650
 
1651
  console.error = (...args) => {
1652
  originalConsole.error.apply(console, args);
1653
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-error');
1654
  };
1655
 
1656
  console.warn = (...args) => {
1657
  originalConsole.warn.apply(console, args);
1658
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-warn');
1659
  };
1660
 
1661
  console.info = (...args) => {
1662
  originalConsole.info.apply(console, args);
1663
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-info');
1664
  };
1665
 
1666
  function logMessage(message, className) {
@@ -1672,23 +1449,23 @@
1672
  }
1673
 
1674
  function formatOutput(output) {
1675
- if (output === null) return '<span class="json-null">null</span>';
1676
- if (output === undefined) return '<span class="json-null">undefined</span>';
1677
- if (typeof output === 'boolean') return `<span class="json-boolean">${output}</span>`;
1678
- if (typeof output === 'number') return `<span class="json-number">${output}</span>`;
1679
- if (typeof output === 'string') return `<span class="json-string">"${output}"</span>`;
1680
- if (typeof output === 'function') return `<span class="json-object">function ${output.name}() { ... }</span>`;
1681
- if (Array.isArray(output)) return `<span class="json-object">[${output.map(formatOutput).join(', ')}]</span>`;
1682
  if (typeof output === 'object') {
1683
  try {
1684
- return `<span class="json-object">${JSON.stringify(output, null, 2)
1685
- .replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
1686
- .replace(/"([^"]+)"/g, '<span class="json-string">"$1"</span>')
1687
- .replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>')
1688
- .replace(/\b(null)\b/g, '<span class="json-null">$1</span>')
1689
- .replace(/\b(\d+)\b/g, '<span class="json-number">$1</span>')}</span>`;
1690
  } catch (e) {
1691
- return `<span class="json-object">${output.toString()}</span>`;
1692
  }
1693
  }
1694
  return output;
@@ -1700,19 +1477,19 @@
1700
  // Elementsパネル作成
1701
  function createElementsPanel() {
1702
  const panel = document.createElement('div');
1703
- panel.className = 'devtools-panel';
1704
  panel.id = 'elements-panel';
1705
 
1706
  const container = document.createElement('div');
1707
- container.className = 'elements-container';
1708
 
1709
  const tree = document.createElement('div');
1710
- tree.className = 'dom-tree';
1711
- tree.id = 'dom-tree';
1712
 
1713
  const cssPanel = document.createElement('div');
1714
- cssPanel.className = 'css-panel';
1715
- cssPanel.id = 'css-panel';
1716
 
1717
  container.appendChild(tree);
1718
  container.appendChild(cssPanel);
@@ -1720,17 +1497,17 @@
1720
 
1721
  // CSSパネル更新関数
1722
  function updateCSSPanel(element) {
1723
- const cssPanel = document.getElementById('css-panel');
1724
  cssPanel.innerHTML = '';
1725
 
1726
  if (!element) return;
1727
 
1728
  if (element.style.length > 0) {
1729
  const inlineRule = document.createElement('div');
1730
- inlineRule.className = 'css-rule';
1731
 
1732
  const selector = document.createElement('div');
1733
- selector.className = 'css-selector';
1734
  selector.textContent = 'インラインスタイル';
1735
  inlineRule.appendChild(selector);
1736
 
@@ -1739,20 +1516,20 @@
1739
  const propValue = element.style[propName];
1740
 
1741
  const propDiv = document.createElement('div');
1742
- propDiv.className = 'css-property';
1743
 
1744
  const nameSpan = document.createElement('span');
1745
- nameSpan.className = 'css-property-name editable';
1746
  nameSpan.textContent = propName;
1747
  nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
1748
 
1749
  const valueSpan = document.createElement('span');
1750
- valueSpan.className = 'css-property-value editable';
1751
  valueSpan.textContent = propValue;
1752
  valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
1753
 
1754
  const toggleSpan = document.createElement('span');
1755
- toggleSpan.className = 'css-toggle';
1756
  toggleSpan.textContent = '×';
1757
  toggleSpan.title = 'プロパティを無効化';
1758
  toggleSpan.onclick = () => {
@@ -1771,10 +1548,10 @@
1771
 
1772
  const computedStyles = window.getComputedStyle(element);
1773
  const computedRule = document.createElement('div');
1774
- computedRule.className = 'css-rule';
1775
 
1776
  const computedSelector = document.createElement('div');
1777
- computedSelector.className = 'css-selector';
1778
  computedSelector.textContent = '計算されたスタイル';
1779
  computedRule.appendChild(computedSelector);
1780
 
@@ -1787,14 +1564,14 @@
1787
  const value = computedStyles[prop];
1788
 
1789
  const propDiv = document.createElement('div');
1790
- propDiv.className = 'css-property';
1791
 
1792
  const nameSpan = document.createElement('span');
1793
- nameSpan.className = 'css-property-name';
1794
  nameSpan.textContent = prop;
1795
 
1796
  const valueSpan = document.createElement('span');
1797
- valueSpan.className = 'css-property-value';
1798
  valueSpan.textContent = value;
1799
 
1800
  propDiv.appendChild(nameSpan);
@@ -1809,23 +1586,23 @@
1809
  function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
1810
  if (node.nodeType === Node.ELEMENT_NODE) {
1811
  const element = document.createElement('div');
1812
- element.className = 'dom-node';
1813
  element.style.marginLeft = `${depth * 15}px`;
1814
  element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
1815
 
1816
  // 右クリックイベント
1817
  element.oncontextmenu = (e) => {
1818
  e.preventDefault();
1819
- selectedElement = node;
1820
- selectedDOMNode = element;
1821
 
1822
- document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
1823
  element.classList.add('selected');
1824
 
1825
  if (node !== document.documentElement) {
1826
- contextMenu.style.display = 'block';
1827
- contextMenu.style.left = `${e.pageX}px`;
1828
- contextMenu.style.top = `${e.pageY}px`;
1829
  }
1830
 
1831
  updateCSSPanel(node);
@@ -1833,13 +1610,13 @@
1833
 
1834
  // 左クリックで選択
1835
  element.onclick = (e) => {
1836
- if (e.target.classList.contains('dom-toggle')) return;
1837
 
1838
  e.stopPropagation();
1839
- selectedElement = node;
1840
- selectedDOMNode = element;
1841
 
1842
- document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
1843
  element.classList.add('selected');
1844
 
1845
  updateCSSPanel(node);
@@ -1851,10 +1628,10 @@
1851
 
1852
  if (hasChildren) {
1853
  const toggle = document.createElement('div');
1854
- toggle.className = 'dom-toggle';
1855
  toggle.onclick = (e) => {
1856
  e.stopPropagation();
1857
- const children = element.querySelector('.dom-children');
1858
  if (children) {
1859
  if (children.style.maxHeight === '0px') {
1860
  children.style.maxHeight = children.scrollHeight + 'px';
@@ -1870,11 +1647,11 @@
1870
 
1871
  // タグ名(HTML要素は編集不可)
1872
  const tag = document.createElement('span');
1873
- tag.className = 'dom-tag';
1874
  tag.textContent = `<${node.tagName.toLowerCase()}`;
1875
 
1876
  if (node !== document.documentElement) {
1877
- tag.classList.add('editable');
1878
  tag.onclick = (e) => {
1879
  e.stopPropagation();
1880
  startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
@@ -1884,8 +1661,8 @@
1884
  });
1885
  newElement.innerHTML = node.innerHTML;
1886
  node.parentNode.replaceChild(newElement, node);
1887
- selectedElement = newElement;
1888
- refreshElementsPanel();
1889
  });
1890
  };
1891
  }
@@ -1895,16 +1672,16 @@
1895
  // 属性(HTML要素は編集不可)
1896
  Array.from(node.attributes).forEach(attr => {
1897
  const attrSpan = document.createElement('span');
1898
- attrSpan.className = 'dom-attr';
1899
  attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
1900
 
1901
  if (node !== document.documentElement) {
1902
- attrSpan.classList.add('editable');
1903
  attrSpan.onclick = (e) => {
1904
  e.stopPropagation();
1905
  startInlineEdit(attrSpan, attr.value, (newValue) => {
1906
  node.setAttribute(attr.name, newValue);
1907
- refreshElementsPanel();
1908
  });
1909
  };
1910
  }
@@ -1916,7 +1693,7 @@
1916
 
1917
  if (hasChildren) {
1918
  const childrenContainer = document.createElement('div');
1919
- childrenContainer.className = 'dom-children';
1920
  childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
1921
 
1922
  node.childNodes.forEach(child => {
@@ -1926,7 +1703,7 @@
1926
  if (node.tagName.toLowerCase() !== 'br') {
1927
  const closeTag = document.createElement('div');
1928
  closeTag.style.marginLeft = `${depth * 15}px`;
1929
- closeTag.innerHTML = `<span class="dom-tag">&lt;/${node.tagName.toLowerCase()}&gt;</span>`;
1930
  childrenContainer.appendChild(closeTag);
1931
  }
1932
 
@@ -1937,13 +1714,13 @@
1937
  } else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
1938
  const text = document.createElement('div');
1939
  text.style.marginLeft = `${depth * 15}px`;
1940
- text.className = 'dom-text editable';
1941
  text.textContent = `"${node.textContent.trim()}"`;
1942
  text.onclick = (e) => {
1943
  e.stopPropagation();
1944
  startInlineEdit(text, node.textContent.trim(), (newValue) => {
1945
  node.textContent = newValue;
1946
- refreshElementsPanel();
1947
  });
1948
  };
1949
  parentElement.appendChild(text);
@@ -1966,23 +1743,23 @@
1966
  }
1967
 
1968
  updateCSSPanel(element);
1969
- refreshElementsPanel();
1970
  }
1971
  }
1972
 
1973
  // refreshElementsPanel関数の定義
1974
- refreshElementsPanel = function() {
1975
- let tree = document.getElementById('dom-tree');
1976
 
1977
  if (!tree) {
1978
  const panel = document.getElementById('elements-panel');
1979
  if (panel) {
1980
- const container = panel.querySelector('.elements-container');
1981
  if (container) {
1982
  tree = document.createElement('div');
1983
- tree.className = 'dom-tree';
1984
- tree.id = 'dom-tree';
1985
- container.insertBefore(tree, container.querySelector('.css-panel'));
1986
  }
1987
  }
1988
  }
@@ -1992,15 +1769,15 @@
1992
  tree.innerHTML = '';
1993
  buildDOMTree(document.documentElement, tree, 0, true);
1994
 
1995
- if (selectedElement) {
1996
- const elementId = selectedElement.id || Array.from(selectedElement.attributes)
1997
  .find(attr => attr.name.startsWith('data-element-id'))?.value;
1998
 
1999
  if (elementId) {
2000
  const node = document.querySelector(`[data-element-id="${elementId}"]`);
2001
  if (node) {
2002
  node.classList.add('selected');
2003
- updateCSSPanel(selectedElement);
2004
  }
2005
  }
2006
  }
@@ -2011,7 +1788,7 @@
2011
 
2012
  // 初期表示
2013
  setTimeout(() => {
2014
- refreshElementsPanel();
2015
  }, 0);
2016
 
2017
  return panel;
@@ -2019,7 +1796,7 @@
2019
 
2020
  // 開発者ツール表示/非表示
2021
  function toggleDevTools() {
2022
- const container = document.getElementById('devtools-container');
2023
  if (container.style.display === 'none') {
2024
  container.style.display = 'flex';
2025
  } else {
@@ -2030,13 +1807,13 @@
2030
  // 開くボタン作成
2031
  function createOpenButton() {
2032
  const button = document.createElement('button');
2033
- button.id = 'open-devtools-btn';
2034
  button.textContent = '開発者ツールを開く';
2035
  button.style.position = 'fixed';
2036
  button.style.bottom = '10px';
2037
  button.style.right = '10px';
2038
  button.style.padding = '8px 16px';
2039
- button.style.background = 'var(--primary-color)';
2040
  button.style.color = '#000';
2041
  button.style.border = 'none';
2042
  button.style.borderRadius = '4px';
@@ -2049,9 +1826,13 @@
2049
 
2050
  // 初期化
2051
  document.addEventListener('DOMContentLoaded', function() {
2052
- createDevTools();
2053
  createOpenButton();
2054
- console.log('開発者ツールが初期化されました');
2055
- console.log('このコンソールでJavaScriptを実行できます');
 
 
 
 
2056
  });
2057
  })();
 
1
  (function() {
2
  // グローバル変数宣言
3
+ let dtContextMenu = null;
4
+ let dtSelectedElement = null;
5
+ let dtSelectedDOMNode = null;
6
+ let dtActiveEditElement = null;
7
+ let dtNetworkRequests = [];
8
+ let dtSelectedRequest = null;
9
+ let dtObserver = null;
10
+ let dtRefreshElementsPanel = null;
11
+
12
+ // DOM変更を監視するMutationObserver (最適化版)
13
+ const setupMutationObserver = () => {
14
+ if (dtObserver) dtObserver.disconnect();
15
+
16
+ dtObserver = new MutationObserver(() => {
17
+ if (dtRefreshElementsPanel) {
18
+ dtRefreshElementsPanel();
 
 
 
 
 
 
19
  }
20
+ });
21
+
22
+ dtObserver.observe(document.documentElement, {
23
+ childList: true,
24
+ subtree: true,
25
+ attributes: false,
26
+ characterData: false
27
  });
28
  };
29
 
30
+ // スタイルの追加 (すべてのクラスにdt-プレフィックス)
31
  const style = document.createElement('style');
32
  style.textContent = `
33
+ .dt-devtools-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  position: fixed;
35
  bottom: 0;
36
  left: 0;
37
  width: 100%;
38
  height: 300px;
39
+ background-color: var(--dt-bg-color);
40
+ border-top: 1px solid var(--dt-border-color);
41
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
42
  z-index: 9999;
43
  display: flex;
44
  flex-direction: column;
45
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
46
+ color: var(--dt-text-color);
47
  }
48
 
49
+ .dt-devtools-header {
50
  display: flex;
51
  justify-content: space-between;
52
  align-items: center;
53
  padding: 5px 10px;
54
+ background-color: var(--dt-tab-bg);
55
+ border-bottom: 1px solid var(--dt-border-color);
56
  }
57
 
58
+ .dt-devtools-tabs {
59
  display: flex;
60
  gap: 5px;
61
  }
62
 
63
+ .dt-devtools-tab {
64
  padding: 5px 10px;
65
  cursor: pointer;
66
  border-radius: 3px 3px 0 0;
67
+ background-color: var(--dt-tab-bg);
68
+ border: 1px solid var(--dt-border-color);
69
  border-bottom: none;
70
  font-size: 12px;
71
+ color: var(--dt-text-muted);
72
  }
73
 
74
+ .dt-devtools-tab.active {
75
+ background-color: var(--dt-tab-active-bg);
76
+ color: var(--dt-text-color);
77
+ border-bottom: 1px solid var(--dt-tab-active-bg);
78
  margin-bottom: -1px;
79
  font-weight: bold;
80
  }
81
 
82
+ .dt-devtools-close {
83
  background: none;
84
  border: none;
85
  font-size: 16px;
86
  cursor: pointer;
87
  padding: 0 5px;
88
+ color: var(--dt-text-color);
89
  }
90
 
91
+ .dt-devtools-content {
92
  flex: 1;
93
  overflow: auto;
94
  position: relative;
95
+ background-color: var(--dt-panel-bg);
96
  }
97
 
98
+ .dt-devtools-panel {
99
  position: absolute;
100
  top: 0;
101
  left: 0;
 
104
  padding: 10px;
105
  overflow: auto;
106
  display: none;
107
+ background-color: var(--dt-panel-bg);
108
  }
109
 
110
+ .dt-devtools-panel.active {
111
  display: block;
112
  }
113
 
114
  /* Console スタイル */
115
+ #dt-console-log {
116
  white-space: pre-wrap;
117
  margin: 0;
118
  line-height: 1.4;
119
  flex: 1;
120
+ color: var(--dt-console-log-color);
121
  font-family: 'Consolas', 'Monaco', monospace;
122
  font-size: 13px;
123
  }
124
 
125
+ .dt-console-log {
126
+ color: var(--dt-console-log-color);
127
  }
128
 
129
+ .dt-console-error {
130
+ color: var(--dt-console-error-color);
131
  }
132
 
133
+ .dt-console-warn {
134
+ color: var(--dt-console-warn-color);
135
  }
136
 
137
+ .dt-console-info {
138
+ color: var(--dt-console-info-color);
139
  }
140
 
141
+ .dt-console-input {
142
  width: calc(100% - 16px);
143
+ background: var(--dt-tab-bg);
144
+ border: 1px solid var(--dt-border-color);
145
+ color: var(--dt-text-color);
146
  padding: 8px;
147
  margin-top: 10px;
148
  font-family: monospace;
 
150
  }
151
 
152
  /* Elements スタイル */
153
+ .dt-elements-container {
154
  display: flex;
155
  flex: 1;
156
  overflow: hidden;
157
  }
158
 
159
+ .dt-dom-tree {
160
  font-family: 'Consolas', 'Monaco', monospace;
161
  flex: 1;
162
  overflow: auto;
163
+ border-right: 1px solid var(--dt-border-color);
164
  padding-right: 10px;
165
+ color: var(--dt-dom-text);
166
  font-size: 13px;
167
  }
168
 
169
+ .dt-dom-node {
170
  margin-left: 15px;
171
  position: relative;
172
  line-height: 1.4;
173
  transition: background-color 0.3s;
174
  }
175
 
176
+ .dt-dom-node.selected {
177
+ background: var(--dt-highlight-bg);
178
  }
179
 
180
+ .dt-dom-node.highlight {
181
+ animation: dt-highlight-fade 1.5s;
182
  }
183
 
184
+ @keyframes dt-highlight-fade {
185
  0% { background-color: rgba(79, 195, 247, 0.5); }
186
  100% { background-color: transparent; }
187
  }
188
 
189
+ .dt-dom-tag {
190
+ color: var(--dt-dom-tag);
191
  font-weight: bold;
192
  }
193
 
194
+ .dt-dom-attr {
195
+ color: var(--dt-dom-attr);
196
  }
197
 
198
+ .dt-dom-attr.editable:hover {
199
  text-decoration: underline;
200
  cursor: pointer;
201
  }
202
 
203
+ .dt-dom-text {
204
+ color: var(--dt-dom-text);
205
  }
206
 
207
+ .dt-dom-edit-input {
208
+ background: var(--dt-panel-bg);
209
+ border: 1px solid var(--dt-primary-color);
210
  padding: 0 2px;
211
  margin: -1px 0;
212
  font-family: monospace;
213
  min-width: 50px;
214
+ color: var(--dt-text-color);
215
  }
216
 
217
+ .dt-css-panel {
218
  flex: 1;
219
  overflow: auto;
220
  padding-left: 10px;
221
  font-size: 13px;
222
  }
223
 
224
+ .dt-css-rule {
225
  margin-bottom: 15px;
226
+ border: 1px solid var(--dt-border-color);
227
  padding: 8px;
228
+ background-color: var(--dt-tab-bg);
229
  border-radius: 3px;
230
  }
231
 
232
+ .dt-css-selector {
233
+ color: var(--dt-primary-color);
234
  margin-bottom: 5px;
235
  font-weight: bold;
236
  }
237
 
238
+ .dt-css-property {
239
  display: flex;
240
  margin-bottom: 3px;
241
  }
242
 
243
+ .dt-css-property-name {
244
+ color: var(--dt-dom-attr);
245
  min-width: 120px;
246
  }
247
 
248
+ .dt-css-property-value {
249
+ color: var(--dt-dom-text);
250
  flex: 1;
251
  }
252
 
253
+ .dt-css-toggle {
254
  margin-left: 10px;
255
+ color: var(--dt-error-color);
256
  cursor: pointer;
257
  }
258
 
259
  /* Context Menu */
260
+ .dt-context-menu {
261
  position: absolute;
262
+ background: var(--dt-panel-bg);
263
+ border: 1px solid var(--dt-border-color);
264
  z-index: 10000;
265
  min-width: 200px;
266
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
 
269
  overflow: hidden;
270
  }
271
 
272
+ .dt-context-menu-item {
273
  padding: 8px 15px;
274
  cursor: pointer;
275
+ color: var(--dt-text-color);
276
  font-size: 13px;
277
  }
278
 
279
+ .dt-context-menu-item:hover {
280
+ background: var(--dt-primary-color);
281
  color: #000;
282
  }
283
 
284
  /* Storage スタイル */
285
+ .dt-storage-table {
286
  width: 100%;
287
  border-collapse: collapse;
288
  margin-bottom: 10px;
289
  font-size: 13px;
290
  }
291
 
292
+ .dt-storage-table th, .dt-storage-table td {
293
+ border: 1px solid var(--dt-border-color);
294
  padding: 5px;
295
  text-align: left;
296
  }
297
 
298
+ .dt-storage-table th {
299
+ background: var(--dt-tab-bg);
300
  }
301
 
302
+ .dt-storage-actions {
303
  display: flex;
304
  gap: 5px;
305
  }
306
 
307
+ .dt-storage-btn {
308
+ background: var(--dt-primary-color);
309
  border: none;
310
  padding: 2px 5px;
311
  cursor: pointer;
 
314
  font-size: 12px;
315
  }
316
 
317
+ .dt-editable {
318
  cursor: pointer;
319
  padding: 2px 5px;
320
  border: 1px dashed transparent;
321
  }
322
 
323
+ .dt-editable:hover {
324
+ border-color: var(--dt-primary-color);
325
  }
326
 
327
+ .dt-add-btn {
328
+ background: var(--dt-primary-color);
329
  color: #000;
330
  border: none;
331
  padding: 5px 10px;
 
335
  font-size: 13px;
336
  }
337
 
338
+ .dt-add-btn:hover {
339
+ background: var(--dt-primary-hover);
340
  }
341
 
342
  /* Network スタイル */
343
+ .dt-network-container {
344
  display: flex;
345
  height: 100%;
346
  overflow: hidden;
347
  }
348
 
349
+ .dt-network-requests {
350
  width: 40%;
351
  overflow-y: auto;
352
+ border-right: 1px solid var(--dt-border-color);
353
  font-size: 13px;
354
  }
355
 
356
+ .dt-network-details {
357
  width: 60%;
358
  overflow-y: auto;
359
  padding-left: 10px;
360
  }
361
 
362
+ .dt-network-request {
363
  padding: 8px;
364
+ border-bottom: 1px solid var(--dt-border-color);
365
  cursor: pointer;
366
  display: flex;
367
  align-items: center;
368
  }
369
 
370
+ .dt-network-request:hover {
371
  background-color: rgba(0, 122, 204, 0.1);
372
  }
373
 
374
+ .dt-network-request.selected {
375
+ background-color: var(--dt-highlight-bg);
376
  }
377
 
378
+ .dt-network-status {
379
  width: 20px;
380
  height: 20px;
381
  border-radius: 50%;
 
386
  flex-shrink: 0;
387
  }
388
 
389
+ .dt-network-status.success {
390
+ background-color: var(--dt-success-color);
391
  color: white;
392
  }
393
 
394
+ .dt-network-status.error {
395
+ background-color: var(--dt-error-color);
396
  color: white;
397
  }
398
 
399
+ .dt-network-method {
400
  font-weight: bold;
401
  margin-right: 8px;
402
+ color: var(--dt-primary-color);
403
  min-width: 40px;
404
  }
405
 
406
+ .dt-network-url {
407
  flex: 1;
408
  white-space: nowrap;
409
  overflow: hidden;
410
  text-overflow: ellipsis;
411
  }
412
 
413
+ .dt-network-time {
414
+ color: var(--dt-text-muted);
415
  font-size: 11px;
416
  margin-left: 8px;
417
  }
418
 
419
+ .dt-network-detail-section {
420
  margin-bottom: 15px;
421
  }
422
 
423
+ .dt-network-detail-title {
424
  font-weight: bold;
425
  margin-bottom: 5px;
426
+ color: var(--dt-primary-color);
427
  }
428
 
429
+ .dt-network-detail-content {
430
+ background: var(--dt-tab-bg);
431
  padding: 8px;
432
  border-radius: 3px;
433
+ border: 1px solid var(--dt-border-color);
434
  font-family: monospace;
435
  white-space: pre-wrap;
436
  font-size: 12px;
 
438
  overflow-y: auto;
439
  }
440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  /* DOM Tree Toggle */
442
+ .dt-dom-toggle {
443
  position: absolute;
444
  left: -12px;
445
  top: 2px;
446
  width: 10px;
447
  height: 10px;
448
  cursor: pointer;
449
+ background-color: var(--dt-text-muted);
450
  clip-path: polygon(0 0, 100% 50%, 0 100%);
451
  transition: transform 0.2s;
452
  }
453
 
454
+ .dt-dom-toggle.collapsed {
455
  transform: rotate(-90deg);
456
  }
457
 
458
+ .dt-dom-children {
459
  overflow: hidden;
460
  transition: max-height 0.3s ease-out;
461
  }
462
 
463
  /* JSON スタイル */
464
+ .dt-json-key {
465
+ color: var(--dt-json-key);
466
+ }
467
+
468
+ .dt-json-string {
469
+ color: var(--dt-json-string);
470
  }
471
 
472
+ .dt-json-number {
473
+ color: var(--dt-json-number);
474
  }
475
 
476
+ .dt-json-boolean {
477
+ color: var(--dt-json-boolean);
478
  }
479
 
480
+ .dt-json-null {
481
+ color: var(--dt-json-null);
482
  }
483
 
484
+ /* CSS変数定義 */
485
+ :root {
486
+ --dt-bg-color: #1e1e1e;
487
+ --dt-panel-bg: #252526;
488
+ --dt-border-color: #3c3c3c;
489
+ --dt-text-color: #e0e0e0;
490
+ --dt-text-muted: #a0a0a0;
491
+ --dt-primary-color: #007acc;
492
+ --dt-primary-hover: #3e9fda;
493
+ --dt-success-color: #4caf50;
494
+ --dt-error-color: #f44336;
495
+ --dt-warning-color: #ff9800;
496
+ --dt-info-color: #2196f3;
497
+ --dt-highlight-bg: rgba(0, 122, 204, 0.2);
498
+ --dt-tab-bg: #2d2d2d;
499
+ --dt-tab-active-bg: #1e1e1e;
500
+ --dt-console-log-color: #e0e0e0;
501
+ --dt-console-error-color: #f44336;
502
+ --dt-console-warn-color: #ff9800;
503
+ --dt-console-info-color: #4fc3f7;
504
+ --dt-json-key: #9cdcfe;
505
+ --dt-json-string: #ce9178;
506
+ --dt-json-number: #b5cea8;
507
+ --dt-json-boolean: #569cd6;
508
+ --dt-json-null: #569cd6;
509
+ --dt-dom-tag: #569cd6;
510
+ --dt-dom-attr: #9cdcfe;
511
+ --dt-dom-text: #d4d4d4;
512
  }
513
  `;
514
  document.head.appendChild(style);
515
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  // 開発者ツールのメイン関数
517
  const createDevTools = () => {
518
  const container = document.createElement('div');
519
+ container.className = 'dt-devtools-container';
520
+ container.id = 'dt-devtools-container';
521
  container.style.display = 'none';
522
 
523
  // ヘッダー部分
524
  const header = document.createElement('div');
525
+ header.className = 'dt-devtools-header';
526
 
527
  const tabs = document.createElement('div');
528
+ tabs.className = 'dt-devtools-tabs';
529
 
530
  const consoleTab = createTab('Console', 'console');
531
  const elementsTab = createTab('Elements', 'elements');
532
  const networkTab = createTab('Network', 'network');
533
  const storageTab = createTab('Storage', 'storage');
 
534
 
535
  tabs.appendChild(consoleTab);
536
  tabs.appendChild(elementsTab);
537
  tabs.appendChild(networkTab);
538
  tabs.appendChild(storageTab);
 
539
 
540
  const closeBtn = document.createElement('button');
541
+ closeBtn.className = 'dt-devtools-close';
542
  closeBtn.textContent = '×';
543
  closeBtn.onclick = toggleDevTools;
544
 
 
547
 
548
  // コンテンツ部分
549
  const content = document.createElement('div');
550
+ content.className = 'dt-devtools-content';
551
 
552
  const consolePanel = createConsolePanel();
553
  const elementsPanel = createElementsPanel();
554
  const networkPanel = createNetworkPanel();
555
  const storagePanel = createStoragePanel();
 
556
 
557
  content.appendChild(consolePanel);
558
  content.appendChild(elementsPanel);
559
  content.appendChild(networkPanel);
560
  content.appendChild(storagePanel);
 
561
 
562
  container.appendChild(header);
563
  container.appendChild(content);
 
570
  // タブ切り替え機能
571
  function createTab(name, panelId) {
572
  const tab = document.createElement('div');
573
+ tab.className = 'dt-devtools-tab';
574
  tab.textContent = name;
575
  tab.onclick = () => {
576
+ document.querySelectorAll('.dt-devtools-tab').forEach(t => t.classList.remove('active'));
577
+ document.querySelectorAll('.dt-devtools-panel').forEach(p => p.classList.remove('active'));
578
  tab.classList.add('active');
579
  document.getElementById(panelId + '-panel').classList.add('active');
580
 
 
588
  elementsTab.click();
589
  };
590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  // ネットワークパネル作成
592
  function createNetworkPanel() {
593
  const panel = document.createElement('div');
594
+ panel.className = 'dt-devtools-panel';
595
  panel.id = 'network-panel';
596
 
597
  const container = document.createElement('div');
598
+ container.className = 'dt-network-container';
599
  panel.appendChild(container);
600
 
601
  // リクエストリスト
602
  const requestsList = document.createElement('div');
603
+ requestsList.className = 'dt-network-requests';
604
  container.appendChild(requestsList);
605
 
606
  // 詳細パネル
607
  const detailsPanel = document.createElement('div');
608
+ detailsPanel.className = 'dt-network-details';
609
  container.appendChild(detailsPanel);
610
 
611
  // ネットワークリクエストを監視
 
637
  error: null
638
  };
639
 
640
+ dtNetworkRequests.push(requestData);
641
  renderNetworkRequests();
642
 
643
  try {
 
714
  error: null
715
  };
716
 
717
+ dtNetworkRequests.push(requestData);
718
  renderNetworkRequests();
719
 
720
  this.addEventListener('load', function() {
 
766
  const panel = document.getElementById('network-panel');
767
  if (!panel || !panel.classList.contains('active')) return;
768
 
769
+ const requestsList = panel.querySelector('.dt-network-requests');
770
+ const detailsPanel = panel.querySelector('.dt-network-details');
771
 
772
  requestsList.innerHTML = '';
773
 
774
+ dtNetworkRequests.forEach(request => {
775
  const requestElement = document.createElement('div');
776
+ requestElement.className = 'dt-network-request';
777
+ if (dtSelectedRequest && dtSelectedRequest.id === request.id) {
778
  requestElement.classList.add('selected');
779
  }
780
 
781
  requestElement.onclick = () => {
782
+ dtSelectedRequest = request;
783
  renderNetworkRequests();
784
  renderNetworkDetails();
785
  };
786
 
787
  const statusElement = document.createElement('div');
788
+ statusElement.className = `dt-network-status ${request.status}`;
789
  statusElement.textContent = request.status === 'success' ? '✓' : '✕';
790
 
791
  const methodElement = document.createElement('div');
792
+ methodElement.className = 'dt-network-method';
793
  methodElement.textContent = request.method;
794
 
795
  const urlElement = document.createElement('div');
796
+ urlElement.className = 'dt-network-url';
797
  urlElement.textContent = request.url;
798
 
799
  const timeElement = document.createElement('div');
800
+ timeElement.className = 'dt-network-time';
801
  timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
802
 
803
  requestElement.appendChild(statusElement);
 
812
  // ネットワークリクエストの詳細を表示
813
  function renderNetworkDetails() {
814
  const panel = document.getElementById('network-panel');
815
+ if (!panel || !dtSelectedRequest) return;
816
 
817
+ const detailsPanel = panel.querySelector('.dt-network-details');
818
  detailsPanel.innerHTML = '';
819
 
820
  // 一般情報
821
  const generalSection = document.createElement('div');
822
+ generalSection.className = 'dt-network-detail-section';
823
  generalSection.innerHTML = `
824
+ <div class="dt-network-detail-title">一般</div>
825
+ <div class="dt-network-detail-content">
826
+ <strong>URL:</strong> ${dtSelectedRequest.url}<br>
827
+ <strong>メソッド:</strong> ${dtSelectedRequest.method}<br>
828
+ <strong>ステータス:</strong> ${dtSelectedRequest.response ? dtSelectedRequest.response.status : '-'}<br>
829
+ <strong>時間:</strong> ${dtSelectedRequest.duration ? Math.round(dtSelectedRequest.duration) + 'ms' : '-'}
830
  </div>
831
  `;
832
  detailsPanel.appendChild(generalSection);
833
 
834
  // リクエストヘッダー
835
+ if (dtSelectedRequest.requestHeaders) {
836
  const headersSection = document.createElement('div');
837
+ headersSection.className = 'dt-network-detail-section';
838
 
839
  const headersTitle = document.createElement('div');
840
+ headersTitle.className = 'dt-network-detail-title';
841
  headersTitle.textContent = 'リクエストヘッダー';
842
 
843
  const headersContent = document.createElement('div');
844
+ headersContent.className = 'dt-network-detail-content';
845
 
846
+ if (typeof dtSelectedRequest.requestHeaders === 'object' && !(dtSelectedRequest.requestHeaders instanceof Headers)) {
847
+ Object.entries(dtSelectedRequest.requestHeaders).forEach(([key, value]) => {
848
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
849
  });
850
+ } else if (dtSelectedRequest.requestHeaders instanceof Headers) {
851
+ dtSelectedRequest.requestHeaders.forEach((value, key) => {
852
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
853
  });
854
  }
 
859
  }
860
 
861
  // リクエストボディ
862
+ if (dtSelectedRequest.requestBody) {
863
  const bodySection = document.createElement('div');
864
+ bodySection.className = 'dt-network-detail-section';
865
 
866
  const bodyTitle = document.createElement('div');
867
+ bodyTitle.className = 'dt-network-detail-title';
868
  bodyTitle.textContent = 'リクエストボディ';
869
 
870
  const bodyContent = document.createElement('div');
871
+ bodyContent.className = 'dt-network-detail-content';
872
 
873
  try {
874
+ if (typeof dtSelectedRequest.requestBody === 'string') {
875
+ bodyContent.textContent = dtSelectedRequest.requestBody;
876
+ } else if (typeof dtSelectedRequest.requestBody === 'object') {
877
+ bodyContent.textContent = JSON.stringify(dtSelectedRequest.requestBody, null, 2);
878
  }
879
  } catch (e) {
880
  bodyContent.textContent = 'ボディを表示できません';
 
886
  }
887
 
888
  // レスポンス
889
+ if (dtSelectedRequest.response) {
890
  const responseSection = document.createElement('div');
891
+ responseSection.className = 'dt-network-detail-section';
892
 
893
  const responseTitle = document.createElement('div');
894
+ responseTitle.className = 'dt-network-detail-title';
895
  responseTitle.textContent = 'レスポンス';
896
 
897
  const responseContent = document.createElement('div');
898
+ responseContent.className = 'dt-network-detail-content';
899
 
900
+ if (dtSelectedRequest.response.body) {
901
+ if (typeof dtSelectedRequest.response.body === 'object') {
902
+ responseContent.textContent = JSON.stringify(dtSelectedRequest.response.body, null, 2);
903
  } else {
904
+ responseContent.textContent = dtSelectedRequest.response.body;
905
  }
906
  } else {
907
  responseContent.textContent = 'レスポンスボディがありません';
 
913
  }
914
 
915
  // エラー
916
+ if (dtSelectedRequest.error) {
917
  const errorSection = document.createElement('div');
918
+ errorSection.className = 'dt-network-detail-section';
919
 
920
  const errorTitle = document.createElement('div');
921
+ errorTitle.className = 'dt-network-detail-title';
922
  errorTitle.textContent = 'エラー';
923
 
924
  const errorContent = document.createElement('div');
925
+ errorContent.className = 'dt-network-detail-content';
926
+ errorContent.textContent = `${dtSelectedRequest.error.name}: ${dtSelectedRequest.error.message}`;
927
 
928
+ if (dtSelectedRequest.error.stack) {
929
+ errorContent.innerHTML += `<br><br>${dtSelectedRequest.error.stack.replace(/\n/g, '<br>')}`;
930
  }
931
 
932
  errorSection.appendChild(errorTitle);
 
937
 
938
  // コンテキストメニュー作成
939
  function createContextMenu() {
940
+ dtContextMenu = document.createElement('div');
941
+ dtContextMenu.className = 'dt-context-menu';
942
+ dtContextMenu.innerHTML = `
943
+ <div class="dt-context-menu-item" data-action="edit-html">HTMLとして編集</div>
944
+ <div class="dt-context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
945
+ <div class="dt-context-menu-item" data-action="add-attribute">属性を追加</div>
946
+ <div class="dt-context-menu-item" data-action="edit-element">要素を編集</div>
947
+ <div class="dt-context-menu-item" data-action="duplicate">要素を複製</div>
948
+ <div class="dt-context-menu-item" data-action="remove">要素を削除</div>
949
+ <div class="dt-context-menu-item" data-action="toggle-visibility">要素を非表示</div>
950
+ <div class="dt-context-menu-item" data-action="force-state">状態を強制</div>
951
  `;
952
+ document.body.appendChild(dtContextMenu);
953
 
954
+ dtContextMenu.querySelectorAll('.dt-context-menu-item').forEach(item => {
955
  item.addEventListener('click', (e) => {
956
  const action = e.target.getAttribute('data-action');
957
  handleContextMenuAction(action);
958
+ dtContextMenu.style.display = 'none';
959
  });
960
  });
961
 
962
  document.addEventListener('click', (e) => {
963
+ if (e.target !== dtContextMenu && !dtContextMenu.contains(e.target)) {
964
+ dtContextMenu.style.display = 'none';
965
  }
966
  });
967
  }
 
969
  // Storageパネル作成
970
  function createStoragePanel() {
971
  const panel = document.createElement('div');
972
+ panel.className = 'dt-devtools-panel';
973
  panel.id = 'storage-panel';
974
 
975
  // LocalStorage表示
 
978
  panel.appendChild(localStorageTitle);
979
 
980
  const localStorageTable = document.createElement('table');
981
+ localStorageTable.className = 'dt-storage-table';
982
  panel.appendChild(localStorageTable);
983
 
984
  const addLocalStorageBtn = document.createElement('button');
985
+ addLocalStorageBtn.className = 'dt-add-btn';
986
  addLocalStorageBtn.textContent = '+ Local Storageに追加';
987
  addLocalStorageBtn.onclick = () => {
988
  const key = prompt('キー名を入力');
 
1001
  panel.appendChild(sessionStorageTitle);
1002
 
1003
  const sessionStorageTable = document.createElement('table');
1004
+ sessionStorageTable.className = 'dt-storage-table';
1005
  panel.appendChild(sessionStorageTable);
1006
 
1007
  const addSessionStorageBtn = document.createElement('button');
1008
+ addSessionStorageBtn.className = 'dt-add-btn';
1009
  addSessionStorageBtn.textContent = '+ Session Storageに追加';
1010
  addSessionStorageBtn.onclick = () => {
1011
  const key = prompt('キー名を入力');
 
1024
  panel.appendChild(cookiesTitle);
1025
 
1026
  const cookiesTable = document.createElement('table');
1027
+ cookiesTable.className = 'dt-storage-table';
1028
  panel.appendChild(cookiesTable);
1029
 
1030
  const addCookieBtn = document.createElement('button');
1031
+ addCookieBtn.className = 'dt-add-btn';
1032
  addCookieBtn.textContent = '+ Cookieに追加';
1033
  addCookieBtn.onclick = () => {
1034
  const name = prompt('Cookie名を入力');
 
1069
 
1070
  const keyCell = document.createElement('td');
1071
  const keySpan = document.createElement('span');
1072
+ keySpan.className = 'dt-editable';
1073
  keySpan.textContent = key;
1074
  keySpan.onclick = () => {
1075
  const newKey = prompt('新しいキー名を入力', key);
 
1083
 
1084
  const valueCell = document.createElement('td');
1085
  const valueSpan = document.createElement('span');
1086
+ valueSpan.className = 'dt-editable';
1087
  valueSpan.textContent = value;
1088
  valueSpan.onclick = () => {
1089
  const newValue = prompt('新しい値を入力', value);
 
1095
  valueCell.appendChild(valueSpan);
1096
 
1097
  const actionsCell = document.createElement('td');
1098
+ actionsCell.className = 'dt-storage-actions';
1099
 
1100
  const deleteBtn = document.createElement('button');
1101
+ deleteBtn.className = 'dt-storage-btn';
1102
  deleteBtn.textContent = 'Delete';
1103
  deleteBtn.onclick = () => {
1104
  storage.removeItem(key);
 
1140
 
1141
  const nameCell = document.createElement('td');
1142
  const nameSpan = document.createElement('span');
1143
+ nameSpan.className = 'dt-editable';
1144
  nameSpan.textContent = decodedName;
1145
  nameSpan.onclick = () => {
1146
  const newName = prompt('新しい名前を入力', decodedName);
 
1157
 
1158
  const valueCell = document.createElement('td');
1159
  const valueSpan = document.createElement('span');
1160
+ valueSpan.className = 'dt-editable';
1161
  valueSpan.textContent = decodeURIComponent(value);
1162
  valueSpan.onclick = () => {
1163
  const newValue = prompt('新しい値を入力', decodeURIComponent(value));
 
1169
  valueCell.appendChild(valueSpan);
1170
 
1171
  const actionsCell = document.createElement('td');
1172
+ actionsCell.className = 'dt-storage-actions';
1173
 
1174
  const deleteBtn = document.createElement('button');
1175
+ deleteBtn.className = 'dt-storage-btn';
1176
  deleteBtn.textContent = 'Delete';
1177
  deleteBtn.onclick = () => {
1178
  document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
 
1197
 
1198
  // コンテキストメニューアクション処理
1199
  function handleContextMenuAction(action) {
1200
+ if (!dtSelectedElement) return;
1201
 
1202
  switch (action) {
1203
  case 'edit-html':
1204
+ if (dtSelectedElement === document.documentElement) {
1205
  alert('ルートHTML要素は直接編集できません');
1206
  return;
1207
  }
1208
+ startInlineEdit(dtSelectedDOMNode, dtSelectedElement.outerHTML, (newValue) => {
1209
  try {
1210
+ dtSelectedElement.outerHTML = newValue;
1211
+ dtRefreshElementsPanel();
1212
  } catch (e) {
1213
  alert('この要素は編集できません: ' + e.message);
1214
  }
1215
  });
1216
  break;
1217
+ case 'edit-whole-html':
1218
+ const htmlContent = document.documentElement.outerHTML;
1219
+ const textarea = document.createElement('textarea');
1220
+ textarea.style.width = '100%';
1221
+ textarea.style.height = '300px';
1222
+ textarea.value = htmlContent;
1223
+
1224
+ const modal = document.createElement('div');
1225
+ modal.style.position = 'fixed';
1226
+ modal.style.top = '0';
1227
+ modal.style.left = '0';
1228
+ modal.style.width = '100%';
1229
+ modal.style.height = '100%';
1230
+ modal.style.backgroundColor = 'rgba(0,0,0,0.8)';
1231
+ modal.style.zIndex = '10000';
1232
+ modal.style.display = 'flex';
1233
+ modal.style.flexDirection = 'column';
1234
+ modal.style.padding = '20px';
1235
+ modal.style.boxSizing = 'border-box';
1236
+
1237
+ const buttonContainer = document.createElement('div');
1238
+ buttonContainer.style.marginTop = '10px';
1239
+ buttonContainer.style.display = 'flex';
1240
+ buttonContainer.style.gap = '10px';
1241
+
1242
+ const saveButton = document.createElement('button');
1243
+ saveButton.textContent = '保存';
1244
+ saveButton.onclick = () => {
1245
+ try {
1246
+ document.documentElement.innerHTML = textarea.value;
1247
+ modal.remove();
1248
+ dtRefreshElementsPanel();
1249
+ } catch (e) {
1250
+ alert('HTMLの解析に失敗しました: ' + e.message);
1251
+ }
1252
+ };
1253
+
1254
+ const cancelButton = document.createElement('button');
1255
+ cancelButton.textContent = 'キャンセル';
1256
+ cancelButton.onclick = () => {
1257
+ modal.remove();
1258
+ };
1259
+
1260
+ buttonContainer.appendChild(saveButton);
1261
+ buttonContainer.appendChild(cancelButton);
1262
+
1263
+ modal.appendChild(textarea);
1264
+ modal.appendChild(buttonContainer);
1265
+
1266
+ document.body.appendChild(modal);
1267
+ break;
1268
  case 'add-attribute':
1269
  const attrName = prompt('属性名を入力');
1270
  if (attrName) {
1271
  const attrValue = prompt('属性値を入力');
1272
+ dtSelectedElement.setAttribute(attrName, attrValue || '');
1273
+ dtRefreshElementsPanel();
1274
  }
1275
  break;
1276
  case 'edit-element':
1277
+ startInlineEdit(dtSelectedDOMNode.querySelector('.dt-dom-tag'), dtSelectedElement.tagName.toLowerCase(), (newValue) => {
1278
  const newElement = document.createElement(newValue);
1279
+ Array.from(dtSelectedElement.attributes).forEach(attr => {
1280
  newElement.setAttribute(attr.name, attr.value);
1281
  });
1282
+ newElement.innerHTML = dtSelectedElement.innerHTML;
1283
+ dtSelectedElement.parentNode.replaceChild(newElement, dtSelectedElement);
1284
+ dtSelectedElement = newElement;
1285
+ dtRefreshElementsPanel();
1286
  });
1287
  break;
1288
  case 'duplicate':
1289
+ const clone = dtSelectedElement.cloneNode(true);
1290
+ dtSelectedElement.parentNode.insertBefore(clone, dtSelectedElement.nextSibling);
1291
+ dtRefreshElementsPanel();
1292
  break;
1293
  case 'remove':
1294
  if (confirm('要素を削除しますか?')) {
1295
+ dtSelectedElement.parentNode.removeChild(dtSelectedElement);
1296
+ dtRefreshElementsPanel();
1297
  }
1298
  break;
1299
  case 'toggle-visibility':
1300
+ if (dtSelectedElement.style.display === 'none') {
1301
+ dtSelectedElement.style.display = '';
1302
  } else {
1303
+ dtSelectedElement.style.display = 'none';
1304
  }
1305
+ dtRefreshElementsPanel();
1306
  break;
1307
  case 'force-state':
1308
  const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
1309
  if (state) {
1310
+ dtSelectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
1311
  'force-focus-within', 'force-focus-visible', 'force-target');
1312
+ dtSelectedElement.classList.add(`force-${state}`);
1313
+ dtRefreshElementsPanel();
1314
  }
1315
  break;
1316
  }
 
1318
 
1319
  // インライン編集関数
1320
  function startInlineEdit(element, initialValue, callback) {
1321
+ if (dtActiveEditElement) return;
1322
 
1323
  const originalValue = element.textContent;
1324
  const rect = element.getBoundingClientRect();
1325
 
1326
  const input = document.createElement('input');
1327
+ input.className = 'dt-dom-edit-input';
1328
  input.value = initialValue || originalValue;
1329
  input.style.position = 'absolute';
1330
  input.style.left = `${rect.left}px`;
 
1335
  input.focus();
1336
  input.select();
1337
 
1338
+ dtActiveEditElement = {
1339
  element: element,
1340
  input: input,
1341
  callback: callback
 
1378
  function cleanup() {
1379
  document.removeEventListener('click', clickOutsideHandler);
1380
  input.remove();
1381
+ dtActiveEditElement = null;
1382
  }
1383
  }
1384
 
1385
  // Consoleパネル作成
1386
  function createConsolePanel() {
1387
  const panel = document.createElement('div');
1388
+ panel.className = 'dt-devtools-panel';
1389
  panel.id = 'console-panel';
1390
 
1391
  const log = document.createElement('div');
1392
+ log.id = 'dt-console-log';
1393
 
1394
  const input = document.createElement('input');
1395
+ input.className = 'dt-console-input';
1396
  input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
1397
  input.onkeypress = (e) => {
1398
  if (e.key === 'Enter') {
1399
  try {
1400
  const result = eval(e.target.value);
1401
  if (result !== undefined) {
1402
+ logMessage('> ' + e.target.value, 'dt-console-log');
1403
+ logMessage('← ' + formatOutput(result), 'dt-console-log');
1404
  }
1405
  } catch (err) {
1406
+ logMessage(err.message, 'dt-console-error');
1407
  }
1408
  e.target.value = '';
1409
  }
 
1422
 
1423
  console.log = (...args) => {
1424
  originalConsole.log.apply(console, args);
1425
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-log');
1426
  };
1427
 
1428
  console.error = (...args) => {
1429
  originalConsole.error.apply(console, args);
1430
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-error');
1431
  };
1432
 
1433
  console.warn = (...args) => {
1434
  originalConsole.warn.apply(console, args);
1435
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-warn');
1436
  };
1437
 
1438
  console.info = (...args) => {
1439
  originalConsole.info.apply(console, args);
1440
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-info');
1441
  };
1442
 
1443
  function logMessage(message, className) {
 
1449
  }
1450
 
1451
  function formatOutput(output) {
1452
+ if (output === null) return '<span class="dt-json-null">null</span>';
1453
+ if (output === undefined) return '<span class="dt-json-null">undefined</span>';
1454
+ if (typeof output === 'boolean') return `<span class="dt-json-boolean">${output}</span>`;
1455
+ if (typeof output === 'number') return `<span class="dt-json-number">${output}</span>`;
1456
+ if (typeof output === 'string') return `<span class="dt-json-string">"${output}"</span>`;
1457
+ if (typeof output === 'function') return `<span class="dt-json-object">function ${output.name}() { ... }</span>`;
1458
+ if (Array.isArray(output)) return `<span class="dt-json-object">[${output.map(formatOutput).join(', ')}]</span>`;
1459
  if (typeof output === 'object') {
1460
  try {
1461
+ return `<span class="dt-json-object">${JSON.stringify(output, null, 2)
1462
+ .replace(/"([^"]+)":/g, '<span class="dt-json-key">"$1"</span>:')
1463
+ .replace(/"([^"]+)"/g, '<span class="dt-json-string">"$1"</span>')
1464
+ .replace(/\b(true|false)\b/g, '<span class="dt-json-boolean">$1</span>')
1465
+ .replace(/\b(null)\b/g, '<span class="dt-json-null">$1</span>')
1466
+ .replace(/\b(\d+)\b/g, '<span class="dt-json-number">$1</span>')}</span>`;
1467
  } catch (e) {
1468
+ return `<span class="dt-json-object">${output.toString()}</span>`;
1469
  }
1470
  }
1471
  return output;
 
1477
  // Elementsパネル作成
1478
  function createElementsPanel() {
1479
  const panel = document.createElement('div');
1480
+ panel.className = 'dt-devtools-panel';
1481
  panel.id = 'elements-panel';
1482
 
1483
  const container = document.createElement('div');
1484
+ container.className = 'dt-elements-container';
1485
 
1486
  const tree = document.createElement('div');
1487
+ tree.className = 'dt-dom-tree';
1488
+ tree.id = 'dt-dom-tree';
1489
 
1490
  const cssPanel = document.createElement('div');
1491
+ cssPanel.className = 'dt-css-panel';
1492
+ cssPanel.id = 'dt-css-panel';
1493
 
1494
  container.appendChild(tree);
1495
  container.appendChild(cssPanel);
 
1497
 
1498
  // CSSパネル更新関数
1499
  function updateCSSPanel(element) {
1500
+ const cssPanel = document.getElementById('dt-css-panel');
1501
  cssPanel.innerHTML = '';
1502
 
1503
  if (!element) return;
1504
 
1505
  if (element.style.length > 0) {
1506
  const inlineRule = document.createElement('div');
1507
+ inlineRule.className = 'dt-css-rule';
1508
 
1509
  const selector = document.createElement('div');
1510
+ selector.className = 'dt-css-selector';
1511
  selector.textContent = 'インラインスタイル';
1512
  inlineRule.appendChild(selector);
1513
 
 
1516
  const propValue = element.style[propName];
1517
 
1518
  const propDiv = document.createElement('div');
1519
+ propDiv.className = 'dt-css-property';
1520
 
1521
  const nameSpan = document.createElement('span');
1522
+ nameSpan.className = 'dt-css-property-name dt-editable';
1523
  nameSpan.textContent = propName;
1524
  nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
1525
 
1526
  const valueSpan = document.createElement('span');
1527
+ valueSpan.className = 'dt-css-property-value dt-editable';
1528
  valueSpan.textContent = propValue;
1529
  valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
1530
 
1531
  const toggleSpan = document.createElement('span');
1532
+ toggleSpan.className = 'dt-css-toggle';
1533
  toggleSpan.textContent = '×';
1534
  toggleSpan.title = 'プロパティを無効化';
1535
  toggleSpan.onclick = () => {
 
1548
 
1549
  const computedStyles = window.getComputedStyle(element);
1550
  const computedRule = document.createElement('div');
1551
+ computedRule.className = 'dt-css-rule';
1552
 
1553
  const computedSelector = document.createElement('div');
1554
+ computedSelector.className = 'dt-css-selector';
1555
  computedSelector.textContent = '計算されたスタイル';
1556
  computedRule.appendChild(computedSelector);
1557
 
 
1564
  const value = computedStyles[prop];
1565
 
1566
  const propDiv = document.createElement('div');
1567
+ propDiv.className = 'dt-css-property';
1568
 
1569
  const nameSpan = document.createElement('span');
1570
+ nameSpan.className = 'dt-css-property-name';
1571
  nameSpan.textContent = prop;
1572
 
1573
  const valueSpan = document.createElement('span');
1574
+ valueSpan.className = 'dt-css-property-value';
1575
  valueSpan.textContent = value;
1576
 
1577
  propDiv.appendChild(nameSpan);
 
1586
  function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
1587
  if (node.nodeType === Node.ELEMENT_NODE) {
1588
  const element = document.createElement('div');
1589
+ element.className = 'dt-dom-node';
1590
  element.style.marginLeft = `${depth * 15}px`;
1591
  element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
1592
 
1593
  // 右クリックイベント
1594
  element.oncontextmenu = (e) => {
1595
  e.preventDefault();
1596
+ dtSelectedElement = node;
1597
+ dtSelectedDOMNode = element;
1598
 
1599
+ document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1600
  element.classList.add('selected');
1601
 
1602
  if (node !== document.documentElement) {
1603
+ dtContextMenu.style.display = 'block';
1604
+ dtContextMenu.style.left = `${e.pageX}px`;
1605
+ dtContextMenu.style.top = `${e.pageY}px`;
1606
  }
1607
 
1608
  updateCSSPanel(node);
 
1610
 
1611
  // 左クリックで選択
1612
  element.onclick = (e) => {
1613
+ if (e.target.classList.contains('dt-dom-toggle')) return;
1614
 
1615
  e.stopPropagation();
1616
+ dtSelectedElement = node;
1617
+ dtSelectedDOMNode = element;
1618
 
1619
+ document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1620
  element.classList.add('selected');
1621
 
1622
  updateCSSPanel(node);
 
1628
 
1629
  if (hasChildren) {
1630
  const toggle = document.createElement('div');
1631
+ toggle.className = 'dt-dom-toggle';
1632
  toggle.onclick = (e) => {
1633
  e.stopPropagation();
1634
+ const children = element.querySelector('.dt-dom-children');
1635
  if (children) {
1636
  if (children.style.maxHeight === '0px') {
1637
  children.style.maxHeight = children.scrollHeight + 'px';
 
1647
 
1648
  // タグ名(HTML要素は編集不可)
1649
  const tag = document.createElement('span');
1650
+ tag.className = 'dt-dom-tag';
1651
  tag.textContent = `<${node.tagName.toLowerCase()}`;
1652
 
1653
  if (node !== document.documentElement) {
1654
+ tag.classList.add('dt-editable');
1655
  tag.onclick = (e) => {
1656
  e.stopPropagation();
1657
  startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
 
1661
  });
1662
  newElement.innerHTML = node.innerHTML;
1663
  node.parentNode.replaceChild(newElement, node);
1664
+ dtSelectedElement = newElement;
1665
+ dtRefreshElementsPanel();
1666
  });
1667
  };
1668
  }
 
1672
  // 属性(HTML要素は編集不可)
1673
  Array.from(node.attributes).forEach(attr => {
1674
  const attrSpan = document.createElement('span');
1675
+ attrSpan.className = 'dt-dom-attr';
1676
  attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
1677
 
1678
  if (node !== document.documentElement) {
1679
+ attrSpan.classList.add('dt-editable');
1680
  attrSpan.onclick = (e) => {
1681
  e.stopPropagation();
1682
  startInlineEdit(attrSpan, attr.value, (newValue) => {
1683
  node.setAttribute(attr.name, newValue);
1684
+ dtRefreshElementsPanel();
1685
  });
1686
  };
1687
  }
 
1693
 
1694
  if (hasChildren) {
1695
  const childrenContainer = document.createElement('div');
1696
+ childrenContainer.className = 'dt-dom-children';
1697
  childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
1698
 
1699
  node.childNodes.forEach(child => {
 
1703
  if (node.tagName.toLowerCase() !== 'br') {
1704
  const closeTag = document.createElement('div');
1705
  closeTag.style.marginLeft = `${depth * 15}px`;
1706
+ closeTag.innerHTML = `<span class="dt-dom-tag">&lt;/${node.tagName.toLowerCase()}&gt;</span>`;
1707
  childrenContainer.appendChild(closeTag);
1708
  }
1709
 
 
1714
  } else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
1715
  const text = document.createElement('div');
1716
  text.style.marginLeft = `${depth * 15}px`;
1717
+ text.className = 'dt-dom-text dt-editable';
1718
  text.textContent = `"${node.textContent.trim()}"`;
1719
  text.onclick = (e) => {
1720
  e.stopPropagation();
1721
  startInlineEdit(text, node.textContent.trim(), (newValue) => {
1722
  node.textContent = newValue;
1723
+ dtRefreshElementsPanel();
1724
  });
1725
  };
1726
  parentElement.appendChild(text);
 
1743
  }
1744
 
1745
  updateCSSPanel(element);
1746
+ dtRefreshElementsPanel();
1747
  }
1748
  }
1749
 
1750
  // refreshElementsPanel関数の定義
1751
+ dtRefreshElementsPanel = function() {
1752
+ let tree = document.getElementById('dt-dom-tree');
1753
 
1754
  if (!tree) {
1755
  const panel = document.getElementById('elements-panel');
1756
  if (panel) {
1757
+ const container = panel.querySelector('.dt-elements-container');
1758
  if (container) {
1759
  tree = document.createElement('div');
1760
+ tree.className = 'dt-dom-tree';
1761
+ tree.id = 'dt-dom-tree';
1762
+ container.insertBefore(tree, container.querySelector('.dt-css-panel'));
1763
  }
1764
  }
1765
  }
 
1769
  tree.innerHTML = '';
1770
  buildDOMTree(document.documentElement, tree, 0, true);
1771
 
1772
+ if (dtSelectedElement) {
1773
+ const elementId = dtSelectedElement.id || Array.from(dtSelectedElement.attributes)
1774
  .find(attr => attr.name.startsWith('data-element-id'))?.value;
1775
 
1776
  if (elementId) {
1777
  const node = document.querySelector(`[data-element-id="${elementId}"]`);
1778
  if (node) {
1779
  node.classList.add('selected');
1780
+ updateCSSPanel(dtSelectedElement);
1781
  }
1782
  }
1783
  }
 
1788
 
1789
  // 初期表示
1790
  setTimeout(() => {
1791
+ dtRefreshElementsPanel();
1792
  }, 0);
1793
 
1794
  return panel;
 
1796
 
1797
  // 開発者ツール表示/非表示
1798
  function toggleDevTools() {
1799
+ const container = document.getElementById('dt-devtools-container');
1800
  if (container.style.display === 'none') {
1801
  container.style.display = 'flex';
1802
  } else {
 
1807
  // 開くボタン作成
1808
  function createOpenButton() {
1809
  const button = document.createElement('button');
1810
+ button.id = 'dt-open-devtools-btn';
1811
  button.textContent = '開発者ツールを開く';
1812
  button.style.position = 'fixed';
1813
  button.style.bottom = '10px';
1814
  button.style.right = '10px';
1815
  button.style.padding = '8px 16px';
1816
+ button.style.background = 'var(--dt-primary-color)';
1817
  button.style.color = '#000';
1818
  button.style.border = 'none';
1819
  button.style.borderRadius = '4px';
 
1826
 
1827
  // 初期化
1828
  document.addEventListener('DOMContentLoaded', function() {
1829
+ // 最小限のUIだけ先に表示
1830
  createOpenButton();
1831
+
1832
+ // メインのツールは遅延読み込み
1833
+ setTimeout(() => {
1834
+ createDevTools();
1835
+ console.log('開発者ツールが初期化されました');
1836
+ }, 1000);
1837
  });
1838
  })();