soiz1 commited on
Commit
c338e05
·
verified ·
1 Parent(s): b3de035

Update dev-tools.js

Browse files
Files changed (1) hide show
  1. dev-tools.js +413 -400
dev-tools.js CHANGED
@@ -17,142 +17,104 @@
17
  let refreshElementsPanel = null;
18
  let vitalsMeasurementStarted = false;
19
 
20
- // Web Vitalsスクリプトを動的に読み込み
21
- const loadWebVitals = () => {
22
- return new Promise((resolve) => {
23
- if (window.webVitals) {
24
- resolve();
25
- return;
26
- }
27
-
28
- const webVitalsScript = document.createElement('script');
29
- webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
30
- webVitalsScript.onload = resolve;
31
- document.head.appendChild(webVitalsScript);
32
- });
33
- };
34
-
35
- // DOM変更を監視するMutationObserver
36
- const setupMutationObserver = () => {
37
- observer = new MutationObserver((mutations) => {
38
- mutations.forEach((mutation) => {
39
- if (mutation.type === 'childList') {
40
- mutation.addedNodes.forEach((node) => {
41
- if (node.nodeType === Node.ELEMENT_NODE) {
42
- highlightNode(node);
43
- }
44
- });
45
- }
46
- });
47
- if (refreshElementsPanel) {
48
- refreshElementsPanel();
49
- }
50
- });
51
-
52
- observer.observe(document.documentElement, {
53
- childList: true,
54
- subtree: true,
55
- attributes: true,
56
- characterData: true
57
- });
58
- };
59
  const style = document.createElement('style');
60
  style.textContent = `
61
- :root {
62
- --bg-color: #1e1e1e;
63
- --panel-bg: #252526;
64
- --border-color: #3c3c3c;
65
- --text-color: #e0e0e0;
66
- --text-muted: #a0a0a0;
67
- --primary-color: #007acc;
68
- --primary-hover: #3e9fda;
69
- --success-color: #4caf50;
70
- --error-color: #f44336;
71
- --warning-color: #ff9800;
72
- --info-color: #2196f3;
73
- --highlight-bg: rgba(0, 122, 204, 0.2);
74
- --tab-bg: #2d2d2d;
75
- --tab-active-bg: #1e1e1e;
76
- --console-log-color: #e0e0e0;
77
- --console-error-color: #f44336;
78
- --console-warn-color: #ff9800;
79
- --console-info-color: #4fc3f7;
80
- --json-key: #9cdcfe;
81
- --json-string: #ce9178;
82
- --json-number: #b5cea8;
83
- --json-boolean: #569cd6;
84
- --json-null: #569cd6;
85
- --dom-tag: #569cd6;
86
- --dom-attr: #9cdcfe;
87
- --dom-text: #d4d4d4;
88
- }
89
-
90
- .devtools-container {
91
  position: fixed;
92
  bottom: 0;
93
  left: 0;
94
  width: 100%;
95
  height: 300px;
96
- background-color: var(--panel-bg);
97
- border-top: 1px solid var(--border-color);
98
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
99
  z-index: 9999;
100
  display: flex;
101
  flex-direction: column;
102
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
103
- color: var(--text-color);
104
  }
105
 
106
- .devtools-header {
107
  display: flex;
108
  justify-content: space-between;
109
  align-items: center;
110
  padding: 5px 10px;
111
- background-color: var(--tab-bg);
112
- border-bottom: 1px solid var(--border-color);
113
  }
114
 
115
- .devtools-tabs {
116
  display: flex;
117
  gap: 5px;
118
  }
119
 
120
- .devtools-tab {
121
  padding: 5px 10px;
122
  cursor: pointer;
123
  border-radius: 3px 3px 0 0;
124
- background-color: var(--tab-bg);
125
- border: 1px solid var(--border-color);
126
  border-bottom: none;
127
  font-size: 12px;
128
- color: var(--text-muted);
129
  }
130
 
131
- .devtools-tab.active {
132
- background-color: var(--tab-active-bg);
133
- color: var(--text-color);
134
- border-bottom: 1px solid var(--tab-active-bg);
135
  margin-bottom: -1px;
136
  font-weight: bold;
137
  }
138
 
139
- .devtools-close {
140
  background: none;
141
  border: none;
142
  font-size: 16px;
143
  cursor: pointer;
144
  padding: 0 5px;
145
- color: var(--text-color);
146
  }
147
 
148
- .devtools-content {
149
  flex: 1;
150
  overflow: auto;
151
  position: relative;
152
- background-color: var(--panel-bg);
153
  }
154
 
155
- .devtools-panel {
156
  position: absolute;
157
  top: 0;
158
  left: 0;
@@ -161,45 +123,45 @@
161
  padding: 10px;
162
  overflow: auto;
163
  display: none;
164
- background-color: var(--panel-bg);
165
  }
166
 
167
- .devtools-panel.active {
168
  display: block;
169
  }
170
 
171
  /* Console スタイル */
172
- #console-log {
173
  white-space: pre-wrap;
174
  margin: 0;
175
  line-height: 1.4;
176
  flex: 1;
177
- color: var(--console-log-color);
178
  font-family: 'Consolas', 'Monaco', monospace;
179
  font-size: 13px;
180
  }
181
 
182
- .console-log {
183
- color: var(--console-log-color);
184
  }
185
 
186
- .console-error {
187
- color: var(--console-error-color);
188
  }
189
 
190
- .console-warn {
191
- color: var(--console-warn-color);
192
  }
193
 
194
- .console-info {
195
- color: var(--console-info-color);
196
  }
197
 
198
- .console-input {
199
  width: calc(100% - 16px);
200
- background: var(--tab-bg);
201
- border: 1px solid var(--border-color);
202
- color: var(--text-color);
203
  padding: 8px;
204
  margin-top: 10px;
205
  font-family: monospace;
@@ -207,117 +169,117 @@
207
  }
208
 
209
  /* Elements スタイル */
210
- .elements-container {
211
  display: flex;
212
  flex: 1;
213
  overflow: hidden;
214
  }
215
 
216
- .dom-tree {
217
  font-family: 'Consolas', 'Monaco', monospace;
218
  flex: 1;
219
  overflow: auto;
220
- border-right: 1px solid var(--border-color);
221
  padding-right: 10px;
222
- color: var(--dom-text);
223
  font-size: 13px;
224
  }
225
 
226
- .dom-node {
227
  margin-left: 15px;
228
  position: relative;
229
  line-height: 1.4;
230
  transition: background-color 0.3s;
231
  }
232
 
233
- .dom-node.selected {
234
- background: var(--highlight-bg);
235
  }
236
 
237
- .dom-node.highlight {
238
- animation: highlight-fade 1.5s;
239
  }
240
 
241
- @keyframes highlight-fade {
242
  0% { background-color: rgba(79, 195, 247, 0.5); }
243
  100% { background-color: transparent; }
244
  }
245
 
246
- .dom-tag {
247
- color: var(--dom-tag);
248
  font-weight: bold;
249
  }
250
 
251
- .dom-attr {
252
- color: var(--dom-attr);
253
  }
254
 
255
- .dom-attr.editable:hover {
256
  text-decoration: underline;
257
  cursor: pointer;
258
  }
259
 
260
- .dom-text {
261
- color: var(--dom-text);
262
  }
263
 
264
- .dom-edit-input {
265
- background: var(--panel-bg);
266
- border: 1px solid var(--primary-color);
267
  padding: 0 2px;
268
  margin: -1px 0;
269
  font-family: monospace;
270
  min-width: 50px;
271
- color: var(--text-color);
272
  }
273
 
274
- .css-panel {
275
  flex: 1;
276
  overflow: auto;
277
  padding-left: 10px;
278
  font-size: 13px;
279
  }
280
 
281
- .css-rule {
282
  margin-bottom: 15px;
283
- border: 1px solid var(--border-color);
284
  padding: 8px;
285
- background-color: var(--tab-bg);
286
  border-radius: 3px;
287
  }
288
 
289
- .css-selector {
290
- color: var(--primary-color);
291
  margin-bottom: 5px;
292
  font-weight: bold;
293
  }
294
 
295
- .css-property {
296
  display: flex;
297
  margin-bottom: 3px;
298
  }
299
 
300
- .css-property-name {
301
- color: var(--dom-attr);
302
  min-width: 120px;
303
  }
304
 
305
- .css-property-value {
306
- color: var(--dom-text);
307
  flex: 1;
308
  }
309
 
310
- .css-toggle {
311
  margin-left: 10px;
312
- color: var(--error-color);
313
  cursor: pointer;
314
  }
315
 
316
  /* Context Menu */
317
- .context-menu {
318
  position: absolute;
319
- background: var(--panel-bg);
320
- border: 1px solid var(--border-color);
321
  z-index: 10000;
322
  min-width: 200px;
323
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
@@ -326,43 +288,43 @@
326
  overflow: hidden;
327
  }
328
 
329
- .context-menu-item {
330
  padding: 8px 15px;
331
  cursor: pointer;
332
- color: var(--text-color);
333
  font-size: 13px;
334
  }
335
 
336
- .context-menu-item:hover {
337
- background: var(--primary-color);
338
  color: #000;
339
  }
340
 
341
  /* Storage スタイル */
342
- .storage-table {
343
  width: 100%;
344
  border-collapse: collapse;
345
  margin-bottom: 10px;
346
  font-size: 13px;
347
  }
348
 
349
- .storage-table th, .storage-table td {
350
- border: 1px solid var(--border-color);
351
  padding: 5px;
352
  text-align: left;
353
  }
354
 
355
- .storage-table th {
356
- background: var(--tab-bg);
357
  }
358
 
359
- .storage-actions {
360
  display: flex;
361
  gap: 5px;
362
  }
363
 
364
- .storage-btn {
365
- background: var(--primary-color);
366
  border: none;
367
  padding: 2px 5px;
368
  cursor: pointer;
@@ -371,18 +333,18 @@
371
  font-size: 12px;
372
  }
373
 
374
- .editable {
375
  cursor: pointer;
376
  padding: 2px 5px;
377
  border: 1px dashed transparent;
378
  }
379
 
380
- .editable:hover {
381
- border-color: var(--primary-color);
382
  }
383
 
384
- .add-btn {
385
- background: var(--primary-color);
386
  color: #000;
387
  border: none;
388
  padding: 5px 10px;
@@ -392,47 +354,47 @@
392
  font-size: 13px;
393
  }
394
 
395
- .add-btn:hover {
396
- background: var(--primary-hover);
397
  }
398
 
399
  /* Network スタイル */
400
- .network-container {
401
  display: flex;
402
  height: 100%;
403
  overflow: hidden;
404
  }
405
 
406
- .network-requests {
407
  width: 40%;
408
  overflow-y: auto;
409
- border-right: 1px solid var(--border-color);
410
  font-size: 13px;
411
  }
412
 
413
- .network-details {
414
  width: 60%;
415
  overflow-y: auto;
416
  padding-left: 10px;
417
  }
418
 
419
- .network-request {
420
  padding: 8px;
421
- border-bottom: 1px solid var(--border-color);
422
  cursor: pointer;
423
  display: flex;
424
  align-items: center;
425
  }
426
 
427
- .network-request:hover {
428
  background-color: rgba(0, 122, 204, 0.1);
429
  }
430
 
431
- .network-request.selected {
432
- background-color: var(--highlight-bg);
433
  }
434
 
435
- .network-status {
436
  width: 20px;
437
  height: 20px;
438
  border-radius: 50%;
@@ -443,51 +405,51 @@
443
  flex-shrink: 0;
444
  }
445
 
446
- .network-status.success {
447
- background-color: var(--success-color);
448
  color: white;
449
  }
450
 
451
- .network-status.error {
452
- background-color: var(--error-color);
453
  color: white;
454
  }
455
 
456
- .network-method {
457
  font-weight: bold;
458
  margin-right: 8px;
459
- color: var(--primary-color);
460
  min-width: 40px;
461
  }
462
 
463
- .network-url {
464
  flex: 1;
465
  white-space: nowrap;
466
  overflow: hidden;
467
  text-overflow: ellipsis;
468
  }
469
 
470
- .network-time {
471
- color: var(--text-muted);
472
  font-size: 11px;
473
  margin-left: 8px;
474
  }
475
 
476
- .network-detail-section {
477
  margin-bottom: 15px;
478
  }
479
 
480
- .network-detail-title {
481
  font-weight: bold;
482
  margin-bottom: 5px;
483
- color: var(--primary-color);
484
  }
485
 
486
- .network-detail-content {
487
- background: var(--tab-bg);
488
  padding: 8px;
489
  border-radius: 3px;
490
- border: 1px solid var(--border-color);
491
  font-family: monospace;
492
  white-space: pre-wrap;
493
  font-size: 12px;
@@ -496,141 +458,175 @@
496
  }
497
 
498
  /* Web Vitals スタイル */
499
- .vitals-container {
500
  display: flex;
501
  flex-direction: column;
502
  gap: 15px;
503
  }
504
 
505
- .vital-card {
506
- background: var(--tab-bg);
507
- border: 1px solid var(--border-color);
508
  border-radius: 5px;
509
  padding: 15px;
510
  }
511
 
512
- .vital-title {
513
  font-weight: bold;
514
  margin-bottom: 10px;
515
- color: var(--primary-color);
516
  display: flex;
517
  justify-content: space-between;
518
  align-items: center;
519
  }
520
 
521
- .vital-value {
522
  font-size: 24px;
523
  font-weight: bold;
524
  margin: 10px 0;
525
  }
526
 
527
- .vital-good {
528
- color: var(--success-color);
529
  }
530
 
531
- .vital-needs-improvement {
532
- color: var(--warning-color);
533
  }
534
 
535
- .vital-poor {
536
- color: var(--error-color);
537
  }
538
 
539
- .vital-description {
540
  font-size: 13px;
541
- color: var(--text-muted);
542
  }
543
 
544
- .vital-thresholds {
545
  display: flex;
546
  margin-top: 10px;
547
  font-size: 12px;
548
  }
549
 
550
- .vital-threshold {
551
  flex: 1;
552
  text-align: center;
553
  padding: 5px;
554
  border-radius: 3px;
555
  }
556
 
557
- .vital-threshold.active {
558
  background: rgba(0, 122, 204, 0.2);
559
  }
560
 
561
  /* DOM Tree Toggle */
562
- .dom-toggle {
563
  position: absolute;
564
  left: -12px;
565
  top: 2px;
566
  width: 10px;
567
  height: 10px;
568
  cursor: pointer;
569
- background-color: var(--text-muted);
570
  clip-path: polygon(0 0, 100% 50%, 0 100%);
571
  transition: transform 0.2s;
572
  }
573
 
574
- .dom-toggle.collapsed {
575
  transform: rotate(-90deg);
576
  }
577
 
578
- .dom-children {
579
  overflow: hidden;
580
  transition: max-height 0.3s ease-out;
581
  }
582
 
583
  /* JSON スタイル */
584
- .json-key {
585
- color: var(--json-key);
586
  }
587
 
588
- .json-string {
589
- color: var(--json-string);
590
  }
591
 
592
- .json-number {
593
- color: var(--json-number);
594
  }
595
 
596
- .json-boolean {
597
- color: var(--json-boolean);
598
  }
599
 
600
- .json-null {
601
- color: var(--json-null);
602
  }
603
  `;
604
  document.head.appendChild(style);
 
605
  // ノードをハイライト表示
606
  function highlightNode(node) {
607
  const elementId = node.id || Math.random().toString(36).substr(2, 9);
608
- node.setAttribute('data-element-id', elementId);
609
 
610
  setTimeout(() => {
611
- const domNode = document.querySelector(`[data-element-id="${elementId}"]`);
612
  if (domNode) {
613
- domNode.classList.add('highlight');
614
  setTimeout(() => {
615
- domNode.classList.remove('highlight');
616
  }, 1500);
617
  }
618
  }, 100);
619
  }
620
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  // 開発者ツールのメイン関数
622
  const createDevTools = () => {
623
  const container = document.createElement('div');
624
- container.className = 'devtools-container';
625
- container.id = 'devtools-container';
626
  container.style.display = 'none';
627
 
628
  // ヘッダー部分
629
  const header = document.createElement('div');
630
- header.className = 'devtools-header';
631
 
632
  const tabs = document.createElement('div');
633
- tabs.className = 'devtools-tabs';
634
 
635
  const consoleTab = createTab('Console', 'console');
636
  const elementsTab = createTab('Elements', 'elements');
@@ -645,7 +641,7 @@
645
  tabs.appendChild(vitalsTab);
646
 
647
  const closeBtn = document.createElement('button');
648
- closeBtn.className = 'devtools-close';
649
  closeBtn.textContent = '×';
650
  closeBtn.onclick = toggleDevTools;
651
 
@@ -654,7 +650,7 @@
654
 
655
  // コンテンツ部分
656
  const content = document.createElement('div');
657
- content.className = 'devtools-content';
658
 
659
  const consolePanel = createConsolePanel();
660
  const elementsPanel = createElementsPanel();
@@ -679,11 +675,11 @@
679
  // タブ切り替え機能
680
  function createTab(name, panelId) {
681
  const tab = document.createElement('div');
682
- tab.className = 'devtools-tab';
683
  tab.textContent = name;
684
  tab.onclick = () => {
685
- document.querySelectorAll('.devtools-tab').forEach(t => t.classList.remove('active'));
686
- document.querySelectorAll('.devtools-panel').forEach(p => p.classList.remove('active'));
687
  tab.classList.add('active');
688
  document.getElementById(panelId + '-panel').classList.add('active');
689
 
@@ -700,86 +696,86 @@
700
  // Web Vitalsパネル作成
701
  function createVitalsPanel() {
702
  const panel = document.createElement('div');
703
- panel.className = 'devtools-panel';
704
  panel.id = 'vitals-panel';
705
 
706
  const container = document.createElement('div');
707
- container.className = 'vitals-container';
708
  panel.appendChild(container);
709
 
710
  // 測定開始ボタン
711
  const startButton = document.createElement('button');
712
- startButton.className = 'add-btn';
713
  startButton.textContent = '測定を開始';
714
  startButton.onclick = startVitalsMeasurement;
715
  container.appendChild(startButton);
716
 
717
  // CLS (Cumulative Layout Shift)
718
  const clsCard = document.createElement('div');
719
- clsCard.className = 'vital-card';
720
  clsCard.innerHTML = `
721
- <div class="vital-title">CLS (Cumulative Layout Shift) <span class="vital-description">視覚的な安定性</span></div>
722
- <div class="vital-value" id="cls-value">-</div>
723
- <div class="vital-thresholds">
724
- <div class="vital-threshold">Good: &lt; 0.1</div>
725
- <div class="vital-threshold">Needs Improvement: &lt; 0.25</div>
726
- <div class="vital-threshold">Poor: ≥ 0.25</div>
727
  </div>
728
  `;
729
  container.appendChild(clsCard);
730
 
731
  // FCP (First Contentful Paint)
732
  const fcpCard = document.createElement('div');
733
- fcpCard.className = 'vital-card';
734
  fcpCard.innerHTML = `
735
- <div class="vital-title">FCP (First Contentful Paint) <span class="vital-description">最初のコンテンツ表示</span></div>
736
- <div class="vital-value" id="fcp-value">-</div>
737
- <div class="vital-thresholds">
738
- <div class="vital-threshold">Good: &lt; 1.8s</div>
739
- <div class="vital-threshold">Needs Improvement: &lt; 3s</div>
740
- <div class="vital-threshold">Poor: ≥ 3s</div>
741
  </div>
742
  `;
743
  container.appendChild(fcpCard);
744
 
745
  // FID (First Input Delay)
746
  const fidCard = document.createElement('div');
747
- fidCard.className = 'vital-card';
748
  fidCard.innerHTML = `
749
- <div class="vital-title">FID (First Input Delay) <span class="vital-description">最初の入力遅延</span></div>
750
- <div class="vital-value" id="fid-value">-</div>
751
- <div class="vital-thresholds">
752
- <div class="vital-threshold">Good: &lt; 100ms</div>
753
- <div class="vital-threshold">Needs Improvement: &lt; 300ms</div>
754
- <div class="vital-threshold">Poor: ≥ 300ms</div>
755
  </div>
756
  `;
757
  container.appendChild(fidCard);
758
 
759
  // LCP (Largest Contentful Paint)
760
  const lcpCard = document.createElement('div');
761
- lcpCard.className = 'vital-card';
762
  lcpCard.innerHTML = `
763
- <div class="vital-title">LCP (Largest Contentful Paint) <span class="vital-description">最大のコンテンツ表示</span></div>
764
- <div class="vital-value" id="lcp-value">-</div>
765
- <div class="vital-thresholds">
766
- <div class="vital-threshold">Good: &lt; 2.5s</div>
767
- <div class="vital-threshold">Needs Improvement: &lt; 4s</div>
768
- <div class="vital-threshold">Poor: ≥ 4s</div>
769
  </div>
770
  `;
771
  container.appendChild(lcpCard);
772
 
773
  // TTFB (Time to First Byte)
774
  const ttfbCard = document.createElement('div');
775
- ttfbCard.className = 'vital-card';
776
  ttfbCard.innerHTML = `
777
- <div class="vital-title">TTFB (Time to First Byte) <span class="vital-description">最初のバイト到達時間</span></div>
778
- <div class="vital-value" id="ttfb-value">-</div>
779
- <div class="vital-thresholds">
780
- <div class="vital-threshold">Good: &lt; 800ms</div>
781
- <div class="vital-threshold">Needs Improvement: &lt; 1.8s</div>
782
- <div class="vital-threshold">Poor: ≥ 1.8s</div>
783
  </div>
784
  `;
785
  container.appendChild(ttfbCard);
@@ -805,9 +801,9 @@
805
  vitals.onLCP(updateLCP);
806
  vitals.onTTFB(updateTTFB);
807
  }
808
- alert('Web Vitalsの測定を開始しました');
809
  } catch (e) {
810
- console.error('Web Vitals error:', e);
811
  }
812
  }
813
  });
@@ -839,7 +835,7 @@
839
  }
840
 
841
  function updateVitalDisplay(id, value, goodThreshold, needsImprovementThreshold) {
842
- const element = document.getElementById(`${id}-value`);
843
  if (!element) return;
844
 
845
  const displayValue = id === 'ttfb' ?
@@ -848,16 +844,16 @@
848
 
849
  element.textContent = displayValue;
850
 
851
- element.className = 'vital-value';
852
  if (value <= goodThreshold) {
853
- element.classList.add('vital-good');
854
  } else if (value <= needsImprovementThreshold) {
855
- element.classList.add('vital-needs-improvement');
856
  } else {
857
- element.classList.add('vital-poor');
858
  }
859
 
860
- const thresholds = element.parentElement.querySelectorAll('.vital-threshold');
861
  thresholds.forEach((threshold, index) => {
862
  threshold.classList.remove('active');
863
  if (
@@ -876,21 +872,21 @@
876
  // ネットワークパネル作成
877
  function createNetworkPanel() {
878
  const panel = document.createElement('div');
879
- panel.className = 'devtools-panel';
880
  panel.id = 'network-panel';
881
 
882
  const container = document.createElement('div');
883
- container.className = 'network-container';
884
  panel.appendChild(container);
885
 
886
  // リクエストリスト
887
  const requestsList = document.createElement('div');
888
- requestsList.className = 'network-requests';
889
  container.appendChild(requestsList);
890
 
891
  // 詳細パネル
892
  const detailsPanel = document.createElement('div');
893
- detailsPanel.className = 'network-details';
894
  container.appendChild(detailsPanel);
895
 
896
  // ネットワークリクエストを監視
@@ -1051,14 +1047,14 @@
1051
  const panel = document.getElementById('network-panel');
1052
  if (!panel || !panel.classList.contains('active')) return;
1053
 
1054
- const requestsList = panel.querySelector('.network-requests');
1055
- const detailsPanel = panel.querySelector('.network-details');
1056
 
1057
  requestsList.innerHTML = '';
1058
 
1059
  networkRequests.forEach(request => {
1060
  const requestElement = document.createElement('div');
1061
- requestElement.className = 'network-request';
1062
  if (selectedRequest && selectedRequest.id === request.id) {
1063
  requestElement.classList.add('selected');
1064
  }
@@ -1070,19 +1066,19 @@
1070
  };
1071
 
1072
  const statusElement = document.createElement('div');
1073
- statusElement.className = `network-status ${request.status}`;
1074
  statusElement.textContent = request.status === 'success' ? '✓' : '✕';
1075
 
1076
  const methodElement = document.createElement('div');
1077
- methodElement.className = 'network-method';
1078
  methodElement.textContent = request.method;
1079
 
1080
  const urlElement = document.createElement('div');
1081
- urlElement.className = 'network-url';
1082
  urlElement.textContent = request.url;
1083
 
1084
  const timeElement = document.createElement('div');
1085
- timeElement.className = 'network-time';
1086
  timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
1087
 
1088
  requestElement.appendChild(statusElement);
@@ -1099,15 +1095,15 @@
1099
  const panel = document.getElementById('network-panel');
1100
  if (!panel || !selectedRequest) return;
1101
 
1102
- const detailsPanel = panel.querySelector('.network-details');
1103
  detailsPanel.innerHTML = '';
1104
 
1105
  // 一般情報
1106
  const generalSection = document.createElement('div');
1107
- generalSection.className = 'network-detail-section';
1108
  generalSection.innerHTML = `
1109
- <div class="network-detail-title">一般</div>
1110
- <div class="network-detail-content">
1111
  <strong>URL:</strong> ${selectedRequest.url}<br>
1112
  <strong>メソッド:</strong> ${selectedRequest.method}<br>
1113
  <strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
@@ -1119,14 +1115,14 @@
1119
  // リクエストヘッダー
1120
  if (selectedRequest.requestHeaders) {
1121
  const headersSection = document.createElement('div');
1122
- headersSection.className = 'network-detail-section';
1123
 
1124
  const headersTitle = document.createElement('div');
1125
- headersTitle.className = 'network-detail-title';
1126
  headersTitle.textContent = 'リクエストヘッダー';
1127
 
1128
  const headersContent = document.createElement('div');
1129
- headersContent.className = 'network-detail-content';
1130
 
1131
  if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
1132
  Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
@@ -1146,14 +1142,14 @@
1146
  // リクエストボディ
1147
  if (selectedRequest.requestBody) {
1148
  const bodySection = document.createElement('div');
1149
- bodySection.className = 'network-detail-section';
1150
 
1151
  const bodyTitle = document.createElement('div');
1152
- bodyTitle.className = 'network-detail-title';
1153
  bodyTitle.textContent = 'リクエストボディ';
1154
 
1155
  const bodyContent = document.createElement('div');
1156
- bodyContent.className = 'network-detail-content';
1157
 
1158
  try {
1159
  if (typeof selectedRequest.requestBody === 'string') {
@@ -1173,14 +1169,14 @@
1173
  // レスポンス
1174
  if (selectedRequest.response) {
1175
  const responseSection = document.createElement('div');
1176
- responseSection.className = 'network-detail-section';
1177
 
1178
  const responseTitle = document.createElement('div');
1179
- responseTitle.className = 'network-detail-title';
1180
  responseTitle.textContent = 'レスポンス';
1181
 
1182
  const responseContent = document.createElement('div');
1183
- responseContent.className = 'network-detail-content';
1184
 
1185
  if (selectedRequest.response.body) {
1186
  if (typeof selectedRequest.response.body === 'object') {
@@ -1200,14 +1196,14 @@
1200
  // エラー
1201
  if (selectedRequest.error) {
1202
  const errorSection = document.createElement('div');
1203
- errorSection.className = 'network-detail-section';
1204
 
1205
  const errorTitle = document.createElement('div');
1206
- errorTitle.className = 'network-detail-title';
1207
  errorTitle.textContent = 'エラー';
1208
 
1209
  const errorContent = document.createElement('div');
1210
- errorContent.className = 'network-detail-content';
1211
  errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
1212
 
1213
  if (selectedRequest.error.stack) {
@@ -1223,20 +1219,20 @@
1223
  // コンテキストメニュー作成
1224
  function createContextMenu() {
1225
  contextMenu = document.createElement('div');
1226
- contextMenu.className = 'context-menu';
1227
  contextMenu.innerHTML = `
1228
- <div class="context-menu-item" data-action="edit-html">HTMLとして編集</div>
1229
- <div class="context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
1230
- <div class="context-menu-item" data-action="add-attribute">属性を追加</div>
1231
- <div class="context-menu-item" data-action="edit-element">要素を編集</div>
1232
- <div class="context-menu-item" data-action="duplicate">要素を複製</div>
1233
- <div class="context-menu-item" data-action="remove">要素を削除</div>
1234
- <div class="context-menu-item" data-action="toggle-visibility">要素を非表示</div>
1235
- <div class="context-menu-item" data-action="force-state">状態を強制</div>
1236
  `;
1237
  document.body.appendChild(contextMenu);
1238
 
1239
- contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
1240
  item.addEventListener('click', (e) => {
1241
  const action = e.target.getAttribute('data-action');
1242
  handleContextMenuAction(action);
@@ -1254,7 +1250,7 @@
1254
  // Storageパネル作成
1255
  function createStoragePanel() {
1256
  const panel = document.createElement('div');
1257
- panel.className = 'devtools-panel';
1258
  panel.id = 'storage-panel';
1259
 
1260
  // LocalStorage表示
@@ -1263,11 +1259,11 @@
1263
  panel.appendChild(localStorageTitle);
1264
 
1265
  const localStorageTable = document.createElement('table');
1266
- localStorageTable.className = 'storage-table';
1267
  panel.appendChild(localStorageTable);
1268
 
1269
  const addLocalStorageBtn = document.createElement('button');
1270
- addLocalStorageBtn.className = 'add-btn';
1271
  addLocalStorageBtn.textContent = '+ Local Storageに追加';
1272
  addLocalStorageBtn.onclick = () => {
1273
  const key = prompt('キー名を入力');
@@ -1286,11 +1282,11 @@
1286
  panel.appendChild(sessionStorageTitle);
1287
 
1288
  const sessionStorageTable = document.createElement('table');
1289
- sessionStorageTable.className = 'storage-table';
1290
  panel.appendChild(sessionStorageTable);
1291
 
1292
  const addSessionStorageBtn = document.createElement('button');
1293
- addSessionStorageBtn.className = 'add-btn';
1294
  addSessionStorageBtn.textContent = '+ Session Storageに追加';
1295
  addSessionStorageBtn.onclick = () => {
1296
  const key = prompt('キー名を入力');
@@ -1309,11 +1305,11 @@
1309
  panel.appendChild(cookiesTitle);
1310
 
1311
  const cookiesTable = document.createElement('table');
1312
- cookiesTable.className = 'storage-table';
1313
  panel.appendChild(cookiesTable);
1314
 
1315
  const addCookieBtn = document.createElement('button');
1316
- addCookieBtn.className = 'add-btn';
1317
  addCookieBtn.textContent = '+ Cookieに追加';
1318
  addCookieBtn.onclick = () => {
1319
  const name = prompt('Cookie名を入力');
@@ -1354,7 +1350,7 @@
1354
 
1355
  const keyCell = document.createElement('td');
1356
  const keySpan = document.createElement('span');
1357
- keySpan.className = 'editable';
1358
  keySpan.textContent = key;
1359
  keySpan.onclick = () => {
1360
  const newKey = prompt('新しいキー名を入力', key);
@@ -1368,7 +1364,7 @@
1368
 
1369
  const valueCell = document.createElement('td');
1370
  const valueSpan = document.createElement('span');
1371
- valueSpan.className = 'editable';
1372
  valueSpan.textContent = value;
1373
  valueSpan.onclick = () => {
1374
  const newValue = prompt('新しい値を入力', value);
@@ -1380,10 +1376,10 @@
1380
  valueCell.appendChild(valueSpan);
1381
 
1382
  const actionsCell = document.createElement('td');
1383
- actionsCell.className = 'storage-actions';
1384
 
1385
  const deleteBtn = document.createElement('button');
1386
- deleteBtn.className = 'storage-btn';
1387
  deleteBtn.textContent = 'Delete';
1388
  deleteBtn.onclick = () => {
1389
  storage.removeItem(key);
@@ -1425,7 +1421,7 @@
1425
 
1426
  const nameCell = document.createElement('td');
1427
  const nameSpan = document.createElement('span');
1428
- nameSpan.className = 'editable';
1429
  nameSpan.textContent = decodedName;
1430
  nameSpan.onclick = () => {
1431
  const newName = prompt('新しい名前を入力', decodedName);
@@ -1442,7 +1438,7 @@
1442
 
1443
  const valueCell = document.createElement('td');
1444
  const valueSpan = document.createElement('span');
1445
- valueSpan.className = 'editable';
1446
  valueSpan.textContent = decodeURIComponent(value);
1447
  valueSpan.onclick = () => {
1448
  const newValue = prompt('新しい値を入力', decodeURIComponent(value));
@@ -1454,10 +1450,10 @@
1454
  valueCell.appendChild(valueSpan);
1455
 
1456
  const actionsCell = document.createElement('td');
1457
- actionsCell.className = 'storage-actions';
1458
 
1459
  const deleteBtn = document.createElement('button');
1460
- deleteBtn.className = 'storage-btn';
1461
  deleteBtn.textContent = 'Delete';
1462
  deleteBtn.onclick = () => {
1463
  document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
@@ -1559,7 +1555,7 @@
1559
  }
1560
  break;
1561
  case 'edit-element':
1562
- startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
1563
  const newElement = document.createElement(newValue);
1564
  Array.from(selectedElement.attributes).forEach(attr => {
1565
  newElement.setAttribute(attr.name, attr.value);
@@ -1609,7 +1605,7 @@
1609
  const rect = element.getBoundingClientRect();
1610
 
1611
  const input = document.createElement('input');
1612
- input.className = 'dom-edit-input';
1613
  input.value = initialValue || originalValue;
1614
  input.style.position = 'absolute';
1615
  input.style.left = `${rect.left}px`;
@@ -1670,25 +1666,25 @@
1670
  // Consoleパネル作成
1671
  function createConsolePanel() {
1672
  const panel = document.createElement('div');
1673
- panel.className = 'devtools-panel';
1674
  panel.id = 'console-panel';
1675
 
1676
  const log = document.createElement('div');
1677
- log.id = 'console-log';
1678
 
1679
  const input = document.createElement('input');
1680
- input.className = 'console-input';
1681
  input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
1682
  input.onkeypress = (e) => {
1683
  if (e.key === 'Enter') {
1684
  try {
1685
  const result = eval(e.target.value);
1686
  if (result !== undefined) {
1687
- logMessage('> ' + e.target.value, 'console-log');
1688
- logMessage('← ' + formatOutput(result), 'console-log');
1689
  }
1690
  } catch (err) {
1691
- logMessage(err.message, 'console-error');
1692
  }
1693
  e.target.value = '';
1694
  }
@@ -1707,22 +1703,22 @@
1707
 
1708
  console.log = (...args) => {
1709
  originalConsole.log.apply(console, args);
1710
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-log');
1711
  };
1712
 
1713
  console.error = (...args) => {
1714
  originalConsole.error.apply(console, args);
1715
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-error');
1716
  };
1717
 
1718
  console.warn = (...args) => {
1719
  originalConsole.warn.apply(console, args);
1720
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-warn');
1721
  };
1722
 
1723
  console.info = (...args) => {
1724
  originalConsole.info.apply(console, args);
1725
- logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-info');
1726
  };
1727
 
1728
  function logMessage(message, className) {
@@ -1734,23 +1730,23 @@
1734
  }
1735
 
1736
  function formatOutput(output) {
1737
- if (output === null) return '<span class="json-null">null</span>';
1738
- if (output === undefined) return '<span class="json-null">undefined</span>';
1739
- if (typeof output === 'boolean') return `<span class="json-boolean">${output}</span>`;
1740
- if (typeof output === 'number') return `<span class="json-number">${output}</span>`;
1741
- if (typeof output === 'string') return `<span class="json-string">"${output}"</span>`;
1742
- if (typeof output === 'function') return `<span class="json-object">function ${output.name}() { ... }</span>`;
1743
- if (Array.isArray(output)) return `<span class="json-object">[${output.map(formatOutput).join(', ')}]</span>`;
1744
  if (typeof output === 'object') {
1745
  try {
1746
- return `<span class="json-object">${JSON.stringify(output, null, 2)
1747
- .replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
1748
- .replace(/"([^"]+)"/g, '<span class="json-string">"$1"</span>')
1749
- .replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>')
1750
- .replace(/\b(null)\b/g, '<span class="json-null">$1</span>')
1751
- .replace(/\b(\d+)\b/g, '<span class="json-number">$1</span>')}</span>`;
1752
  } catch (e) {
1753
- return `<span class="json-object">${output.toString()}</span>`;
1754
  }
1755
  }
1756
  return output;
@@ -1762,19 +1758,19 @@
1762
  // Elementsパネル作成
1763
  function createElementsPanel() {
1764
  const panel = document.createElement('div');
1765
- panel.className = 'devtools-panel';
1766
  panel.id = 'elements-panel';
1767
 
1768
  const container = document.createElement('div');
1769
- container.className = 'elements-container';
1770
 
1771
  const tree = document.createElement('div');
1772
- tree.className = 'dom-tree';
1773
- tree.id = 'dom-tree';
1774
 
1775
  const cssPanel = document.createElement('div');
1776
- cssPanel.className = 'css-panel';
1777
- cssPanel.id = 'css-panel';
1778
 
1779
  container.appendChild(tree);
1780
  container.appendChild(cssPanel);
@@ -1782,17 +1778,17 @@
1782
 
1783
  // CSSパネル更新関数
1784
  function updateCSSPanel(element) {
1785
- const cssPanel = document.getElementById('css-panel');
1786
  cssPanel.innerHTML = '';
1787
 
1788
  if (!element) return;
1789
 
1790
  if (element.style.length > 0) {
1791
  const inlineRule = document.createElement('div');
1792
- inlineRule.className = 'css-rule';
1793
 
1794
  const selector = document.createElement('div');
1795
- selector.className = 'css-selector';
1796
  selector.textContent = 'インラインスタイル';
1797
  inlineRule.appendChild(selector);
1798
 
@@ -1801,20 +1797,20 @@
1801
  const propValue = element.style[propName];
1802
 
1803
  const propDiv = document.createElement('div');
1804
- propDiv.className = 'css-property';
1805
 
1806
  const nameSpan = document.createElement('span');
1807
- nameSpan.className = 'css-property-name editable';
1808
  nameSpan.textContent = propName;
1809
  nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
1810
 
1811
  const valueSpan = document.createElement('span');
1812
- valueSpan.className = 'css-property-value editable';
1813
  valueSpan.textContent = propValue;
1814
  valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
1815
 
1816
  const toggleSpan = document.createElement('span');
1817
- toggleSpan.className = 'css-toggle';
1818
  toggleSpan.textContent = '×';
1819
  toggleSpan.title = 'プロパティを無効化';
1820
  toggleSpan.onclick = () => {
@@ -1833,10 +1829,10 @@
1833
 
1834
  const computedStyles = window.getComputedStyle(element);
1835
  const computedRule = document.createElement('div');
1836
- computedRule.className = 'css-rule';
1837
 
1838
  const computedSelector = document.createElement('div');
1839
- computedSelector.className = 'css-selector';
1840
  computedSelector.textContent = '計算されたスタイル';
1841
  computedRule.appendChild(computedSelector);
1842
 
@@ -1849,14 +1845,14 @@
1849
  const value = computedStyles[prop];
1850
 
1851
  const propDiv = document.createElement('div');
1852
- propDiv.className = 'css-property';
1853
 
1854
  const nameSpan = document.createElement('span');
1855
- nameSpan.className = 'css-property-name';
1856
  nameSpan.textContent = prop;
1857
 
1858
  const valueSpan = document.createElement('span');
1859
- valueSpan.className = 'css-property-value';
1860
  valueSpan.textContent = value;
1861
 
1862
  propDiv.appendChild(nameSpan);
@@ -1871,9 +1867,9 @@
1871
  function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
1872
  if (node.nodeType === Node.ELEMENT_NODE) {
1873
  const element = document.createElement('div');
1874
- element.className = 'dom-node';
1875
  element.style.marginLeft = `${depth * 15}px`;
1876
- element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
1877
 
1878
  // 右クリックイベント
1879
  element.oncontextmenu = (e) => {
@@ -1881,7 +1877,7 @@
1881
  selectedElement = node;
1882
  selectedDOMNode = element;
1883
 
1884
- document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
1885
  element.classList.add('selected');
1886
 
1887
  if (node !== document.documentElement) {
@@ -1895,13 +1891,13 @@
1895
 
1896
  // 左クリックで選択
1897
  element.onclick = (e) => {
1898
- if (e.target.classList.contains('dom-toggle')) return;
1899
 
1900
  e.stopPropagation();
1901
  selectedElement = node;
1902
  selectedDOMNode = element;
1903
 
1904
- document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
1905
  element.classList.add('selected');
1906
 
1907
  updateCSSPanel(node);
@@ -1913,10 +1909,10 @@
1913
 
1914
  if (hasChildren) {
1915
  const toggle = document.createElement('div');
1916
- toggle.className = 'dom-toggle';
1917
  toggle.onclick = (e) => {
1918
  e.stopPropagation();
1919
- const children = element.querySelector('.dom-children');
1920
  if (children) {
1921
  if (children.style.maxHeight === '0px') {
1922
  children.style.maxHeight = children.scrollHeight + 'px';
@@ -1932,11 +1928,11 @@
1932
 
1933
  // タグ名(HTML要素は編集不可)
1934
  const tag = document.createElement('span');
1935
- tag.className = 'dom-tag';
1936
  tag.textContent = `<${node.tagName.toLowerCase()}`;
1937
 
1938
  if (node !== document.documentElement) {
1939
- tag.classList.add('editable');
1940
  tag.onclick = (e) => {
1941
  e.stopPropagation();
1942
  startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
@@ -1957,11 +1953,11 @@
1957
  // 属性(HTML要素は編集不可)
1958
  Array.from(node.attributes).forEach(attr => {
1959
  const attrSpan = document.createElement('span');
1960
- attrSpan.className = 'dom-attr';
1961
  attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
1962
 
1963
  if (node !== document.documentElement) {
1964
- attrSpan.classList.add('editable');
1965
  attrSpan.onclick = (e) => {
1966
  e.stopPropagation();
1967
  startInlineEdit(attrSpan, attr.value, (newValue) => {
@@ -1978,7 +1974,7 @@
1978
 
1979
  if (hasChildren) {
1980
  const childrenContainer = document.createElement('div');
1981
- childrenContainer.className = 'dom-children';
1982
  childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
1983
 
1984
  node.childNodes.forEach(child => {
@@ -1988,7 +1984,7 @@
1988
  if (node.tagName.toLowerCase() !== 'br') {
1989
  const closeTag = document.createElement('div');
1990
  closeTag.style.marginLeft = `${depth * 15}px`;
1991
- closeTag.innerHTML = `<span class="dom-tag">&lt;/${node.tagName.toLowerCase()}&gt;</span>`;
1992
  childrenContainer.appendChild(closeTag);
1993
  }
1994
 
@@ -1999,7 +1995,7 @@
1999
  } else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
2000
  const text = document.createElement('div');
2001
  text.style.marginLeft = `${depth * 15}px`;
2002
- text.className = 'dom-text editable';
2003
  text.textContent = `"${node.textContent.trim()}"`;
2004
  text.onclick = (e) => {
2005
  e.stopPropagation();
@@ -2034,17 +2030,17 @@
2034
 
2035
  // refreshElementsPanel関数の定義
2036
  refreshElementsPanel = function() {
2037
- let tree = document.getElementById('dom-tree');
2038
 
2039
  if (!tree) {
2040
  const panel = document.getElementById('elements-panel');
2041
  if (panel) {
2042
- const container = panel.querySelector('.elements-container');
2043
  if (container) {
2044
  tree = document.createElement('div');
2045
- tree.className = 'dom-tree';
2046
- tree.id = 'dom-tree';
2047
- container.insertBefore(tree, container.querySelector('.css-panel'));
2048
  }
2049
  }
2050
  }
@@ -2056,10 +2052,10 @@
2056
 
2057
  if (selectedElement) {
2058
  const elementId = selectedElement.id || Array.from(selectedElement.attributes)
2059
- .find(attr => attr.name.startsWith('data-element-id'))?.value;
2060
 
2061
  if (elementId) {
2062
- const node = document.querySelector(`[data-element-id="${elementId}"]`);
2063
  if (node) {
2064
  node.classList.add('selected');
2065
  updateCSSPanel(selectedElement);
@@ -2081,7 +2077,7 @@
2081
 
2082
  // 開発者ツール表示/非表示
2083
  function toggleDevTools() {
2084
- const container = document.getElementById('devtools-container');
2085
  if (container.style.display === 'none') {
2086
  container.style.display = 'flex';
2087
  } else {
@@ -2092,13 +2088,13 @@
2092
  // 開くボタン作成
2093
  function createOpenButton() {
2094
  const button = document.createElement('button');
2095
- button.id = 'open-devtools-btn';
2096
  button.textContent = '開発者ツールを開く';
2097
  button.style.position = 'fixed';
2098
  button.style.bottom = '10px';
2099
  button.style.right = '10px';
2100
  button.style.padding = '8px 16px';
2101
- button.style.background = 'var(--primary-color)';
2102
  button.style.color = '#000';
2103
  button.style.border = 'none';
2104
  button.style.borderRadius = '4px';
@@ -2111,9 +2107,26 @@
2111
 
2112
  // 初期化
2113
  document.addEventListener('DOMContentLoaded', function() {
2114
- createDevTools();
2115
  createOpenButton();
2116
- console.log('開発者ツールが初期化されました');
2117
- console.log('このコンソールでJavaScriptを実行できます');
 
 
 
 
 
2118
  });
 
 
 
 
 
 
 
 
 
 
 
 
2119
  })();
 
17
  let refreshElementsPanel = null;
18
  let vitalsMeasurementStarted = false;
19
 
20
+ // スタイルの追加
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  const style = document.createElement('style');
22
  style.textContent = `
23
+ .dt-root {
24
+ --dt-bg-color: #1e1e1e;
25
+ --dt-panel-bg: #252526;
26
+ --dt-border-color: #3c3c3c;
27
+ --dt-text-color: #e0e0e0;
28
+ --dt-text-muted: #a0a0a0;
29
+ --dt-primary-color: #007acc;
30
+ --dt-primary-hover: #3e9fda;
31
+ --dt-success-color: #4caf50;
32
+ --dt-error-color: #f44336;
33
+ --dt-warning-color: #ff9800;
34
+ --dt-info-color: #2196f3;
35
+ --dt-highlight-bg: rgba(0, 122, 204, 0.2);
36
+ --dt-tab-bg: #2d2d2d;
37
+ --dt-tab-active-bg: #1e1e1e;
38
+ --dt-console-log-color: #e0e0e0;
39
+ --dt-console-error-color: #f44336;
40
+ --dt-console-warn-color: #ff9800;
41
+ --dt-console-info-color: #4fc3f7;
42
+ --dt-json-key: #9cdcfe;
43
+ --dt-json-string: #ce9178;
44
+ --dt-json-number: #b5cea8;
45
+ --dt-json-boolean: #569cd6;
46
+ --dt-json-null: #569cd6;
47
+ --dt-dom-tag: #569cd6;
48
+ --dt-dom-attr: #9cdcfe;
49
+ --dt-dom-text: #d4d4d4;
50
+ }
51
+
52
+ .dt-devtools-container {
53
  position: fixed;
54
  bottom: 0;
55
  left: 0;
56
  width: 100%;
57
  height: 300px;
58
+ background-color: var(--dt-panel-bg);
59
+ border-top: 1px solid var(--dt-border-color);
60
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
61
  z-index: 9999;
62
  display: flex;
63
  flex-direction: column;
64
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
65
+ color: var(--dt-text-color);
66
  }
67
 
68
+ .dt-devtools-header {
69
  display: flex;
70
  justify-content: space-between;
71
  align-items: center;
72
  padding: 5px 10px;
73
+ background-color: var(--dt-tab-bg);
74
+ border-bottom: 1px solid var(--dt-border-color);
75
  }
76
 
77
+ .dt-devtools-tabs {
78
  display: flex;
79
  gap: 5px;
80
  }
81
 
82
+ .dt-devtools-tab {
83
  padding: 5px 10px;
84
  cursor: pointer;
85
  border-radius: 3px 3px 0 0;
86
+ background-color: var(--dt-tab-bg);
87
+ border: 1px solid var(--dt-border-color);
88
  border-bottom: none;
89
  font-size: 12px;
90
+ color: var(--dt-text-muted);
91
  }
92
 
93
+ .dt-devtools-tab.active {
94
+ background-color: var(--dt-tab-active-bg);
95
+ color: var(--dt-text-color);
96
+ border-bottom: 1px solid var(--dt-tab-active-bg);
97
  margin-bottom: -1px;
98
  font-weight: bold;
99
  }
100
 
101
+ .dt-devtools-close {
102
  background: none;
103
  border: none;
104
  font-size: 16px;
105
  cursor: pointer;
106
  padding: 0 5px;
107
+ color: var(--dt-text-color);
108
  }
109
 
110
+ .dt-devtools-content {
111
  flex: 1;
112
  overflow: auto;
113
  position: relative;
114
+ background-color: var(--dt-panel-bg);
115
  }
116
 
117
+ .dt-devtools-panel {
118
  position: absolute;
119
  top: 0;
120
  left: 0;
 
123
  padding: 10px;
124
  overflow: auto;
125
  display: none;
126
+ background-color: var(--dt-panel-bg);
127
  }
128
 
129
+ .dt-devtools-panel.active {
130
  display: block;
131
  }
132
 
133
  /* Console スタイル */
134
+ #dt-console-log {
135
  white-space: pre-wrap;
136
  margin: 0;
137
  line-height: 1.4;
138
  flex: 1;
139
+ color: var(--dt-console-log-color);
140
  font-family: 'Consolas', 'Monaco', monospace;
141
  font-size: 13px;
142
  }
143
 
144
+ .dt-console-log {
145
+ color: var(--dt-console-log-color);
146
  }
147
 
148
+ .dt-console-error {
149
+ color: var(--dt-console-error-color);
150
  }
151
 
152
+ .dt-console-warn {
153
+ color: var(--dt-console-warn-color);
154
  }
155
 
156
+ .dt-console-info {
157
+ color: var(--dt-console-info-color);
158
  }
159
 
160
+ .dt-console-input {
161
  width: calc(100% - 16px);
162
+ background: var(--dt-tab-bg);
163
+ border: 1px solid var(--dt-border-color);
164
+ color: var(--dt-text-color);
165
  padding: 8px;
166
  margin-top: 10px;
167
  font-family: monospace;
 
169
  }
170
 
171
  /* Elements スタイル */
172
+ .dt-elements-container {
173
  display: flex;
174
  flex: 1;
175
  overflow: hidden;
176
  }
177
 
178
+ .dt-dom-tree {
179
  font-family: 'Consolas', 'Monaco', monospace;
180
  flex: 1;
181
  overflow: auto;
182
+ border-right: 1px solid var(--dt-border-color);
183
  padding-right: 10px;
184
+ color: var(--dt-dom-text);
185
  font-size: 13px;
186
  }
187
 
188
+ .dt-dom-node {
189
  margin-left: 15px;
190
  position: relative;
191
  line-height: 1.4;
192
  transition: background-color 0.3s;
193
  }
194
 
195
+ .dt-dom-node.selected {
196
+ background: var(--dt-highlight-bg);
197
  }
198
 
199
+ .dt-dom-node.highlight {
200
+ animation: dt-highlight-fade 1.5s;
201
  }
202
 
203
+ @keyframes dt-highlight-fade {
204
  0% { background-color: rgba(79, 195, 247, 0.5); }
205
  100% { background-color: transparent; }
206
  }
207
 
208
+ .dt-dom-tag {
209
+ color: var(--dt-dom-tag);
210
  font-weight: bold;
211
  }
212
 
213
+ .dt-dom-attr {
214
+ color: var(--dt-dom-attr);
215
  }
216
 
217
+ .dt-dom-attr.editable:hover {
218
  text-decoration: underline;
219
  cursor: pointer;
220
  }
221
 
222
+ .dt-dom-text {
223
+ color: var(--dt-dom-text);
224
  }
225
 
226
+ .dt-dom-edit-input {
227
+ background: var(--dt-panel-bg);
228
+ border: 1px solid var(--dt-primary-color);
229
  padding: 0 2px;
230
  margin: -1px 0;
231
  font-family: monospace;
232
  min-width: 50px;
233
+ color: var(--dt-text-color);
234
  }
235
 
236
+ .dt-css-panel {
237
  flex: 1;
238
  overflow: auto;
239
  padding-left: 10px;
240
  font-size: 13px;
241
  }
242
 
243
+ .dt-css-rule {
244
  margin-bottom: 15px;
245
+ border: 1px solid var(--dt-border-color);
246
  padding: 8px;
247
+ background-color: var(--dt-tab-bg);
248
  border-radius: 3px;
249
  }
250
 
251
+ .dt-css-selector {
252
+ color: var(--dt-primary-color);
253
  margin-bottom: 5px;
254
  font-weight: bold;
255
  }
256
 
257
+ .dt-css-property {
258
  display: flex;
259
  margin-bottom: 3px;
260
  }
261
 
262
+ .dt-css-property-name {
263
+ color: var(--dt-dom-attr);
264
  min-width: 120px;
265
  }
266
 
267
+ .dt-css-property-value {
268
+ color: var(--dt-dom-text);
269
  flex: 1;
270
  }
271
 
272
+ .dt-css-toggle {
273
  margin-left: 10px;
274
+ color: var(--dt-error-color);
275
  cursor: pointer;
276
  }
277
 
278
  /* Context Menu */
279
+ .dt-context-menu {
280
  position: absolute;
281
+ background: var(--dt-panel-bg);
282
+ border: 1px solid var(--dt-border-color);
283
  z-index: 10000;
284
  min-width: 200px;
285
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
 
288
  overflow: hidden;
289
  }
290
 
291
+ .dt-context-menu-item {
292
  padding: 8px 15px;
293
  cursor: pointer;
294
+ color: var(--dt-text-color);
295
  font-size: 13px;
296
  }
297
 
298
+ .dt-context-menu-item:hover {
299
+ background: var(--dt-primary-color);
300
  color: #000;
301
  }
302
 
303
  /* Storage スタイル */
304
+ .dt-storage-table {
305
  width: 100%;
306
  border-collapse: collapse;
307
  margin-bottom: 10px;
308
  font-size: 13px;
309
  }
310
 
311
+ .dt-storage-table th, .dt-storage-table td {
312
+ border: 1px solid var(--dt-border-color);
313
  padding: 5px;
314
  text-align: left;
315
  }
316
 
317
+ .dt-storage-table th {
318
+ background: var(--dt-tab-bg);
319
  }
320
 
321
+ .dt-storage-actions {
322
  display: flex;
323
  gap: 5px;
324
  }
325
 
326
+ .dt-storage-btn {
327
+ background: var(--dt-primary-color);
328
  border: none;
329
  padding: 2px 5px;
330
  cursor: pointer;
 
333
  font-size: 12px;
334
  }
335
 
336
+ .dt-editable {
337
  cursor: pointer;
338
  padding: 2px 5px;
339
  border: 1px dashed transparent;
340
  }
341
 
342
+ .dt-editable:hover {
343
+ border-color: var(--dt-primary-color);
344
  }
345
 
346
+ .dt-add-btn {
347
+ background: var(--dt-primary-color);
348
  color: #000;
349
  border: none;
350
  padding: 5px 10px;
 
354
  font-size: 13px;
355
  }
356
 
357
+ .dt-add-btn:hover {
358
+ background: var(--dt-primary-hover);
359
  }
360
 
361
  /* Network スタイル */
362
+ .dt-network-container {
363
  display: flex;
364
  height: 100%;
365
  overflow: hidden;
366
  }
367
 
368
+ .dt-network-requests {
369
  width: 40%;
370
  overflow-y: auto;
371
+ border-right: 1px solid var(--dt-border-color);
372
  font-size: 13px;
373
  }
374
 
375
+ .dt-network-details {
376
  width: 60%;
377
  overflow-y: auto;
378
  padding-left: 10px;
379
  }
380
 
381
+ .dt-network-request {
382
  padding: 8px;
383
+ border-bottom: 1px solid var(--dt-border-color);
384
  cursor: pointer;
385
  display: flex;
386
  align-items: center;
387
  }
388
 
389
+ .dt-network-request:hover {
390
  background-color: rgba(0, 122, 204, 0.1);
391
  }
392
 
393
+ .dt-network-request.selected {
394
+ background-color: var(--dt-highlight-bg);
395
  }
396
 
397
+ .dt-network-status {
398
  width: 20px;
399
  height: 20px;
400
  border-radius: 50%;
 
405
  flex-shrink: 0;
406
  }
407
 
408
+ .dt-network-status.success {
409
+ background-color: var(--dt-success-color);
410
  color: white;
411
  }
412
 
413
+ .dt-network-status.error {
414
+ background-color: var(--dt-error-color);
415
  color: white;
416
  }
417
 
418
+ .dt-network-method {
419
  font-weight: bold;
420
  margin-right: 8px;
421
+ color: var(--dt-primary-color);
422
  min-width: 40px;
423
  }
424
 
425
+ .dt-network-url {
426
  flex: 1;
427
  white-space: nowrap;
428
  overflow: hidden;
429
  text-overflow: ellipsis;
430
  }
431
 
432
+ .dt-network-time {
433
+ color: var(--dt-text-muted);
434
  font-size: 11px;
435
  margin-left: 8px;
436
  }
437
 
438
+ .dt-network-detail-section {
439
  margin-bottom: 15px;
440
  }
441
 
442
+ .dt-network-detail-title {
443
  font-weight: bold;
444
  margin-bottom: 5px;
445
+ color: var(--dt-primary-color);
446
  }
447
 
448
+ .dt-network-detail-content {
449
+ background: var(--dt-tab-bg);
450
  padding: 8px;
451
  border-radius: 3px;
452
+ border: 1px solid var(--dt-border-color);
453
  font-family: monospace;
454
  white-space: pre-wrap;
455
  font-size: 12px;
 
458
  }
459
 
460
  /* Web Vitals スタイル */
461
+ .dt-vitals-container {
462
  display: flex;
463
  flex-direction: column;
464
  gap: 15px;
465
  }
466
 
467
+ .dt-vital-card {
468
+ background: var(--dt-tab-bg);
469
+ border: 1px solid var(--dt-border-color);
470
  border-radius: 5px;
471
  padding: 15px;
472
  }
473
 
474
+ .dt-vital-title {
475
  font-weight: bold;
476
  margin-bottom: 10px;
477
+ color: var(--dt-primary-color);
478
  display: flex;
479
  justify-content: space-between;
480
  align-items: center;
481
  }
482
 
483
+ .dt-vital-value {
484
  font-size: 24px;
485
  font-weight: bold;
486
  margin: 10px 0;
487
  }
488
 
489
+ .dt-vital-good {
490
+ color: var(--dt-success-color);
491
  }
492
 
493
+ .dt-vital-needs-improvement {
494
+ color: var(--dt-warning-color);
495
  }
496
 
497
+ .dt-vital-poor {
498
+ color: var(--dt-error-color);
499
  }
500
 
501
+ .dt-vital-description {
502
  font-size: 13px;
503
+ color: var(--dt-text-muted);
504
  }
505
 
506
+ .dt-vital-thresholds {
507
  display: flex;
508
  margin-top: 10px;
509
  font-size: 12px;
510
  }
511
 
512
+ .dt-vital-threshold {
513
  flex: 1;
514
  text-align: center;
515
  padding: 5px;
516
  border-radius: 3px;
517
  }
518
 
519
+ .dt-vital-threshold.active {
520
  background: rgba(0, 122, 204, 0.2);
521
  }
522
 
523
  /* DOM Tree Toggle */
524
+ .dt-dom-toggle {
525
  position: absolute;
526
  left: -12px;
527
  top: 2px;
528
  width: 10px;
529
  height: 10px;
530
  cursor: pointer;
531
+ background-color: var(--dt-text-muted);
532
  clip-path: polygon(0 0, 100% 50%, 0 100%);
533
  transition: transform 0.2s;
534
  }
535
 
536
+ .dt-dom-toggle.collapsed {
537
  transform: rotate(-90deg);
538
  }
539
 
540
+ .dt-dom-children {
541
  overflow: hidden;
542
  transition: max-height 0.3s ease-out;
543
  }
544
 
545
  /* JSON スタイル */
546
+ .dt-json-key {
547
+ color: var(--dt-json-key);
548
  }
549
 
550
+ .dt-json-string {
551
+ color: var(--dt-json-string);
552
  }
553
 
554
+ .dt-json-number {
555
+ color: var(--dt-json-number);
556
  }
557
 
558
+ .dt-json-boolean {
559
+ color: var(--dt-json-boolean);
560
  }
561
 
562
+ .dt-json-null {
563
+ color: var(--dt-json-null);
564
  }
565
  `;
566
  document.head.appendChild(style);
567
+
568
  // ノードをハイライト表示
569
  function highlightNode(node) {
570
  const elementId = node.id || Math.random().toString(36).substr(2, 9);
571
+ node.setAttribute('data-dt-element-id', elementId);
572
 
573
  setTimeout(() => {
574
+ const domNode = document.querySelector(`[data-dt-element-id="${elementId}"]`);
575
  if (domNode) {
576
+ domNode.classList.add('dt-highlight');
577
  setTimeout(() => {
578
+ domNode.classList.remove('dt-highlight');
579
  }, 1500);
580
  }
581
  }, 100);
582
  }
583
 
584
+ // Web Vitalsスクリプトを動的に読み込み
585
+ const loadWebVitals = () => {
586
+ return new Promise((resolve) => {
587
+ if (window.webVitals) {
588
+ resolve();
589
+ return;
590
+ }
591
+
592
+ const webVitalsScript = document.createElement('script');
593
+ webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
594
+ webVitalsScript.onload = resolve;
595
+ document.head.appendChild(webVitalsScript);
596
+ });
597
+ };
598
+
599
+ // DOM変更を監視するMutationObserver
600
+ const setupMutationObserver = () => {
601
+ if (observer) observer.disconnect();
602
+
603
+ observer = new MutationObserver((mutations) => {
604
+ if (refreshElementsPanel) {
605
+ refreshElementsPanel();
606
+ }
607
+ });
608
+
609
+ observer.observe(document.documentElement, {
610
+ childList: true,
611
+ subtree: true,
612
+ attributes: false,
613
+ characterData: false
614
+ });
615
+ };
616
+
617
  // 開発者ツールのメイン関数
618
  const createDevTools = () => {
619
  const container = document.createElement('div');
620
+ container.className = 'dt-devtools-container';
621
+ container.id = 'dt-devtools-container';
622
  container.style.display = 'none';
623
 
624
  // ヘッダー部分
625
  const header = document.createElement('div');
626
+ header.className = 'dt-devtools-header';
627
 
628
  const tabs = document.createElement('div');
629
+ tabs.className = 'dt-devtools-tabs';
630
 
631
  const consoleTab = createTab('Console', 'console');
632
  const elementsTab = createTab('Elements', 'elements');
 
641
  tabs.appendChild(vitalsTab);
642
 
643
  const closeBtn = document.createElement('button');
644
+ closeBtn.className = 'dt-devtools-close';
645
  closeBtn.textContent = '×';
646
  closeBtn.onclick = toggleDevTools;
647
 
 
650
 
651
  // コンテンツ部分
652
  const content = document.createElement('div');
653
+ content.className = 'dt-devtools-content';
654
 
655
  const consolePanel = createConsolePanel();
656
  const elementsPanel = createElementsPanel();
 
675
  // タブ切り替え機能
676
  function createTab(name, panelId) {
677
  const tab = document.createElement('div');
678
+ tab.className = 'dt-devtools-tab';
679
  tab.textContent = name;
680
  tab.onclick = () => {
681
+ document.querySelectorAll('.dt-devtools-tab').forEach(t => t.classList.remove('active'));
682
+ document.querySelectorAll('.dt-devtools-panel').forEach(p => p.classList.remove('active'));
683
  tab.classList.add('active');
684
  document.getElementById(panelId + '-panel').classList.add('active');
685
 
 
696
  // Web Vitalsパネル作成
697
  function createVitalsPanel() {
698
  const panel = document.createElement('div');
699
+ panel.className = 'dt-devtools-panel';
700
  panel.id = 'vitals-panel';
701
 
702
  const container = document.createElement('div');
703
+ container.className = 'dt-vitals-container';
704
  panel.appendChild(container);
705
 
706
  // 測定開始ボタン
707
  const startButton = document.createElement('button');
708
+ startButton.className = 'dt-add-btn';
709
  startButton.textContent = '測定を開始';
710
  startButton.onclick = startVitalsMeasurement;
711
  container.appendChild(startButton);
712
 
713
  // CLS (Cumulative Layout Shift)
714
  const clsCard = document.createElement('div');
715
+ clsCard.className = 'dt-vital-card';
716
  clsCard.innerHTML = `
717
+ <div class="dt-vital-title">CLS (Cumulative Layout Shift) <span class="dt-vital-description">視覚的な安定性</span></div>
718
+ <div class="dt-vital-value" id="dt-cls-value">-</div>
719
+ <div class="dt-vital-thresholds">
720
+ <div class="dt-vital-threshold">Good: &lt; 0.1</div>
721
+ <div class="dt-vital-threshold">Needs Improvement: &lt; 0.25</div>
722
+ <div class="dt-vital-threshold">Poor: ≥ 0.25</div>
723
  </div>
724
  `;
725
  container.appendChild(clsCard);
726
 
727
  // FCP (First Contentful Paint)
728
  const fcpCard = document.createElement('div');
729
+ fcpCard.className = 'dt-vital-card';
730
  fcpCard.innerHTML = `
731
+ <div class="dt-vital-title">FCP (First Contentful Paint) <span class="dt-vital-description">最初のコンテンツ表示</span></div>
732
+ <div class="dt-vital-value" id="dt-fcp-value">-</div>
733
+ <div class="dt-vital-thresholds">
734
+ <div class="dt-vital-threshold">Good: &lt; 1.8s</div>
735
+ <div class="dt-vital-threshold">Needs Improvement: &lt; 3s</div>
736
+ <div class="dt-vital-threshold">Poor: ≥ 3s</div>
737
  </div>
738
  `;
739
  container.appendChild(fcpCard);
740
 
741
  // FID (First Input Delay)
742
  const fidCard = document.createElement('div');
743
+ fidCard.className = 'dt-vital-card';
744
  fidCard.innerHTML = `
745
+ <div class="dt-vital-title">FID (First Input Delay) <span class="dt-vital-description">最初の入力遅延</span></div>
746
+ <div class="dt-vital-value" id="dt-fid-value">-</div>
747
+ <div class="dt-vital-thresholds">
748
+ <div class="dt-vital-threshold">Good: &lt; 100ms</div>
749
+ <div class="dt-vital-threshold">Needs Improvement: &lt; 300ms</div>
750
+ <div class="dt-vital-threshold">Poor: ≥ 300ms</div>
751
  </div>
752
  `;
753
  container.appendChild(fidCard);
754
 
755
  // LCP (Largest Contentful Paint)
756
  const lcpCard = document.createElement('div');
757
+ lcpCard.className = 'dt-vital-card';
758
  lcpCard.innerHTML = `
759
+ <div class="dt-vital-title">LCP (Largest Contentful Paint) <span class="dt-vital-description">最大のコンテンツ表示</span></div>
760
+ <div class="dt-vital-value" id="dt-lcp-value">-</div>
761
+ <div class="dt-vital-thresholds">
762
+ <div class="dt-vital-threshold">Good: &lt; 2.5s</div>
763
+ <div class="dt-vital-threshold">Needs Improvement: &lt; 4s</div>
764
+ <div class="dt-vital-threshold">Poor: ≥ 4s</div>
765
  </div>
766
  `;
767
  container.appendChild(lcpCard);
768
 
769
  // TTFB (Time to First Byte)
770
  const ttfbCard = document.createElement('div');
771
+ ttfbCard.className = 'dt-vital-card';
772
  ttfbCard.innerHTML = `
773
+ <div class="dt-vital-title">TTFB (Time to First Byte) <span class="dt-vital-description">最初のバイト到達時間</span></div>
774
+ <div class="dt-vital-value" id="dt-ttfb-value">-</div>
775
+ <div class="dt-vital-thresholds">
776
+ <div class="dt-vital-threshold">Good: &lt; 800ms</div>
777
+ <div class="dt-vital-threshold">Needs Improvement: &lt; 1.8s</div>
778
+ <div class="dt-vital-threshold">Poor: ≥ 1.8s</div>
779
  </div>
780
  `;
781
  container.appendChild(ttfbCard);
 
801
  vitals.onLCP(updateLCP);
802
  vitals.onTTFB(updateTTFB);
803
  }
804
+ logMessage('Web Vitalsの測定を開始しました', 'dt-console-info');
805
  } catch (e) {
806
+ logMessage('Web Vitals error: ' + e.message, 'dt-console-error');
807
  }
808
  }
809
  });
 
835
  }
836
 
837
  function updateVitalDisplay(id, value, goodThreshold, needsImprovementThreshold) {
838
+ const element = document.getElementById(`dt-${id}-value`);
839
  if (!element) return;
840
 
841
  const displayValue = id === 'ttfb' ?
 
844
 
845
  element.textContent = displayValue;
846
 
847
+ element.className = 'dt-vital-value';
848
  if (value <= goodThreshold) {
849
+ element.classList.add('dt-vital-good');
850
  } else if (value <= needsImprovementThreshold) {
851
+ element.classList.add('dt-vital-needs-improvement');
852
  } else {
853
+ element.classList.add('dt-vital-poor');
854
  }
855
 
856
+ const thresholds = element.parentElement.querySelectorAll('.dt-vital-threshold');
857
  thresholds.forEach((threshold, index) => {
858
  threshold.classList.remove('active');
859
  if (
 
872
  // ネットワークパネル作成
873
  function createNetworkPanel() {
874
  const panel = document.createElement('div');
875
+ panel.className = 'dt-devtools-panel';
876
  panel.id = 'network-panel';
877
 
878
  const container = document.createElement('div');
879
+ container.className = 'dt-network-container';
880
  panel.appendChild(container);
881
 
882
  // リクエストリスト
883
  const requestsList = document.createElement('div');
884
+ requestsList.className = 'dt-network-requests';
885
  container.appendChild(requestsList);
886
 
887
  // 詳細パネル
888
  const detailsPanel = document.createElement('div');
889
+ detailsPanel.className = 'dt-network-details';
890
  container.appendChild(detailsPanel);
891
 
892
  // ネットワークリクエストを監視
 
1047
  const panel = document.getElementById('network-panel');
1048
  if (!panel || !panel.classList.contains('active')) return;
1049
 
1050
+ const requestsList = panel.querySelector('.dt-network-requests');
1051
+ const detailsPanel = panel.querySelector('.dt-network-details');
1052
 
1053
  requestsList.innerHTML = '';
1054
 
1055
  networkRequests.forEach(request => {
1056
  const requestElement = document.createElement('div');
1057
+ requestElement.className = 'dt-network-request';
1058
  if (selectedRequest && selectedRequest.id === request.id) {
1059
  requestElement.classList.add('selected');
1060
  }
 
1066
  };
1067
 
1068
  const statusElement = document.createElement('div');
1069
+ statusElement.className = `dt-network-status ${request.status}`;
1070
  statusElement.textContent = request.status === 'success' ? '✓' : '✕';
1071
 
1072
  const methodElement = document.createElement('div');
1073
+ methodElement.className = 'dt-network-method';
1074
  methodElement.textContent = request.method;
1075
 
1076
  const urlElement = document.createElement('div');
1077
+ urlElement.className = 'dt-network-url';
1078
  urlElement.textContent = request.url;
1079
 
1080
  const timeElement = document.createElement('div');
1081
+ timeElement.className = 'dt-network-time';
1082
  timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
1083
 
1084
  requestElement.appendChild(statusElement);
 
1095
  const panel = document.getElementById('network-panel');
1096
  if (!panel || !selectedRequest) return;
1097
 
1098
+ const detailsPanel = panel.querySelector('.dt-network-details');
1099
  detailsPanel.innerHTML = '';
1100
 
1101
  // 一般情報
1102
  const generalSection = document.createElement('div');
1103
+ generalSection.className = 'dt-network-detail-section';
1104
  generalSection.innerHTML = `
1105
+ <div class="dt-network-detail-title">一般</div>
1106
+ <div class="dt-network-detail-content">
1107
  <strong>URL:</strong> ${selectedRequest.url}<br>
1108
  <strong>メソッド:</strong> ${selectedRequest.method}<br>
1109
  <strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
 
1115
  // リクエストヘッダー
1116
  if (selectedRequest.requestHeaders) {
1117
  const headersSection = document.createElement('div');
1118
+ headersSection.className = 'dt-network-detail-section';
1119
 
1120
  const headersTitle = document.createElement('div');
1121
+ headersTitle.className = 'dt-network-detail-title';
1122
  headersTitle.textContent = 'リクエストヘッダー';
1123
 
1124
  const headersContent = document.createElement('div');
1125
+ headersContent.className = 'dt-network-detail-content';
1126
 
1127
  if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
1128
  Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
 
1142
  // リクエストボディ
1143
  if (selectedRequest.requestBody) {
1144
  const bodySection = document.createElement('div');
1145
+ bodySection.className = 'dt-network-detail-section';
1146
 
1147
  const bodyTitle = document.createElement('div');
1148
+ bodyTitle.className = 'dt-network-detail-title';
1149
  bodyTitle.textContent = 'リクエストボディ';
1150
 
1151
  const bodyContent = document.createElement('div');
1152
+ bodyContent.className = 'dt-network-detail-content';
1153
 
1154
  try {
1155
  if (typeof selectedRequest.requestBody === 'string') {
 
1169
  // レスポンス
1170
  if (selectedRequest.response) {
1171
  const responseSection = document.createElement('div');
1172
+ responseSection.className = 'dt-network-detail-section';
1173
 
1174
  const responseTitle = document.createElement('div');
1175
+ responseTitle.className = 'dt-network-detail-title';
1176
  responseTitle.textContent = 'レスポンス';
1177
 
1178
  const responseContent = document.createElement('div');
1179
+ responseContent.className = 'dt-network-detail-content';
1180
 
1181
  if (selectedRequest.response.body) {
1182
  if (typeof selectedRequest.response.body === 'object') {
 
1196
  // エラー
1197
  if (selectedRequest.error) {
1198
  const errorSection = document.createElement('div');
1199
+ errorSection.className = 'dt-network-detail-section';
1200
 
1201
  const errorTitle = document.createElement('div');
1202
+ errorTitle.className = 'dt-network-detail-title';
1203
  errorTitle.textContent = 'エラー';
1204
 
1205
  const errorContent = document.createElement('div');
1206
+ errorContent.className = 'dt-network-detail-content';
1207
  errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
1208
 
1209
  if (selectedRequest.error.stack) {
 
1219
  // コンテキストメニュー作成
1220
  function createContextMenu() {
1221
  contextMenu = document.createElement('div');
1222
+ contextMenu.className = 'dt-context-menu';
1223
  contextMenu.innerHTML = `
1224
+ <div class="dt-context-menu-item" data-action="edit-html">HTMLとして編集</div>
1225
+ <div class="dt-context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
1226
+ <div class="dt-context-menu-item" data-action="add-attribute">属性を追加</div>
1227
+ <div class="dt-context-menu-item" data-action="edit-element">要素を編集</div>
1228
+ <div class="dt-context-menu-item" data-action="duplicate">要素を複製</div>
1229
+ <div class="dt-context-menu-item" data-action="remove">要素を削除</div>
1230
+ <div class="dt-context-menu-item" data-action="toggle-visibility">要素を非表示</div>
1231
+ <div class="dt-context-menu-item" data-action="force-state">状態を強制</div>
1232
  `;
1233
  document.body.appendChild(contextMenu);
1234
 
1235
+ contextMenu.querySelectorAll('.dt-context-menu-item').forEach(item => {
1236
  item.addEventListener('click', (e) => {
1237
  const action = e.target.getAttribute('data-action');
1238
  handleContextMenuAction(action);
 
1250
  // Storageパネル作成
1251
  function createStoragePanel() {
1252
  const panel = document.createElement('div');
1253
+ panel.className = 'dt-devtools-panel';
1254
  panel.id = 'storage-panel';
1255
 
1256
  // LocalStorage表示
 
1259
  panel.appendChild(localStorageTitle);
1260
 
1261
  const localStorageTable = document.createElement('table');
1262
+ localStorageTable.className = 'dt-storage-table';
1263
  panel.appendChild(localStorageTable);
1264
 
1265
  const addLocalStorageBtn = document.createElement('button');
1266
+ addLocalStorageBtn.className = 'dt-add-btn';
1267
  addLocalStorageBtn.textContent = '+ Local Storageに追加';
1268
  addLocalStorageBtn.onclick = () => {
1269
  const key = prompt('キー名を入力');
 
1282
  panel.appendChild(sessionStorageTitle);
1283
 
1284
  const sessionStorageTable = document.createElement('table');
1285
+ sessionStorageTable.className = 'dt-storage-table';
1286
  panel.appendChild(sessionStorageTable);
1287
 
1288
  const addSessionStorageBtn = document.createElement('button');
1289
+ addSessionStorageBtn.className = 'dt-add-btn';
1290
  addSessionStorageBtn.textContent = '+ Session Storageに追加';
1291
  addSessionStorageBtn.onclick = () => {
1292
  const key = prompt('キー名を入力');
 
1305
  panel.appendChild(cookiesTitle);
1306
 
1307
  const cookiesTable = document.createElement('table');
1308
+ cookiesTable.className = 'dt-storage-table';
1309
  panel.appendChild(cookiesTable);
1310
 
1311
  const addCookieBtn = document.createElement('button');
1312
+ addCookieBtn.className = 'dt-add-btn';
1313
  addCookieBtn.textContent = '+ Cookieに追加';
1314
  addCookieBtn.onclick = () => {
1315
  const name = prompt('Cookie名を入力');
 
1350
 
1351
  const keyCell = document.createElement('td');
1352
  const keySpan = document.createElement('span');
1353
+ keySpan.className = 'dt-editable';
1354
  keySpan.textContent = key;
1355
  keySpan.onclick = () => {
1356
  const newKey = prompt('新しいキー名を入力', key);
 
1364
 
1365
  const valueCell = document.createElement('td');
1366
  const valueSpan = document.createElement('span');
1367
+ valueSpan.className = 'dt-editable';
1368
  valueSpan.textContent = value;
1369
  valueSpan.onclick = () => {
1370
  const newValue = prompt('新しい値を入力', value);
 
1376
  valueCell.appendChild(valueSpan);
1377
 
1378
  const actionsCell = document.createElement('td');
1379
+ actionsCell.className = 'dt-storage-actions';
1380
 
1381
  const deleteBtn = document.createElement('button');
1382
+ deleteBtn.className = 'dt-storage-btn';
1383
  deleteBtn.textContent = 'Delete';
1384
  deleteBtn.onclick = () => {
1385
  storage.removeItem(key);
 
1421
 
1422
  const nameCell = document.createElement('td');
1423
  const nameSpan = document.createElement('span');
1424
+ nameSpan.className = 'dt-editable';
1425
  nameSpan.textContent = decodedName;
1426
  nameSpan.onclick = () => {
1427
  const newName = prompt('新しい名前を入力', decodedName);
 
1438
 
1439
  const valueCell = document.createElement('td');
1440
  const valueSpan = document.createElement('span');
1441
+ valueSpan.className = 'dt-editable';
1442
  valueSpan.textContent = decodeURIComponent(value);
1443
  valueSpan.onclick = () => {
1444
  const newValue = prompt('新しい値を入力', decodeURIComponent(value));
 
1450
  valueCell.appendChild(valueSpan);
1451
 
1452
  const actionsCell = document.createElement('td');
1453
+ actionsCell.className = 'dt-storage-actions';
1454
 
1455
  const deleteBtn = document.createElement('button');
1456
+ deleteBtn.className = 'dt-storage-btn';
1457
  deleteBtn.textContent = 'Delete';
1458
  deleteBtn.onclick = () => {
1459
  document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
 
1555
  }
1556
  break;
1557
  case 'edit-element':
1558
+ startInlineEdit(selectedDOMNode.querySelector('.dt-dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
1559
  const newElement = document.createElement(newValue);
1560
  Array.from(selectedElement.attributes).forEach(attr => {
1561
  newElement.setAttribute(attr.name, attr.value);
 
1605
  const rect = element.getBoundingClientRect();
1606
 
1607
  const input = document.createElement('input');
1608
+ input.className = 'dt-dom-edit-input';
1609
  input.value = initialValue || originalValue;
1610
  input.style.position = 'absolute';
1611
  input.style.left = `${rect.left}px`;
 
1666
  // Consoleパネル作成
1667
  function createConsolePanel() {
1668
  const panel = document.createElement('div');
1669
+ panel.className = 'dt-devtools-panel';
1670
  panel.id = 'console-panel';
1671
 
1672
  const log = document.createElement('div');
1673
+ log.id = 'dt-console-log';
1674
 
1675
  const input = document.createElement('input');
1676
+ input.className = 'dt-console-input';
1677
  input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
1678
  input.onkeypress = (e) => {
1679
  if (e.key === 'Enter') {
1680
  try {
1681
  const result = eval(e.target.value);
1682
  if (result !== undefined) {
1683
+ logMessage('> ' + e.target.value, 'dt-console-log');
1684
+ logMessage('← ' + formatOutput(result), 'dt-console-log');
1685
  }
1686
  } catch (err) {
1687
+ logMessage(err.message, 'dt-console-error');
1688
  }
1689
  e.target.value = '';
1690
  }
 
1703
 
1704
  console.log = (...args) => {
1705
  originalConsole.log.apply(console, args);
1706
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-log');
1707
  };
1708
 
1709
  console.error = (...args) => {
1710
  originalConsole.error.apply(console, args);
1711
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-error');
1712
  };
1713
 
1714
  console.warn = (...args) => {
1715
  originalConsole.warn.apply(console, args);
1716
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-warn');
1717
  };
1718
 
1719
  console.info = (...args) => {
1720
  originalConsole.info.apply(console, args);
1721
+ logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-info');
1722
  };
1723
 
1724
  function logMessage(message, className) {
 
1730
  }
1731
 
1732
  function formatOutput(output) {
1733
+ if (output === null) return '<span class="dt-json-null">null</span>';
1734
+ if (output === undefined) return '<span class="dt-json-null">undefined</span>';
1735
+ if (typeof output === 'boolean') return `<span class="dt-json-boolean">${output}</span>`;
1736
+ if (typeof output === 'number') return `<span class="dt-json-number">${output}</span>`;
1737
+ if (typeof output === 'string') return `<span class="dt-json-string">"${output}"</span>`;
1738
+ if (typeof output === 'function') return `<span class="dt-json-object">function ${output.name}() { ... }</span>`;
1739
+ if (Array.isArray(output)) return `<span class="dt-json-object">[${output.map(formatOutput).join(', ')}]</span>`;
1740
  if (typeof output === 'object') {
1741
  try {
1742
+ return `<span class="dt-json-object">${JSON.stringify(output, null, 2)
1743
+ .replace(/"([^"]+)":/g, '<span class="dt-json-key">"$1"</span>:')
1744
+ .replace(/"([^"]+)"/g, '<span class="dt-json-string">"$1"</span>')
1745
+ .replace(/\b(true|false)\b/g, '<span class="dt-json-boolean">$1</span>')
1746
+ .replace(/\b(null)\b/g, '<span class="dt-json-null">$1</span>')
1747
+ .replace(/\b(\d+)\b/g, '<span class="dt-json-number">$1</span>')}</span>`;
1748
  } catch (e) {
1749
+ return `<span class="dt-json-object">${output.toString()}</span>`;
1750
  }
1751
  }
1752
  return output;
 
1758
  // Elementsパネル作成
1759
  function createElementsPanel() {
1760
  const panel = document.createElement('div');
1761
+ panel.className = 'dt-devtools-panel';
1762
  panel.id = 'elements-panel';
1763
 
1764
  const container = document.createElement('div');
1765
+ container.className = 'dt-elements-container';
1766
 
1767
  const tree = document.createElement('div');
1768
+ tree.className = 'dt-dom-tree';
1769
+ tree.id = 'dt-dom-tree';
1770
 
1771
  const cssPanel = document.createElement('div');
1772
+ cssPanel.className = 'dt-css-panel';
1773
+ cssPanel.id = 'dt-css-panel';
1774
 
1775
  container.appendChild(tree);
1776
  container.appendChild(cssPanel);
 
1778
 
1779
  // CSSパネル更新関数
1780
  function updateCSSPanel(element) {
1781
+ const cssPanel = document.getElementById('dt-css-panel');
1782
  cssPanel.innerHTML = '';
1783
 
1784
  if (!element) return;
1785
 
1786
  if (element.style.length > 0) {
1787
  const inlineRule = document.createElement('div');
1788
+ inlineRule.className = 'dt-css-rule';
1789
 
1790
  const selector = document.createElement('div');
1791
+ selector.className = 'dt-css-selector';
1792
  selector.textContent = 'インラインスタイル';
1793
  inlineRule.appendChild(selector);
1794
 
 
1797
  const propValue = element.style[propName];
1798
 
1799
  const propDiv = document.createElement('div');
1800
+ propDiv.className = 'dt-css-property';
1801
 
1802
  const nameSpan = document.createElement('span');
1803
+ nameSpan.className = 'dt-css-property-name dt-editable';
1804
  nameSpan.textContent = propName;
1805
  nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
1806
 
1807
  const valueSpan = document.createElement('span');
1808
+ valueSpan.className = 'dt-css-property-value dt-editable';
1809
  valueSpan.textContent = propValue;
1810
  valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
1811
 
1812
  const toggleSpan = document.createElement('span');
1813
+ toggleSpan.className = 'dt-css-toggle';
1814
  toggleSpan.textContent = '×';
1815
  toggleSpan.title = 'プロパティを無効化';
1816
  toggleSpan.onclick = () => {
 
1829
 
1830
  const computedStyles = window.getComputedStyle(element);
1831
  const computedRule = document.createElement('div');
1832
+ computedRule.className = 'dt-css-rule';
1833
 
1834
  const computedSelector = document.createElement('div');
1835
+ computedSelector.className = 'dt-css-selector';
1836
  computedSelector.textContent = '計算されたスタイル';
1837
  computedRule.appendChild(computedSelector);
1838
 
 
1845
  const value = computedStyles[prop];
1846
 
1847
  const propDiv = document.createElement('div');
1848
+ propDiv.className = 'dt-css-property';
1849
 
1850
  const nameSpan = document.createElement('span');
1851
+ nameSpan.className = 'dt-css-property-name';
1852
  nameSpan.textContent = prop;
1853
 
1854
  const valueSpan = document.createElement('span');
1855
+ valueSpan.className = 'dt-css-property-value';
1856
  valueSpan.textContent = value;
1857
 
1858
  propDiv.appendChild(nameSpan);
 
1867
  function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
1868
  if (node.nodeType === Node.ELEMENT_NODE) {
1869
  const element = document.createElement('div');
1870
+ element.className = 'dt-dom-node';
1871
  element.style.marginLeft = `${depth * 15}px`;
1872
+ element.dataset.dtElementId = node.id || Math.random().toString(36).substr(2, 9);
1873
 
1874
  // 右クリックイベント
1875
  element.oncontextmenu = (e) => {
 
1877
  selectedElement = node;
1878
  selectedDOMNode = element;
1879
 
1880
+ document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1881
  element.classList.add('selected');
1882
 
1883
  if (node !== document.documentElement) {
 
1891
 
1892
  // 左クリックで選択
1893
  element.onclick = (e) => {
1894
+ if (e.target.classList.contains('dt-dom-toggle')) return;
1895
 
1896
  e.stopPropagation();
1897
  selectedElement = node;
1898
  selectedDOMNode = element;
1899
 
1900
+ document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1901
  element.classList.add('selected');
1902
 
1903
  updateCSSPanel(node);
 
1909
 
1910
  if (hasChildren) {
1911
  const toggle = document.createElement('div');
1912
+ toggle.className = 'dt-dom-toggle';
1913
  toggle.onclick = (e) => {
1914
  e.stopPropagation();
1915
+ const children = element.querySelector('.dt-dom-children');
1916
  if (children) {
1917
  if (children.style.maxHeight === '0px') {
1918
  children.style.maxHeight = children.scrollHeight + 'px';
 
1928
 
1929
  // タグ名(HTML要素は編集不可)
1930
  const tag = document.createElement('span');
1931
+ tag.className = 'dt-dom-tag';
1932
  tag.textContent = `<${node.tagName.toLowerCase()}`;
1933
 
1934
  if (node !== document.documentElement) {
1935
+ tag.classList.add('dt-editable');
1936
  tag.onclick = (e) => {
1937
  e.stopPropagation();
1938
  startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
 
1953
  // 属性(HTML要素は編集不可)
1954
  Array.from(node.attributes).forEach(attr => {
1955
  const attrSpan = document.createElement('span');
1956
+ attrSpan.className = 'dt-dom-attr';
1957
  attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
1958
 
1959
  if (node !== document.documentElement) {
1960
+ attrSpan.classList.add('dt-editable');
1961
  attrSpan.onclick = (e) => {
1962
  e.stopPropagation();
1963
  startInlineEdit(attrSpan, attr.value, (newValue) => {
 
1974
 
1975
  if (hasChildren) {
1976
  const childrenContainer = document.createElement('div');
1977
+ childrenContainer.className = 'dt-dom-children';
1978
  childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
1979
 
1980
  node.childNodes.forEach(child => {
 
1984
  if (node.tagName.toLowerCase() !== 'br') {
1985
  const closeTag = document.createElement('div');
1986
  closeTag.style.marginLeft = `${depth * 15}px`;
1987
+ closeTag.innerHTML = `<span class="dt-dom-tag">&lt;/${node.tagName.toLowerCase()}&gt;</span>`;
1988
  childrenContainer.appendChild(closeTag);
1989
  }
1990
 
 
1995
  } else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
1996
  const text = document.createElement('div');
1997
  text.style.marginLeft = `${depth * 15}px`;
1998
+ text.className = 'dt-dom-text dt-editable';
1999
  text.textContent = `"${node.textContent.trim()}"`;
2000
  text.onclick = (e) => {
2001
  e.stopPropagation();
 
2030
 
2031
  // refreshElementsPanel関数の定義
2032
  refreshElementsPanel = function() {
2033
+ let tree = document.getElementById('dt-dom-tree');
2034
 
2035
  if (!tree) {
2036
  const panel = document.getElementById('elements-panel');
2037
  if (panel) {
2038
+ const container = panel.querySelector('.dt-elements-container');
2039
  if (container) {
2040
  tree = document.createElement('div');
2041
+ tree.className = 'dt-dom-tree';
2042
+ tree.id = 'dt-dom-tree';
2043
+ container.insertBefore(tree, container.querySelector('.dt-css-panel'));
2044
  }
2045
  }
2046
  }
 
2052
 
2053
  if (selectedElement) {
2054
  const elementId = selectedElement.id || Array.from(selectedElement.attributes)
2055
+ .find(attr => attr.name.startsWith('data-dt-element-id'))?.value;
2056
 
2057
  if (elementId) {
2058
+ const node = document.querySelector(`[data-dt-element-id="${elementId}"]`);
2059
  if (node) {
2060
  node.classList.add('selected');
2061
  updateCSSPanel(selectedElement);
 
2077
 
2078
  // 開発者ツール表示/非表示
2079
  function toggleDevTools() {
2080
+ const container = document.getElementById('dt-devtools-container');
2081
  if (container.style.display === 'none') {
2082
  container.style.display = 'flex';
2083
  } else {
 
2088
  // 開くボタン作成
2089
  function createOpenButton() {
2090
  const button = document.createElement('button');
2091
+ button.id = 'dt-open-devtools-btn';
2092
  button.textContent = '開発者ツールを開く';
2093
  button.style.position = 'fixed';
2094
  button.style.bottom = '10px';
2095
  button.style.right = '10px';
2096
  button.style.padding = '8px 16px';
2097
+ button.style.background = 'var(--dt-primary-color)';
2098
  button.style.color = '#000';
2099
  button.style.border = 'none';
2100
  button.style.borderRadius = '4px';
 
2107
 
2108
  // 初期化
2109
  document.addEventListener('DOMContentLoaded', function() {
2110
+ // 最小限のUIだけ先に表示
2111
  createOpenButton();
2112
+
2113
+ // メインのツールは遅延読み込み
2114
+ setTimeout(() => {
2115
+ createDevTools();
2116
+ logMessage('開発者ツールが初期化されました', 'dt-console-info');
2117
+ logMessage('このコンソールでJavaScriptを実行できます', 'dt-console-info');
2118
+ }, 1000);
2119
  });
2120
+
2121
+ // ログメッセージ用ヘルパー関数
2122
+ function logMessage(message, className) {
2123
+ const log = document.getElementById('dt-console-log');
2124
+ if (!log) return;
2125
+
2126
+ const line = document.createElement('div');
2127
+ line.className = className;
2128
+ line.innerHTML = message;
2129
+ log.appendChild(line);
2130
+ log.scrollTop = log.scrollHeight;
2131
+ }
2132
  })();