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

Update dev-tools.js

Browse files
Files changed (1) hide show
  1. dev-tools.js +668 -449
dev-tools.js CHANGED
@@ -1,101 +1,134 @@
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,45 +137,45 @@
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,117 +183,117 @@
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,43 +302,43 @@
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,18 +347,18 @@
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,47 +368,47 @@
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,51 +419,51 @@
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,107 +471,183 @@
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,17 +656,19 @@
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,11 +681,11 @@
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,24 +699,188 @@
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,7 +912,7 @@
637
  error: null
638
  };
639
 
640
- dtNetworkRequests.push(requestData);
641
  renderNetworkRequests();
642
 
643
  try {
@@ -714,7 +989,7 @@
714
  error: null
715
  };
716
 
717
- dtNetworkRequests.push(requestData);
718
  renderNetworkRequests();
719
 
720
  this.addEventListener('load', function() {
@@ -766,38 +1041,38 @@
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,43 +1087,43 @@
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,22 +1134,22 @@
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,22 +1161,22 @@
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,20 +1188,20 @@
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,31 +1212,30 @@
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,7 +1243,7 @@
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,11 +1252,11 @@
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,11 +1275,11 @@
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,11 +1298,11 @@
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,7 +1343,7 @@
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,7 +1357,7 @@
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,10 +1369,10 @@
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,7 +1414,7 @@
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,7 +1431,7 @@
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,10 +1443,10 @@
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,120 +1471,69 @@
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,13 +1541,13 @@
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,7 +1558,7 @@
1335
  input.focus();
1336
  input.select();
1337
 
1338
- dtActiveEditElement = {
1339
  element: element,
1340
  input: input,
1341
  callback: callback
@@ -1378,32 +1601,32 @@
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,22 +1645,22 @@
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,23 +1672,23 @@
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,19 +1700,19 @@
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,17 +1720,17 @@
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,20 +1739,20 @@
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,10 +1771,10 @@
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,14 +1787,14 @@
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,23 +1809,23 @@
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,13 +1833,13 @@
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,10 +1851,10 @@
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,11 +1870,11 @@
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,8 +1884,8 @@
1661
  });
1662
  newElement.innerHTML = node.innerHTML;
1663
  node.parentNode.replaceChild(newElement, node);
1664
- dtSelectedElement = newElement;
1665
- dtRefreshElementsPanel();
1666
  });
1667
  };
1668
  }
@@ -1672,16 +1895,16 @@
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,7 +1916,7 @@
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,7 +1926,7 @@
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,13 +1937,13 @@
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,23 +1966,23 @@
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,15 +1992,15 @@
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,7 +2011,7 @@
1788
 
1789
  // 初期表示
1790
  setTimeout(() => {
1791
- dtRefreshElementsPanel();
1792
  }, 0);
1793
 
1794
  return panel;
@@ -1796,7 +2019,7 @@
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,13 +2030,13 @@
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,13 +2049,9 @@
1826
 
1827
  // 初期化
1828
  document.addEventListener('DOMContentLoaded', function() {
1829
- // 最小限のUIだけ先に表示
1830
  createOpenButton();
1831
-
1832
- // メインのツールは遅延読み込み
1833
- setTimeout(() => {
1834
- createDevTools();
1835
- console.log('開発者ツールが初期化されました');
1836
- }, 1000);
1837
  });
1838
  })();
 
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
  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
  }
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
  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
  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
  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
  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
  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
 
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
  // タブ切り替え機能
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
  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
  error: null
913
  };
914
 
915
+ networkRequests.push(requestData);
916
  renderNetworkRequests();
917
 
918
  try {
 
989
  error: null
990
  };
991
 
992
+ networkRequests.push(requestData);
993
  renderNetworkRequests();
994
 
995
  this.addEventListener('load', function() {
 
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
  // ネットワークリクエストの詳細を表示
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
  }
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
  }
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
  }
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
 
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
  // Storageパネル作成
1244
  function createStoragePanel() {
1245
  const panel = document.createElement('div');
1246
+ panel.className = 'devtools-panel';
1247
  panel.id = 'storage-panel';
1248
 
1249
  // LocalStorage表示
 
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
  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
  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
 
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
 
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
  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
 
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
 
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
  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
 
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
 
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
  input.focus();
1559
  input.select();
1560
 
1561
+ activeEditElement = {
1562
  element: element,
1563
  input: input,
1564
  callback: callback
 
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
 
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
  }
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
  // 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
 
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
  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
 
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
  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
  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
 
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
 
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
 
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
  });
1885
  newElement.innerHTML = node.innerHTML;
1886
  node.parentNode.replaceChild(newElement, node);
1887
+ selectedElement = newElement;
1888
+ refreshElementsPanel();
1889
  });
1890
  };
1891
  }
 
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
 
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
  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
  } 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
  }
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
  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
 
2012
  // 初期表示
2013
  setTimeout(() => {
2014
+ refreshElementsPanel();
2015
  }, 0);
2016
 
2017
  return panel;
 
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
  // 開くボタン作成
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
 
2050
  // 初期化
2051
  document.addEventListener('DOMContentLoaded', function() {
2052
+ createDevTools();
2053
  createOpenButton();
2054
+ console.log('開発者ツールが初期化されました');
2055
+ console.log('このコンソールでJavaScriptを実行できます');
 
 
 
 
2056
  });
2057
  })();