addashboard-cli 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1632 @@
1
+ <!DOCTYPE html>
2
+ <!-- bizagent-drive:{"fileId":"19yZQrELdu6UWiBI-0pzBT9uiVH3rEKPi","link":"https://drive.google.com/file/d/19yZQrELdu6UWiBI-0pzBT9uiVH3rEKPi/view?usp=drivesdk"} -->
3
+ <html lang="ja">
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width,initial-scale=1">
7
+ <title>YDA入稿プランナー / 媒体要件 — WEBGRAM</title>
8
+ <style>
9
+ :root{
10
+ --fg:#111418; --muted:#5b6470; --faint:#8a92a0; --line:#e6e8ec; --line2:#eef0f3;
11
+ --bg:#ffffff; --soft:#f7f8fa; --softer:#fbfcfd;
12
+ --accent:#4f46e5; --accent-soft:#eef0fe; --accent-line:#c7ccf7;
13
+ --green:#0f7b3f; --green-soft:#e7f4ec;
14
+ --amber:#9a6212; --amber-soft:#fbf1dd;
15
+ --danger:#c02626; --danger-soft:#fdeaea;
16
+ --purple:#6b46c1;
17
+ --mono:ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace;
18
+ --radius:10px;
19
+ }
20
+ *{box-sizing:border-box}
21
+ html,body{margin:0;padding:0;background:var(--soft);color:var(--fg);
22
+ font-family:-apple-system,BlinkMacSystemFont,"Hiragino Kaku Gothic ProN","Yu Gothic UI","Segoe UI",Inter,sans-serif;
23
+ font-size:14px; line-height:1.6; -webkit-font-smoothing:antialiased;}
24
+ h1,h2,h3,h4{margin:0; font-weight:700; letter-spacing:.01em;}
25
+ a{color:var(--accent);}
26
+ button{font-family:inherit;}
27
+ code,kbd{font-family:var(--mono); font-size:.85em; background:var(--line2); padding:1px 5px; border-radius:4px;}
28
+ .mono{font-family:var(--mono); font-variant-numeric:tabular-nums;}
29
+
30
+ /* ── top bar ── */
31
+ header.bar{
32
+ position:sticky; top:0; z-index:40; background:rgba(255,255,255,.92); backdrop-filter:blur(8px);
33
+ border-bottom:1px solid var(--line); padding:10px 20px; display:flex; align-items:center; gap:14px; flex-wrap:wrap;
34
+ }
35
+ header.bar .brand{display:flex; align-items:baseline; gap:10px; margin-right:6px;}
36
+ header.bar .brand .logo{font-size:15px; font-weight:800; letter-spacing:.02em;}
37
+ header.bar .brand .pjname{font-size:12.5px; color:var(--muted);}
38
+ .seg{display:inline-flex; background:var(--soft); border:1px solid var(--line); border-radius:8px; padding:2px;}
39
+ .seg button{border:0; background:transparent; padding:6px 14px; font-size:12.5px; font-weight:600; color:var(--muted); border-radius:6px; cursor:pointer;}
40
+ .seg button.active{background:#fff; color:var(--fg); box-shadow:0 1px 2px rgba(16,24,40,.08);}
41
+ .spacer{flex:1;}
42
+ .act{display:inline-flex; align-items:center; gap:6px; font-size:12.5px; font-weight:600; color:var(--fg); background:#fff; border:1px solid var(--line); padding:6px 11px; border-radius:7px; cursor:pointer;}
43
+ .act:hover{background:var(--soft);}
44
+ .act.primary{background:var(--accent); color:#fff; border-color:var(--accent);}
45
+ .act.primary:hover{background:#4338ca;}
46
+ .act.ghost{background:transparent; border-color:transparent; color:var(--muted);}
47
+ .act.ghost:hover{background:var(--soft);}
48
+ .vbadge{display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:700; padding:5px 10px; border-radius:999px; cursor:pointer;}
49
+ .vbadge.ok{background:var(--green-soft); color:var(--green);}
50
+ .vbadge.err{background:var(--danger-soft); color:var(--danger);}
51
+ .vbadge.warn{background:var(--amber-soft); color:var(--amber);}
52
+
53
+ main{max-width:1400px; margin:0 auto; padding:22px 20px 80px;}
54
+ .view{display:none;}
55
+ .view.active{display:block;}
56
+
57
+ /* ── requirement doc ── */
58
+ .doc{max-width:1000px;}
59
+ .doc .eyebrow{font-size:11.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--faint); font-weight:700;}
60
+ .doc h1.title{font-size:26px; margin:6px 0 4px;}
61
+ .doc .sub{color:var(--muted); font-size:13.5px; margin-bottom:22px;}
62
+ details.sec{border:1px solid var(--line); border-radius:var(--radius); background:#fff; margin:14px 0; overflow:hidden;}
63
+ details.sec>summary{list-style:none; cursor:pointer; padding:14px 18px; display:flex; align-items:center; gap:12px; user-select:none; background:var(--softer);}
64
+ details.sec>summary::-webkit-details-marker{display:none;}
65
+ details.sec>summary .num{display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:6px; background:var(--fg); color:#fff; font-size:11px; font-weight:800; flex-shrink:0;}
66
+ details.sec>summary .stitle{font-size:15.5px; font-weight:700; flex:1;}
67
+ details.sec>summary .chev{width:9px; height:9px; border-right:2px solid var(--faint); border-bottom:2px solid var(--faint); transform:rotate(45deg); transition:transform .15s;}
68
+ details.sec[open]>summary{border-bottom:1px solid var(--line);}
69
+ details.sec[open]>summary .chev{transform:rotate(-135deg);}
70
+ details.sec .body{padding:14px 20px 20px;}
71
+ .doc p{margin:8px 0;}
72
+ .doc h4{font-size:13.5px; margin:16px 0 6px; color:var(--fg);}
73
+ .doc ul{margin:6px 0; padding-left:1.25em;} .doc li{margin:3px 0;}
74
+ .lead{color:var(--muted); font-size:13px; margin:2px 0 14px;}
75
+ table.g{width:100%; border-collapse:collapse; font-size:12.8px; margin:8px 0 6px;}
76
+ table.g th,table.g td{text-align:left; padding:8px 10px; border-bottom:1px solid var(--line2); vertical-align:top;}
77
+ table.g th{background:var(--soft); font-weight:700; color:#3a4150; font-size:11.5px; letter-spacing:.02em; position:sticky; top:0;}
78
+ table.g tr:hover td{background:var(--softer);}
79
+ .pill{display:inline-block; font-size:11px; padding:2px 8px; border-radius:999px; background:var(--accent-soft); color:var(--accent); font-weight:700; letter-spacing:.02em; white-space:nowrap;}
80
+ .pill.green{background:var(--green-soft); color:var(--green);} .pill.amber{background:var(--amber-soft); color:var(--amber);}
81
+ .pill.gray{background:var(--line2); color:#4a5160;} .pill.danger{background:var(--danger-soft); color:var(--danger);}
82
+ .pill.purple{background:#efe9fb; color:var(--purple);}
83
+ .newbadge{display:inline-block; font-size:9px; font-weight:800; color:#fff; background:var(--green); border-radius:4px; padding:1px 5px; letter-spacing:.04em; vertical-align:middle; text-transform:uppercase; margin-left:2px;}
84
+ .callout{border-left:3px solid var(--accent); background:var(--soft); padding:11px 15px; margin:14px 0; border-radius:0 8px 8px 0; font-size:13px;}
85
+ .callout.amber{border-color:var(--amber); background:var(--amber-soft);}
86
+ .callout.green{border-color:var(--green); background:var(--green-soft);}
87
+ .callout.danger{border-color:var(--danger); background:var(--danger-soft);}
88
+ .grid2{display:grid; grid-template-columns:1fr 1fr; gap:14px; margin:10px 0;}
89
+ .kv{border:1px solid var(--line); border-radius:9px; padding:12px 14px; background:#fff;}
90
+ .kv h4{margin:0 0 8px;}
91
+ .fx{font-family:var(--mono); font-size:12px; background:#0f1420; color:#d6e0ff; padding:10px 12px; border-radius:8px; overflow-x:auto; white-space:pre; line-height:1.55;}
92
+ .fx .c{color:#6ee7a8;} .fx .k{color:#f0a868;}
93
+
94
+ /* ── planner shared ── */
95
+ .toolbar2{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:0 0 14px;}
96
+ .chipset{display:flex; gap:6px; flex-wrap:wrap; align-items:center;}
97
+ .chip{display:inline-flex; align-items:center; gap:6px; font-size:11.5px; background:#fff; border:1px solid var(--line); border-radius:7px; padding:4px 9px; color:var(--muted);}
98
+ .chip b{color:var(--fg); font-weight:700;}
99
+ /* インライン編集チップ(案件テンプレのモーダル廃止→ここで直接編集) */
100
+ .chip .chipin{border:none; background:transparent; font-weight:700; color:var(--fg); font-family:inherit; font-size:11.5px; padding:0 1px; border-bottom:1px dashed var(--line); border-radius:0; min-width:0;}
101
+ .chip .chipin:hover{border-bottom-color:var(--muted);}
102
+ .chip .chipin:focus{outline:none; border-bottom:1px solid var(--accent); background:var(--accent-soft);}
103
+ .chip select.chipin{-webkit-appearance:none; appearance:none; cursor:pointer;}
104
+ /* 開閉式 戦略メモ */
105
+ .stratbox{border:1px solid var(--line); border-radius:9px; background:#fff; margin:0 0 12px; overflow:hidden;}
106
+ .stratbox>summary{list-style:none; cursor:pointer; padding:9px 14px; font-size:12.5px; font-weight:700; color:var(--fg); background:var(--softer); user-select:none; display:flex; align-items:center; gap:8px;}
107
+ .stratbox>summary::-webkit-details-marker{display:none;}
108
+ .stratbox>summary::before{content:'▸'; color:var(--muted); font-size:11px;}
109
+ .stratbox[open]>summary::before{content:'▾';}
110
+ .stratbox textarea{width:100%; border:none; border-top:1px solid var(--line2); padding:10px 14px; font-family:inherit; font-size:12.5px; line-height:1.6; min-height:420px; resize:vertical; box-sizing:border-box; background:#fff; color:var(--fg);}
111
+ .stratbox textarea::placeholder{color:var(--faint);}
112
+ .stratbox textarea:focus{outline:none;}
113
+ /* 案件設定(開閉式) */
114
+ .cfgbox{border:1px solid var(--line); border-radius:9px; background:#fff; margin:0 0 12px; overflow:hidden;}
115
+ .cfgbox>summary{list-style:none; cursor:pointer; padding:9px 14px; font-size:12.5px; font-weight:700; color:var(--fg); background:var(--softer); user-select:none; display:flex; align-items:center; gap:8px;}
116
+ .cfgbox>summary::-webkit-details-marker{display:none;}
117
+ .cfgbox>summary::before{content:'▸'; color:var(--muted); font-size:11px;}
118
+ .cfgbox[open]>summary::before{content:'▾';}
119
+ .cfgbox .chipset{padding:12px 14px 12px; border-top:1px solid var(--line2);}
120
+ .filt{display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--muted);}
121
+ .filt select,.filt input{font-family:inherit; font-size:12px; border:1px solid var(--line); border-radius:6px; padding:4px 7px; background:#fff;}
122
+
123
+ /* ── sheet ── */
124
+ .sheetwrap{background:#fff; border:1px solid var(--line); border-radius:var(--radius); overflow:auto; max-height:calc(100vh - 220px);}
125
+ table.sheet{border-collapse:separate; border-spacing:0; width:100%; font-size:12.5px; min-width:1180px; user-select:none; -webkit-user-select:none;}
126
+ table.sheet input.cell-in{user-select:text; -webkit-user-select:text;}
127
+ table.sheet th{position:sticky; top:0; z-index:5; background:var(--soft); color:#3a4150; font-weight:700; font-size:11px; letter-spacing:.02em; text-align:left; padding:8px 9px; border-bottom:1px solid var(--line); white-space:nowrap;}
128
+ table.sheet td{padding:3px 9px; border-bottom:1px solid var(--line2); vertical-align:middle; white-space:nowrap;}
129
+ /* 番号+内容を1ボックスに(インライン表示) */
130
+ .cellcombo{display:inline-flex; align-items:center; gap:0; border:1px solid var(--line); border-radius:6px; padding:1px 7px; background:#fff; max-width:230px; min-width:0;}
131
+ .cellcombo:focus-within{border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-soft);}
132
+ .cellcombo.comboSel{background:var(--accent-soft); border-color:var(--accent);}
133
+ .cellcombo .cnt{font-variant-numeric:tabular-nums;}
134
+ .cellcombo .cell-in{border:none !important; box-shadow:none !important; background:transparent !important; padding:2px 0; flex:0 0 auto; field-sizing:content; width:auto; min-width:1.6ch; max-width:6ch; text-align:right;}
135
+ .cellcombo .combosep{color:var(--faint); font-size:11px; flex:0 0 auto; margin:0 4px 0 0;}
136
+ .cellcombo .ghost{flex:1 1 auto; min-width:0; max-width:none;}
137
+ .cellcombo.err{border-color:var(--danger); background:var(--danger-soft);}
138
+ .combodd{flex:0 0 auto; margin-left:6px; border:none; background:transparent; color:var(--muted); cursor:pointer; font-size:10px; padding:0 2px; border-radius:4px; line-height:1;}
139
+ .combodd:hover{background:var(--soft); color:var(--fg);}
140
+ /* ▼ 右スライドサイドバー(一覧から選択) */
141
+ .drawer-backdrop{position:fixed; inset:0; background:rgba(17,20,24,.16); z-index:88; opacity:0; pointer-events:none; transition:opacity .2s;}
142
+ .drawer-backdrop.open{opacity:1; pointer-events:auto;}
143
+ .refdrawer{position:fixed; top:0; right:0; height:100vh; width:380px; max-width:92vw; background:#fff; border-left:1px solid var(--line); box-shadow:-14px 0 36px rgba(16,24,40,.16); z-index:89; transform:translateX(100%); transition:transform .22s ease; display:flex; flex-direction:column;}
144
+ .refdrawer.open{transform:translateX(0);}
145
+ .refdrawer .dh{display:flex; align-items:center; gap:10px; padding:14px 16px 10px;}
146
+ .refdrawer .dh .dt{font-size:14px; font-weight:700; flex:1;}
147
+ .refdrawer .dh .dx{border:none; background:transparent; font-size:15px; color:var(--muted); cursor:pointer; padding:2px 8px; border-radius:6px;}
148
+ .refdrawer .dh .dx:hover{background:var(--soft);}
149
+ .refdrawer .dtabs{display:flex; gap:6px; padding:0 16px;}
150
+ .refdrawer .dtabs button{border:1px solid var(--line); background:var(--soft); padding:5px 12px; font-size:12px; font-weight:600; color:var(--muted); border-radius:7px; cursor:pointer;}
151
+ .refdrawer .dtabs button.active{background:var(--accent-soft); color:var(--accent); border-color:var(--accent-line);}
152
+ .refdrawer .dsearch{padding:10px 16px 4px;}
153
+ .refdrawer .dsearch input{width:100%; font-family:inherit; font-size:13px; border:1px solid var(--line); border-radius:8px; padding:8px 10px;}
154
+ .refdrawer .dtarget{padding:2px 16px 8px; font-size:11.5px; color:var(--faint);}
155
+ .refdrawer .dlist{overflow-y:auto; flex:1; border-top:1px solid var(--line2);}
156
+ .ddrow{display:flex; gap:8px; padding:8px 16px; cursor:pointer; font-size:12.5px; border-bottom:1px solid var(--line2); white-space:nowrap; align-items:center;}
157
+ .ddrow:hover{background:var(--accent-soft);}
158
+ .ddrow.cur{background:var(--accent-soft); box-shadow:inset 3px 0 0 var(--accent);}
159
+ .ddrow .ddno{font-family:var(--mono); color:var(--accent); font-weight:700; flex:0 0 auto; min-width:40px;}
160
+ .ddrow .ddtx{color:var(--fg); overflow:hidden; text-overflow:ellipsis;}
161
+ table.sheet tr.cphead td{background:var(--softer); border-top:1px solid var(--line); font-weight:700;}
162
+ table.sheet tr.cphead .cptitle{font-size:12.5px; color:var(--fg); display:flex; align-items:center; gap:8px;}
163
+ table.sheet tr.adrow:hover td{background:#fcfdff;}
164
+ table.sheet tr.adrow.hasErr td{background:var(--danger-soft);}
165
+ table.sheet tr.adrow.banner td{background:#faf9fc;}
166
+ .cell-in{width:100%; border:1px solid transparent; background:transparent; border-radius:5px; padding:4px 6px; font-family:inherit; font-size:12.5px; color:var(--fg);}
167
+ .cell-in:hover{border-color:var(--line);}
168
+ .cell-in:focus{outline:none; border-color:var(--accent); background:#fff; box-shadow:0 0 0 2px var(--accent-soft);}
169
+ .cell-in.num{font-family:var(--mono); font-variant-numeric:tabular-nums;}
170
+ .cell-in.err{border-color:var(--danger); background:var(--danger-soft);}
171
+ .cell-in.warnempty{background:var(--amber-soft); box-shadow:inset 2px 0 0 var(--amber);}
172
+ select.cell-in{-webkit-appearance:none; appearance:none; cursor:pointer;}
173
+ .ghost{display:inline-block; font-size:11px; color:var(--faint); line-height:1.35; max-width:150px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:middle;}
174
+ .ghost.err{color:var(--danger);} .ghost b.cnt{font-variant-numeric:tabular-nums;}
175
+ .medibadge{display:inline-flex; align-items:center; gap:6px; font-size:11px; flex-shrink:0; white-space:nowrap;}
176
+ .medibadge .sw{width:26px; height:26px; border-radius:5px; background:linear-gradient(135deg,#e9ecf5,#dfe3ee); display:inline-flex; align-items:center; justify-content:center; font-size:9px; color:#6b7280; font-weight:700; flex-shrink:0;}
177
+ .idpill{font-family:var(--mono); font-size:10.5px; color:var(--muted);}
178
+ .idpill.warn{color:var(--amber);} .idpill.err{color:var(--danger);}
179
+ .breadc{font-size:10.5px; color:var(--faint); white-space:nowrap; max-width:170px; overflow:hidden; text-overflow:ellipsis;}
180
+ .rowact{display:inline-flex; gap:2px;}
181
+ .iconbtn{border:1px solid transparent; background:transparent; width:24px; height:24px; border-radius:6px; cursor:pointer; color:var(--muted); font-size:13px; line-height:1; display:inline-flex; align-items:center; justify-content:center;}
182
+ .iconbtn:hover{background:var(--soft); color:var(--fg); border-color:var(--line);}
183
+ .iconbtn.danger:hover{background:var(--danger-soft); color:var(--danger); border-color:var(--danger-soft);}
184
+ .minick{display:inline-flex; align-items:center; gap:4px; font-size:11px; color:var(--muted); cursor:pointer;}
185
+
186
+ /* ── tree ── */
187
+ .tree{display:flex; flex-direction:column; gap:10px;}
188
+ .cpnode{border:1px solid var(--line); border-radius:var(--radius); background:#fff; overflow:hidden;}
189
+ .noderow{display:flex; align-items:center; gap:9px; padding:10px 12px; cursor:default;}
190
+ .cpnode>.noderow{background:var(--softer);}
191
+ .caret{width:20px; height:20px; border-radius:5px; border:1px solid var(--line); background:#fff; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; color:var(--muted); flex-shrink:0;}
192
+ .caret .a{width:6px;height:6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform .15s;}
193
+ .collapsed .caret .a{transform:rotate(-135deg);}
194
+ .nodename{font-weight:700; font-size:13.5px; color:var(--fg); border:1px solid transparent; border-radius:5px; padding:2px 6px; margin-left:-2px;}
195
+ .nodename:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-soft);}
196
+ .metae{display:flex; gap:6px; align-items:center; flex-wrap:wrap;}
197
+ .nodeact{display:inline-flex; gap:3px; margin-left:auto;}
198
+ .agwrap{padding:4px 10px 12px 34px; display:flex; flex-direction:column; gap:8px;}
199
+ .cpnode.collapsed .agwrap{display:none;}
200
+ .agnode{border:1px solid var(--line2); border-radius:9px; background:var(--softer); overflow:hidden;}
201
+ .agnode>.noderow{background:#fff;}
202
+ .adwrap{padding:2px 10px 10px 30px; display:flex; flex-direction:column; gap:5px;}
203
+ .agnode.collapsed .adwrap{display:none;}
204
+ .adnode{display:flex; align-items:center; gap:9px; padding:7px 9px; border:1px solid var(--line2); border-radius:8px; background:#fff;}
205
+ .adnode.hasErr{border-color:var(--danger); background:var(--danger-soft);}
206
+ .adnode.banner{background:#faf9fc;}
207
+ .dot{width:8px;height:8px;border-radius:50%; flex-shrink:0;}
208
+ .dot.ok{background:var(--green);} .dot.err{background:var(--danger);} .dot.warn{background:var(--amber);}
209
+ .adttl{font-size:12.5px; flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
210
+ .adttl .sm{color:var(--faint); font-size:11px;}
211
+ .cbadge{display:inline-flex; align-items:center; justify-content:center; min-width:24px; height:20px; padding:0 5px; border-radius:5px; background:var(--line2); font-size:10px; font-weight:700; color:#5b6470; font-family:var(--mono);}
212
+ .ratio{font-family:var(--mono); font-size:11px; font-weight:700; color:var(--accent); background:var(--accent-soft); padding:2px 7px; border-radius:5px;}
213
+ .addbtn{align-self:flex-start; font-size:11.5px; color:var(--accent); background:var(--accent-soft); border:1px dashed var(--accent-line); border-radius:7px; padding:5px 10px; cursor:pointer; font-weight:600;}
214
+ .addbtn:hover{background:#e3e7fd;}
215
+
216
+ /* ── selection (spreadsheet) ── */
217
+ td.rownum{ text-align:center; color:var(--faint); font-size:10.5px; font-variant-numeric:tabular-nums; cursor:pointer; user-select:none; width:34px; position:sticky; left:0; z-index:3; background:#fff;}
218
+ table.sheet th:first-child{ left:0; z-index:6;}
219
+ td.rownum:hover{ background:var(--soft); color:var(--fg);}
220
+ td.rownum.rowsel{ background:var(--accent-soft); color:var(--accent); font-weight:700;}
221
+ .cell-in.selcell{ background:var(--accent-soft) !important; box-shadow:inset 0 0 0 1px var(--accent-line);}
222
+ .cell-in.selanchor{ box-shadow:inset 0 0 0 2px var(--accent);}
223
+ /* 広告グループ ターゲティング: セル全体をクリック→右サイドバー(直接入力せず選択式・1行省略で高さコンパクト、全文はtitle) */
224
+ td.tgtd{ padding:3px 6px;}
225
+ .tgpick{ display:flex; align-items:center; gap:6px; min-width:130px; max-width:230px; min-height:26px; border:1px solid var(--line); border-radius:6px; padding:3px 8px; background:#fff; cursor:pointer; line-height:1.3;}
226
+ .tgpick:hover{ border-color:var(--accent); background:var(--accent-soft);}
227
+ .tgpick.tgempty{ background:var(--soft);}
228
+ .tgpick .tgval{ flex:1 1 auto; min-width:0; font-size:12px; color:var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
229
+ .tgpick .tghint{ color:var(--faint);}
230
+ .tgpick .tgcaret{ flex:0 0 auto; color:var(--muted); font-size:10px;}
231
+ .tgpick.selcell{ background:var(--accent-soft) !important; box-shadow:inset 0 0 0 1px var(--accent-line); border-color:var(--accent);}
232
+ .tgpick.selanchor{ box-shadow:inset 0 0 0 2px var(--accent);}
233
+ body.noselect{ user-select:none; -webkit-user-select:none;}
234
+ .hinttxt{ font-size:11px; color:var(--faint);}
235
+ /* ── ref data view ── */
236
+ table.reftable{ border-collapse:separate; border-spacing:0; width:100%; font-size:12.5px; min-width:640px;}
237
+ table.reftable th{ position:sticky; top:0; z-index:5; background:var(--soft); color:#3a4150; font-weight:700; font-size:11px; text-align:left; padding:8px 10px; border-bottom:1px solid var(--line); white-space:nowrap;}
238
+ table.reftable td{ padding:5px 10px; border-bottom:1px solid var(--line2); vertical-align:middle;}
239
+ table.reftable tr:hover td{ background:#fcfdff;}
240
+ .reftext{ max-width:560px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
241
+ .reflen{ font-variant-numeric:tabular-nums; font-size:11px; color:var(--faint);}
242
+ .reflen.warn{ color:var(--amber); font-weight:700;} .reflen.err{ color:var(--danger); font-weight:700;}
243
+
244
+ /* ── modal ── */
245
+ .overlay{position:fixed; inset:0; background:rgba(17,20,24,.42); z-index:80; display:flex; align-items:center; justify-content:center; padding:20px;}
246
+ .modal{background:#fff; border-radius:14px; box-shadow:0 20px 60px rgba(16,24,40,.28); width:min(560px,100%); max-height:88vh; overflow:auto;}
247
+ .modal h3{font-size:16px; padding:18px 20px 4px;}
248
+ .modal .msub{color:var(--muted); font-size:12.5px; padding:0 20px 6px;}
249
+ .modal .mbody{padding:10px 20px;}
250
+ .mfield{margin:12px 0;}
251
+ .mfield label{display:block; font-size:12px; font-weight:600; color:var(--muted); margin-bottom:5px;}
252
+ .mfield input[type=text],.mfield input[type=number],.mfield select,.mfield textarea{width:100%; font-family:inherit; font-size:13px; border:1px solid var(--line); border-radius:8px; padding:8px 10px; background:#fff;}
253
+ .mfield textarea{min-height:70px; resize:vertical;}
254
+ .mcheck{display:flex; align-items:center; gap:8px; font-size:13px; margin:7px 0; cursor:pointer;}
255
+ .mrow{display:grid; grid-template-columns:1fr 1fr; gap:12px;}
256
+ .modal .mfoot{display:flex; justify-content:flex-end; gap:8px; padding:14px 20px 18px; border-top:1px solid var(--line2); margin-top:8px; position:sticky; bottom:0; background:#fff;}
257
+ .hint{font-size:11.5px; color:var(--faint); margin-top:4px;}
258
+ .toast{position:fixed; bottom:22px; left:50%; transform:translateX(-50%); background:#111418; color:#fff; font-size:12.5px; padding:9px 16px; border-radius:9px; z-index:100; box-shadow:0 8px 24px rgba(0,0,0,.25); opacity:0; transition:opacity .2s, transform .2s;}
259
+ .toast.show{opacity:1; transform:translateX(-50%) translateY(-2px);}
260
+ .empty{padding:40px; text-align:center; color:var(--faint); font-size:13px;}
261
+ @media (max-width:820px){ .grid2,.mrow{grid-template-columns:1fr;} }
262
+ @media print{ header.bar{position:static;} .view{display:block!important;} .toolbar2,.rowact,.nodeact,.act,.seg{display:none;} details.sec[open]>.body,details.sec>.body{display:block;} }
263
+ </style>
264
+ </head>
265
+ <body>
266
+ <header class="bar">
267
+ <div class="brand"><span class="logo">YDA入稿プランナー</span><span class="pjname" id="pjLabel">—</span></div>
268
+ <div class="seg" id="viewSeg">
269
+ <button data-v="req">媒体要件</button>
270
+ <button data-v="sheet" class="active">プラン</button>
271
+ <button data-v="ref">参照データ</button>
272
+ <button data-v="hist">履歴</button>
273
+ </div>
274
+ <span class="vbadge ok" id="vbadge" title="バリデーション状況">✓ 0件</span>
275
+ <div class="spacer"></div>
276
+ <button class="act ghost" id="btnUndo" title="元に戻す (Ctrl/Cmd+Z)">↶ 戻す</button>
277
+ <button class="act" id="btnLoad">JSON読込</button>
278
+ <button class="act" id="btnSave">バージョン保存</button>
279
+ <button class="act primary" id="btnSubmitJson">入稿JSON書き出し</button>
280
+ <input type="file" id="fileInput" accept=".json,application/json" style="display:none">
281
+ </header>
282
+
283
+ <main>
284
+ <!-- ============ 媒体要件ビュー ============ -->
285
+ <section class="view" id="view-req">
286
+ <div class="doc">
287
+ <div class="seg" id="mediaTabs" style="display:inline-flex; margin-bottom:16px">
288
+ <button data-mt="yda" class="active">YDA</button>
289
+ <button data-mt="meta">Meta広告 <span class="pill amber" style="margin-left:4px">検証中</span></button>
290
+ </div>
291
+
292
+ <!-- ======== YDA タブ ======== -->
293
+ <div id="mt-yda">
294
+ <div class="eyebrow">YDA / Yahoo Display Ads — 媒体要件・仕様・使い方</div>
295
+ <h1 class="title">YDA 広告入稿 媒体要件</h1>
296
+ <p class="sub">2026-07-16 定例ヒアリング・2026-07-21 定例(北さん運用フィードバック)+ 現行一括入稿シート(リョウシンJVコンドロイ錠)の実データ解析にもとづく。2026-07-23 よりCSV書き出しを廃止し、保存・入稿受け渡しはJSONに統一。</p>
297
+
298
+ <details class="sec" open><summary><span class="num">1</span><span class="stitle">全体像 — 3層構造と量産の考え方</span><span class="chev"></span></summary>
299
+ <div class="body">
300
+ <p class="lead">YDA運用の核は「<b>変数=キャンペーン</b>」「<b>クリエイティブ束=広告グループ内の広告</b>」という2軸の量産構造。キャンペーンを横に増やし、広告を縦に増やす。</p>
301
+ <table class="g"><thead><tr><th>階層</th><th>意味</th><th>このツールでの単位</th></tr></thead><tbody>
302
+ <tr><td><b>キャンペーン</b></td><td>検証したい<b>変数の隔離単位</b>。「変数の数だけキャンペーンを分ける」=全部同じにして1点だけ変えて比較する実験設計</td><td>ツリーの最上位ノード</td></tr>
303
+ <tr><td><b>広告グループ</b></td><td>1つの <code>【paramCode】</code> に対応。キャンペーンID・広告グループIDはこの paramCode から解決される</td><td>中間ノード(paramCodeを保持)</td></tr>
304
+ <tr><td><b>広告</b></td><td>1クリエイティブ=画像 × タイトル × 説明文 の1通りの組み合わせ(=固定型)</td><td>葉ノード / シート1行</td></tr>
305
+ </tbody></table>
306
+ <div class="grid2">
307
+ <div class="kv"><h4><span class="ratio">1 : 1 : 4</span> 基本の量産単位</h4><p>1キャンペーン・1広告グループに<b>4本</b>の広告を並べ、アルゴリズムに良いものを選ばせる。</p></div>
308
+ <div class="kv"><h4><span class="ratio">1 : 1 : 10</span> 初期クリエイティブ</h4><p>立ち上げ期は<b>10本</b>を一気に撒いてデータを取り、伸びたものへ配信を寄せる “撒いて選ぶ”。</p></div>
309
+ </div>
310
+ <div class="callout">ツリーの各ノードの複製ボタンで、この <code>1:1:N</code> を1クリックで生成できる(テンプレ複製→中身クリア→変数差し替え)。</div>
311
+ </div></details>
312
+
313
+ <details class="sec"><summary><span class="num">2</span><span class="stitle">広告形式 — 対応/非対応(入稿経路)</span><span class="chev"></span></summary>
314
+ <div class="body">
315
+ <p class="lead">本ツールが扱うのは <b>レスポンシブ(画像)・固定型</b>。入稿は「入稿JSON書き出し → <code>webgram submit --platform yda</code>」のAPI入稿に統一(CSVは2026-07-23廃止)。</p>
316
+ <table class="g"><thead><tr><th>形式</th><th>入稿経路</th><th>利用</th><th>備考</th></tr></thead><tbody>
317
+ <tr><td><b>レスポンシブ(画像)・固定型</b></td><td><span class="pill green">API入稿 ★主用途</span></td><td>★★★</td><td>タイトル/説明文/画像を人が1通りに固定し、成果を自分で検証。<code>Card</code>(1200x628)/<code>SQ</code>(1080x1080) の2形式。API上は <code>RESPONSIVE_AD + mainMediaFormat:IMAGE</code>、画像は <code>asset.mediaAssets[].mediaId</code>(PR #82・実API疎通は検証中)</td></tr>
318
+ <tr><td>レスポンシブ(画像)・アセット型</td><td><span class="pill amber">非対象</span></td><td>—</td><td>複数タイトル/画像を登録しYahoo側が自動最適化。<b>1広告=単一組合せの本ツールでは非対象</b></td></tr>
319
+ <tr><td>レスポンシブ(動画)</td><td><span class="pill gray">別フロー(CLI)</span></td><td>—</td><td><code>webgram upload-video → submit</code> のAPI入稿(本番実績あり)。<code>thumbnailFlg:'TRUE'</code> のサムネ画像は動画専用で、画像広告のメイン画像とは排他</td></tr>
320
+ <tr><td>バナー(静止画・固定サイズ)</td><td><span class="pill danger">手入稿</span></td><td>★★★</td><td>API入稿は未対応。Yahoo管理画面で個別入稿。本ツールでは <span class="pill">手入稿</span> フラグで区別し、入稿JSONから除外される</td></tr>
321
+ <tr><td>カルーセル / テキスト</td><td><span class="pill gray">未使用</span></td><td>—</td><td>運用では使わない</td></tr>
322
+ </tbody></table>
323
+ </div></details>
324
+
325
+ <details class="sec"><summary><span class="num">3</span><span class="stitle">入稿モード — 新規 / 既存(ASC)</span><span class="chev"></span></summary>
326
+ <div class="body">
327
+ <table class="g"><thead><tr><th>モード</th><th>構造</th><th>予算/日</th><th>頻度・工数</th></tr></thead><tbody>
328
+ <tr><td><b>新規</b></td><td>1キャンペーン=1広告グループ=1広告を基本に、テンプレ複製で量産。変数ごとにキャンペーンを分ける</td><td><b>12,000円</b></td><td>頻度高・主戦場。既存より圧倒的に多い</td></tr>
329
+ <tr><td><b>既存(ASC)</b></td><td>1キャンペーン内に複数広告をまとめ、好調クリエイティブへ配信を寄せる(1:1:10 等)</td><td><b>5万〜10万円</b></td><td>予算大。担当者により幅</td></tr>
330
+ <tr><td>既存 - 広告グループ追加</td><td>既存キャンペーンに新しい広告グループを足す</td><td>既存準拠</td><td>工数中</td></tr>
331
+ <tr><td>既存 - 既存グループへ広告追加</td><td>既存グループに広告(クリエイティブ)だけ足す</td><td>既存準拠</td><td>工数小(操作感は同じ)</td></tr>
332
+ </tbody></table>
333
+ <div class="callout amber">既存モードは Yahoo払出し済みの campaignId / adGroupId の取り込みが前提。paramCode がグループ参照表に無い=<b>新規/未払出</b>として黄色表示になる(実シートの <code>#N/A</code> に相当)。</div>
334
+ </div></details>
335
+
336
+ <details class="sec"><summary><span class="num">4</span><span class="stitle">固定デフォルト値 — 案件テンプレとして一括適用</span><span class="chev"></span></summary>
337
+ <div class="body">
338
+ <p class="lead">全広告に共通する固定値は「案件テンプレ」で1箇所定義し全広告へ一括適用する(現行の全行ハードコードを廃止)。</p>
339
+ <table class="g"><thead><tr><th>項目</th><th>既定値</th><th>備考</th></tr></thead><tbody>
340
+ <tr><td>アトリビューション計測期間</td><td><b>1日</b></td><td>媒体既定7日を毎回1日に変更する慣習</td></tr>
341
+ <tr><td>CTAボタン</td><td><b>もっとみる</b></td><td>画像入稿の実データ既定。動画入稿は「詳細を見る」。<span class="pill amber">要確定</span></td></tr>
342
+ <tr><td>入札戦略</td><td><b>tCPA 8,500円</b></td><td>API値は <code>MAXIMIZE_CONVERSIONS + targetCpa:8500</code>。もう一択は「最大配信」</td></tr>
343
+ <tr><td>予算</td><td>新規 12,000 / ASC 50,000〜</td><td>モードで切替</td></tr>
344
+ <tr><td>配信設定</td><td><b>オフ (PAUSED)</b></td><td>誤配信防止。入稿後に手動オン</td></tr>
345
+ <tr><td>広告タイプ / 形式</td><td>レスポンシブ / 画像</td><td>固定(API: <code>RESPONSIVE_AD + IMAGE</code>)</td></tr>
346
+ <tr><td>主体者表記</td><td><b>富山常備薬</b></td><td>案件固定(薬機/景表対応)。API: <code>responsiveAd.principal</code></td></tr>
347
+ <tr><td>表示URLの形式</td><td>ドメインのみ表示する</td><td>固定(API: <code>displayUrlLevel:'DOMAIN'</code>)</td></tr>
348
+ </tbody></table>
349
+ </div></details>
350
+
351
+ <details class="sec"><summary><span class="num">5</span><span class="stitle">命名規則・生成式(実データ突合済み)</span><span class="chev"></span></summary>
352
+ <div class="body">
353
+ <p class="lead">派生値はデータに保存せず、都度この式で算出する(行オフセット依存の数式を排除)。例は実データ <code>957c-45L60-jv_Cac_148</code> で検証。</p>
354
+ <div class="fx"><span class="c">// CR名</span>
355
+ crName = 画像番号 + (形式=="SQ" ? "<span class="k">s</span>" : "<span class="k">c</span>") <span class="c">→ 957c</span>
356
+ <span class="c">// 広告名 (col5)</span>
357
+ adName = crName + "-" + タイトル番号 + "<span class="k">L</span>" + 説明文番号 + "-" + paramCode
358
+ <span class="c">→ 957c-45L60-jv_Cac_148</span>
359
+ <span class="c">// キャンペーン/広告グループID … paramCode で参照表を引く</span>
360
+ campaignId, adGroupId = groups[ paramCode ] <span class="c">→ 32525263 / 451760490</span>
361
+ <span class="c">// メディアID … 画像番号+形式で参照表を引く</span>
362
+ mediaId = media[ 画像番号 ][ 形式 ] <span class="c">→ 21667629</span>
363
+ <span class="c">// タイトル/説明文 … 番号で文言ライブラリを引く</span>
364
+ title = titles[ タイトル番号 ] desc = titles[ 説明文番号 ]
365
+ <span class="c">// 最終リンク先URL(入稿JSONの finalUrl)</span>
366
+ finalUrl = baseURL + 記事slug + "?ad=" + paramCode</div>
367
+ <div class="grid2" style="margin-top:12px">
368
+ <div class="kv"><h4>キャンペーン名</h4><p class="mono" style="font-size:12px">{空き番号}_{CR名}_{YYYY-MM-DD}</p><p>空き番号は使用済みから未使用を割当(連番でなくてよい・被らないことだけ守る)。</p></div>
369
+ <div class="kv"><h4>入稿リンク末尾番号</h4><p>ベースURLは案件固定、末尾パラメータ番号のみ可変。既存の使用済み番号から空きを自動割当。</p></div>
370
+ </div>
371
+ </div></details>
372
+
373
+ <details class="sec"><summary><span class="num">6</span><span class="stitle">入稿JSON — 構造と入力項目・制約(パラメータ)</span><span class="chev"></span></summary>
374
+ <div class="body">
375
+ <p class="lead">「入稿JSON書き出し」はワンクリックで、<b>画面のプランをそのまま</b> backend <code>ydaSubmitSchema</code> 準拠のボディに変換する(設定ダイアログなし)。<code>webgram submit --platform yda --json-file &lt;このファイル&gt;</code> がそのまま <code>/api/submission/yda</code> に渡す。手入稿(バナー)と未解決の広告は自動除外。</p>
376
+ <div class="fx">{ accountId, campaigns: [ { name, goal, status, startDate, budget, biddingStrategy, targetCpa?, cpc?,
377
+ adGroups: [ { name, status, smartTargeting, devices, ages, genders, days, startHour?, endHour?,
378
+ placementsInclude, placementsExclude, audiencesInclude, audiencesExclude,
379
+ ads: [ { name, mediaId, title, description, finalUrl } ] } ] } ] }</div>
380
+ <table class="g"><thead><tr><th>項目</th><th>画面上の出どころ</th><th>変換・制約</th></tr></thead><tbody>
381
+ <tr><td class="mono">accountId</td><td><b>案件設定「アカウントID」</b></td><td>必須。未入力だと書き出し中止</td></tr>
382
+ <tr><td class="mono">goal</td><td><b>案件設定「目的」</b></td><td><code>CONVERSION</code> / <code>CLICK</code> / <code>BRAND_AWARENESS</code> / <code>VIDEO_VIEW</code> / <code>VIEWABLE_IMPRESSION</code></td></tr>
383
+ <tr><td class="mono">startDate</td><td><b>案件設定「開始日」</b></td><td>YYYY-MM-DD。空欄=書き出し日</td></tr>
384
+ <tr><td class="mono">status</td><td>キャンペーン行/広告グループ行の「配信設定」</td><td><code>オン→ACTIVE</code> / <code>オフ→PAUSED</code>(既定オフ=誤配信防止)</td></tr>
385
+ <tr><td class="mono">budget</td><td>キャンペーン行「1日の予算」</td><td>未入力時はモード別既定(新規12,000/ASC50,000)</td></tr>
386
+ <tr><td class="mono">biddingStrategy</td><td>キャンペーン行「入札戦略」+ 目標CV単価</td><td><code>tCPA → MAXIMIZE_CONVERSIONS + targetCpa</code>(tCPA値はキャンペーン行→案件既定8,500の順)/ <code>最大配信 → MAXIMIZE_CONVERSIONS</code>(目標なし <span class="pill amber">要実機確認</span>)</td></tr>
387
+ <tr><td class="mono">devices</td><td>広告グループ行「デバイス」</td><td><code>スマートフォン→SMARTPHONE</code> / <code>PC→DESKTOP</code> / <code>タブレット→TABLET</code>。全てのデバイス/空=全配信(<code>[]</code>)</td></tr>
388
+ <tr><td class="mono">ages / genders</td><td>広告グループ行「年齢」「性別」</td><td><code>20-24歳→GT_RANGE20_24</code> 等 / <code>男性→ST_MALE</code>・<code>女性→ST_FEMALE</code>・<code>不明→ST_UNKNOWN</code>。空=全配信</td></tr>
389
+ <tr><td class="mono">days / startHour / endHour</td><td>広告グループ行「曜日・時間帯」</td><td><code>月→MONDAY</code> 等。<code>平日</code>=月〜金、<code>土日祝</code>=土日 に展開。時間帯(午前(6-12時)等)は <code>startHour/endHour</code> に変換(複数選択時は最初の1つ)</td></tr>
390
+ <tr><td class="mono">audiences* / placements*</td><td>—</td><td>常に空配列で送信(backendスキーマ必須項目)。<b>「地域」「オーディエンスリスト」「サーチキーワード」列はJSONに反映されない</b>(APIのID解決が必要なため。入稿後に管理画面で設定 or 将来対応)</td></tr>
391
+ <tr><td class="mono">ads[].name</td><td><span class="pill">自動</span></td><td>生成式 <code>{画像番号}{c|s}-{タイトル番号}L{説明文番号}-{paramCode}</code></td></tr>
392
+ <tr><td class="mono">ads[].mediaId</td><td><span class="pill">自動</span></td><td>画像番号+形式 → 参照表。未解決は入稿対象外</td></tr>
393
+ <tr><td class="mono">ads[].title</td><td><span class="pill purple">可変</span></td><td>タイトル番号 → 文言。<b>35字以内</b>(API上のアセット制約はタイトル≤20字・説明文≤90字)</td></tr>
394
+ <tr><td class="mono">ads[].description</td><td><span class="pill purple">可変</span></td><td>説明文番号 → 文言</td></tr>
395
+ <tr><td class="mono">ads[].finalUrl</td><td><span class="pill">自動</span></td><td>生成式 <code>baseURL + slug + "?ad=" + paramCode</code></td></tr>
396
+ </tbody></table>
397
+ <div class="callout">プランナーの保存JSON(バージョン保存)はこれとは別物で、<code>schemaVersion:2</code> の全state(meta / project / campaigns / ui / refs / submit)を保持する。<b>meta</b> = バージョンID・作成/更新日時・作成者・対象媒体・名称・メモ。旧v1 JSONも読込時に自動マイグレーションされる。</div>
398
+ </div></details>
399
+
400
+ <details class="sec"><summary><span class="num">7</span><span class="stitle">制約・バリデーション</span><span class="chev"></span></summary>
401
+ <div class="body">
402
+ <ul>
403
+ <li><b>タイトル 35文字以内</b>(タイトル集ヘッダに明記)。超過はシート/ツリーで赤表示。</li>
404
+ <li><b>メディアID未解決</b>:画像番号が参照表に無い→入稿不可(実シートの <code>#N/A</code>)。赤エラー。</li>
405
+ <li><b>タイトル/説明文番号未解決</b>:文言ライブラリに無い→赤エラー。</li>
406
+ <li><b>paramCode未払出</b>:参照表に無い→黄色警告(新規は想定内、既存は要確認)。</li>
407
+ <li>説明文は同じ文言ライブラリを共有するが、タイトルより長文可のため35字制限は<b>タイトル枠のみ</b>に適用。</li>
408
+ </ul>
409
+ </div></details>
410
+
411
+ <details class="sec"><summary><span class="num">8</span><span class="stitle">運用フロー — 北さんの複製量産 / 戦略の後乗せ</span><span class="chev"></span></summary>
412
+ <div class="body">
413
+ <ol>
414
+ <li>完成キャンペーンを<b>テンプレとして丸ごと複製</b>する</li>
415
+ <li>複製の中身(クリエイティブ/リンク/固有パラメータ)を<b>一旦消す</b></li>
416
+ <li>変えたい<b>変数だけ差し替える</b></li>
417
+ <li>これを検証したい変数の数だけ繰り返し量産する</li>
418
+ <li>初期は 1:1:10 で撒き、数日データを見る</li>
419
+ <li>伸びた型で追加量産し、<b>戦略(配信戦略・訴求方針)は後から埋める</b></li>
420
+ </ol>
421
+ <div class="callout green">このツールはツリーの複製ダイアログで「何を残し・何をクリアするか」を選べる。キャンペーンの「量産」で M本を一括生成。<b>戦略メモ欄は空のまま量産→後乗せ</b>で埋められる。</div>
422
+ <div class="callout"><b>本ツールの立ち位置(2026-07-16 定例、2026-07-23 更新)</b>:AIで生成できる部分は<b>AIに生成させ(JSON読込)</b>、絶対に人が確定する部分(クリエイティブ選定・確認)を<b>スプシ的コピペ・一括入力・複製</b>で楽にする。ここで確認・修正して <b>OK なら 入稿JSON書き出し(=GO)</b>→ <code>webgram submit --platform yda</code>(dry-run → 本実行)。CSVを介さない直接入稿へ移行済み。今後はキャンペーン/広告グループIDのAPI/MCP自動取得、新規画像のメディア登録の自動化へ。</div>
423
+ </div></details>
424
+
425
+ <details class="sec"><summary><span class="num">9</span><span class="stitle">使用手順 — 保存・履歴・入稿の流れ</span><span class="chev"></span></summary>
426
+ <div class="body">
427
+ <ol>
428
+ <li><b>プラン作成</b>:プランビューは「戦略メモ → 案件設定 → シート/ツリー切替」の順。シート選択時はさらに広告/広告グループ/キャンペーンのタブで編集する。AI が生成した state JSON は「JSON読込」で取り込む(旧v1形式も自動マイグレーション)。</li>
429
+ <li><b>確認</b>:バリデーションバッジ(タイトル35字超・参照未解決・paramCode未払出)が緑になるまで修正。手入稿バナーは「手入稿」フラグで区別。</li>
430
+ <li><b>バージョン保存</b>:ヘッダーの「バージョン保存」。保存のたびに<b>常に新バージョン</b>(v1, v2…)として名称・メモ・作成者付きで保存される(上書きなし)。<b>履歴本体はブラウザ内に自動保存</b>されるため、フォルダ設定なしでも一覧・再読込できる。</li>
431
+ <li><b>保存フォルダ接続(初回に1回だけ)</b>:履歴ビューの「📁 保存フォルダに接続」でスキル同梱の <code>入稿計画/</code> フォルダを一度選ぶと、<b>次回からは開くだけで自動接続</b>され、保存のたびにファイルにも書き出される。未接続時はダウンロード動作。</li>
432
+ <li><b>履歴の確認・再利用</b>:履歴ビューで一覧(Ver/日時/媒体/名称)を確認し、「読込」で過去バージョンを復元、「編集」で名称・メモ変更、「削除」で破棄。</li>
433
+ <li><b>入稿JSON書き出し</b>:ヘッダーの「入稿JSON書き出し」をワンクリック。アカウントID・目的・開始日は案件設定チップ、ターゲティングは広告グループ行の列から自動変換される(<code>*_submit-body_*.json</code>)。</li>
434
+ <li><b>入稿(dry-run → 本実行)</b>:<code>webgram submit --platform yda --json-file &lt;submit-body&gt;</code>。必ず dry-run で内容確認後、<code>--no-dry-run</code> で本実行。実行後は管理画面の一覧でOK確認。</li>
435
+ </ol>
436
+ </div></details>
437
+
438
+ <details class="sec"><summary><span class="num">10</span><span class="stitle">注意事項・未対応事項(要確認)</span><span class="chev"></span></summary>
439
+ <div class="body">
440
+ <ul>
441
+ <li><b>バナー(固定サイズ)はAPI未対応</b> → 引き続き管理画面で手入稿。入稿JSONからは自動除外される。</li>
442
+ <li><b>画像レスポンシブのAPI入稿(PR #82)は実API疎通が未検証</b>。本入稿前に必ず dry-run → 少数テスト入稿で確認。</li>
443
+ <li>CTA既定が「もっとみる」(画像) か「詳細を見る」(動画) か。画像/動画でCTA既定が異なる可能性。<code>buttonText</code> のenum値も要実測。</li>
444
+ <li>年齢区分enumは実装内に系統差あり(<code>GT_RANGE*</code> が本番採用)。公式ドキュメント裏取りは未記録のため本入稿前に確認推奨。</li>
445
+ <li>新規モードで campaignId/adGroupId 未払出のときの挙動(キャンペーン新規作成 or 既存への広告追加のみ)の実機確認。</li>
446
+ <li>画像サムネイルURLの入手経路(現状は番号+メディアIDバッジ表示)。</li>
447
+ <li>参照表(タイトル集/メディアID/グループID)の更新運用。Yahoo払出し後に増えるため、参照データビューまたはJSON差し替えで最新化。</li>
448
+ <li>フォルダ連携(File System Access API)はChrome系のみ。Safariなどはダウンロードにフォールバックされる。</li>
449
+ </ul>
450
+ </div></details>
451
+ </div><!-- /mt-yda -->
452
+
453
+ <!-- ======== Meta タブ(検証中) ======== -->
454
+ <div id="mt-meta" style="display:none">
455
+ <div class="eyebrow">Meta Ads — 検証範囲・要件一覧(ドラフト)</div>
456
+ <h1 class="title">Meta 広告入稿 <span class="pill amber" style="vertical-align:middle">検証中</span></h1>
457
+ <p class="sub">2026-07-23 時点のドラフト。backend/CLI には入稿実装が存在するが、<b>プランナーGUIは未対応</b>・実運用検証は未着手。北さんヒアリング(YDA入稿Skillブラッシュアップ)と backend 実装(metaSubmission.ts)にもとづく要件案。</p>
458
+
459
+ <details class="sec" open><summary><span class="num">1</span><span class="stitle">概要 — 実装済みの土台</span><span class="chev"></span></summary>
460
+ <div class="body">
461
+ <p class="lead">API側の土台は実装済み。GUI(プランナー)と実運用フローが未整備という状態。</p>
462
+ <table class="g"><thead><tr><th>レイヤ</th><th>状態</th><th>内容</th></tr></thead><tbody>
463
+ <tr><td>backend</td><td><span class="pill green">実装済み</span></td><td><code>/api/submission/meta</code> 系ルート + <code>MetaSubmissionConnector</code>(オーディエンス取得 / 既存キャンペーン取得・インポート / キャンペーン作成 / 動画ライブラリ)</td></tr>
464
+ <tr><td>CLI</td><td><span class="pill green">実装済み</span></td><td><code>webgram submit --platform meta</code>(動画は upload-video で mediaId 解決 → submit。dry-run 対応)</td></tr>
465
+ <tr><td>プランナーGUI</td><td><span class="pill danger">未対応</span></td><td>Meta用のシート/ツリー/参照データ/入稿JSON書き出しは未実装(本タブは仕様整理のみ)</td></tr>
466
+ <tr><td>実運用検証</td><td><span class="pill amber">未着手</span></td><td>本番アカウントでの PoC 入稿・運用フロー確立が未実施</td></tr>
467
+ </tbody></table>
468
+ </div></details>
469
+
470
+ <details class="sec" open><summary><span class="num">2</span><span class="stitle">対応広告形式・入稿パターン(ヒアリングベース)</span><span class="chev"></span></summary>
471
+ <div class="body">
472
+ <table class="g"><thead><tr><th>パターン</th><th>頻度・工数</th><th>内容</th></tr></thead><tbody>
473
+ <tr><td><b>キャンペーンごと作成</b></td><td>★★★ 頻度高・工数大</td><td>キャンペーン設定 → 広告グループ設定 → 広告設定 を毎回一式作る。主戦場</td></tr>
474
+ <tr><td>広告グループごと追加</td><td>★★</td><td>既存キャンペーンに広告グループを追加</td></tr>
475
+ <tr><td>既存広告追加</td><td>★</td><td>既存グループにクリエイティブだけ追加(ASC的運用)</td></tr>
476
+ </tbody></table>
477
+ <p>クリエイティブは動画中心(動画ライブラリ + upload-video 実装済み)。画像・カルーセルの範囲は要確定。</p>
478
+ </div></details>
479
+
480
+ <details class="sec" open><summary><span class="num">3</span><span class="stitle">入力項目と制約(要件一覧ドラフト)</span><span class="chev"></span></summary>
481
+ <div class="body">
482
+ <table class="g"><thead><tr><th>階層</th><th>項目</th><th>備考</th></tr></thead><tbody>
483
+ <tr><td><b>キャンペーン</b></td><td>名前 / 戦略 / 目標値 / 予算 / ステータス</td><td>戦略は「コンバージョン数の最大化」、目標値(目標単価)はキャンペーンごとに別々</td></tr>
484
+ <tr><td rowspan="7"><b>広告グループ<br>(7項目)</b></td><td>スマートターゲティング</td><td>Advantage+ 系の自動ターゲティング可否</td></tr>
485
+ <tr><td>曜日・時間帯</td><td>配信スケジュール</td></tr>
486
+ <tr><td>デバイス</td><td>配信デバイス</td></tr>
487
+ <tr><td>性別</td><td>—</td></tr>
488
+ <tr><td>年齢</td><td>—</td></tr>
489
+ <tr><td>オーディエンスリスト</td><td>どのデータ(カスタム/類似オーディエンス)に配布するか。<code>fetchAudiences</code> で取得可</td></tr>
490
+ <tr><td>プレイスメント</td><td>配信面の指定</td></tr>
491
+ <tr><td><b>広告</b></td><td>名前 / クリエイティブ(動画 or 画像)/ テキスト / リンク先URL / CTA</td><td>動画は動画ライブラリの videoId、画像は imageHash(<code>parseCreative</code> が判別)</td></tr>
492
+ </tbody></table>
493
+ </div></details>
494
+
495
+ <details class="sec" open><summary><span class="num">4</span><span class="stitle">検証範囲(案)と手順</span><span class="chev"></span></summary>
496
+ <div class="body">
497
+ <ol>
498
+ <li><b>契約確認</b>:<code>webgram submit --platform meta --json-file fixtures/meta_poc.json</code> を dry-run で実行し、送信ボディの構造を確認する。</li>
499
+ <li><b>既存構造の取り込み</b>:既存キャンペーンのインポート(<code>importCampaign</code>)で、実運用中のキャンペーン設定(7項目)を吸い上げてテンプレ化できるか検証。</li>
500
+ <li><b>動画アップロード</b>:upload-video → 動画ライブラリ反映 → 広告作成の一連を dry-run→少額 PoC で確認。</li>
501
+ <li><b>本番PoC</b>:PAUSED で1キャンペーン入稿 → 管理画面で目視確認 → 問題なければ配信オン。</li>
502
+ <li><b>GUI対応判断</b>:PoC の結果を踏まえ、プランナーのMeta対応(媒体切替・Meta用参照データ・入稿JSON書き出し)の要件を確定する。</li>
503
+ </ol>
504
+ </div></details>
505
+
506
+ <details class="sec" open><summary><span class="num">5</span><span class="stitle">未確定事項</span><span class="chev"></span></summary>
507
+ <div class="body">
508
+ <ul>
509
+ <li>Meta広告フィードバックの所在と最終確認者(タスク確認事項より・未回答)。</li>
510
+ <li>命名規則(YDAの <code>{画像番号}{c|s}-{title}L{desc}-{paramCode}</code> に相当するもの)をMetaでどう定義するか。</li>
511
+ <li>クリエイティブ検証の単位(YDAの「1キャンペーン=1変数」に相当する実験設計)。</li>
512
+ <li>プランナーGUIでのMeta対応範囲(シート列構成・参照データの持ち方)。</li>
513
+ </ul>
514
+ </div></details>
515
+ </div><!-- /mt-meta -->
516
+ </div>
517
+ </section>
518
+
519
+ <!-- ============ シートビュー ============ -->
520
+ <!-- ============ プランビュー (戦略メモ → 案件設定 → シート/ツリー) ============ -->
521
+ <section class="view active" id="view-sheet">
522
+ <details class="stratbox" id="stratBox">
523
+ <summary>戦略メモ(開閉)</summary>
524
+ <textarea id="stratInput" placeholder="この案件・検証の戦略/訴求方針をメモ(後乗せOK)"></textarea>
525
+ </details>
526
+ <details class="cfgbox" id="cfgBox" open>
527
+ <summary>案件設定</summary>
528
+ <div class="chipset" id="tplChips"></div>
529
+ </details>
530
+ <div class="seg" id="modeSeg" style="margin:0 0 10px">
531
+ <button data-m="sheet" class="active">シート</button>
532
+ <button data-m="tree">ツリー</button>
533
+ </div>
534
+ <div id="sheetArea">
535
+ <div class="seg" id="sheetTabs" style="margin:0 0 10px">
536
+ <button data-st="ads" class="active">広告</button>
537
+ <button data-st="groups">広告グループ</button>
538
+ <button data-st="campaigns">キャンペーン</button>
539
+ </div>
540
+ <div class="toolbar2">
541
+ <button class="act ghost" id="btnDupSel" title="選択した行を複製">⧉ 選択を複製</button>
542
+ <span class="hinttxt">セル選択(クリック/Shift+クリック/ドラッグ)→ Cmd/Ctrl+C コピー・Cmd/Ctrl+V 貼付(スプシから一括)</span>
543
+ <div class="spacer" style="flex:1"></div>
544
+ <span class="filt">表示 <select id="sheetFilter">
545
+ <option value="all">すべて</option>
546
+ <option value="err">エラーのみ</option>
547
+ <option value="banner">手入稿(バナー)のみ</option>
548
+ </select></span>
549
+ </div>
550
+ <div class="sheetwrap"><div id="sheetHost"></div></div>
551
+ </div>
552
+ <div id="treeArea" hidden>
553
+ <div class="toolbar2">
554
+ <div class="chipset"><span class="chip">キャンペーン <b id="cntCp">0</b></span><span class="chip">広告グループ <b id="cntAg">0</b></span><span class="chip">広告 <b id="cntAd">0</b></span></div>
555
+ <div class="spacer" style="flex:1"></div>
556
+ <button class="act ghost" id="btnExpandAll">すべて展開</button>
557
+ <button class="act ghost" id="btnCollapseAll">すべて折畳</button>
558
+ </div>
559
+ <div class="tree" id="treeHost"></div>
560
+ </div>
561
+ </section>
562
+
563
+ <!-- ============ 参照データビュー ============ -->
564
+ <section class="view" id="view-ref">
565
+ <div class="toolbar2">
566
+ <div class="seg" id="refTabs">
567
+ <button data-rt="titles" class="active">タイトル&amp;説明</button>
568
+ <button data-rt="media">登録画像</button>
569
+ </div>
570
+ <span class="filt">検索 <input id="refSearch" type="text" placeholder="番号・文言・IDで絞り込み" style="width:220px"></span>
571
+ <div class="spacer" style="flex:1"></div>
572
+ <span class="chip" id="refCount">—</span>
573
+ <button class="act" id="btnRefAdd">+ 新規登録</button>
574
+ <button class="act ghost" id="btnRefReset" title="埋め込みの初期値に戻す">リセット</button>
575
+ </div>
576
+ <div class="sheetwrap"><div id="refHost"></div></div>
577
+ </section>
578
+
579
+ <!-- ============ 履歴ビュー ============ -->
580
+ <section class="view" id="view-hist">
581
+ <div class="toolbar2">
582
+ <div class="chipset"><span class="chip" id="fsStatus">フォルダ未接続</span></div>
583
+ <div class="spacer" style="flex:1"></div>
584
+ <button class="act" id="btnConnectFolder">📁 保存フォルダに接続</button>
585
+ </div>
586
+ <div class="sheetwrap" style="padding:14px 18px"><div id="histHost"></div></div>
587
+ </section>
588
+ </main>
589
+
590
+ <div id="modalRoot"></div>
591
+ <div id="dlHost" hidden></div>
592
+ <div class="drawer-backdrop" id="drawerBackdrop"></div>
593
+ <aside class="refdrawer" id="refDrawer" aria-hidden="true">
594
+ <div class="dh"><span class="dt" id="drawerTitle">一覧</span><button class="dx" id="drawerClose" title="閉じる">✕</button></div>
595
+ <div class="dsearch"><input type="text" id="drawerSearch" placeholder="番号・内容で検索(任意)"></div>
596
+ <div class="dtarget" id="drawerTarget"></div>
597
+ <div class="dlist" id="drawerList"></div>
598
+ </aside>
599
+ <div class="toast" id="toast"></div>
600
+
601
+ <script id="embedData" type="application/json">{"headers": ["キャンペーンID", "広告グループID", "広告ID", "メディアID", "広告名", "配信設定", "広告タイプ", "メインメディアの形式", "タイトル", "説明文1", "説明文2", "ボタン", "主体者表記", "最終リンク先URL", "スマートフォン向けURL", "表示URLの形式", "トラッキングURL", "カスタムパラメータ", "表示URL", "価格のプレフィックス", "価格のサフィックス", "広告のベースカラー", "メディアID(ロゴ1)", "メディアID(ロゴ2)", "メディアID(ロゴ3)", "メディアID(動画サムネイル)", "第三者測定スクリプトタグsrc属性URL", "インプレッションビーコンURL", "ビューアブルインプレッションビーコンURL", "視聴ビーコンURL(再生開始)", "視聴ビーコンURL(3秒視聴)", "視聴ビーコンURL(10秒視聴)", "視聴ビーコンURL(25%再生)", "視聴ビーコンURL(50%再生)", "視聴ビーコンURL(75%再生)", "視聴ビーコンURL(再生完了)", "リンク先URL(キャンペーンバナー)", "メディアID(キャンペーンバナー600_500)", "メディアID(キャンペーンバナー600_600)", "メディアID(キャンペーンバナー600_80)", "メディアID(キャンペーンバナー600_1200)", "削除", "キャンペーン名(編集不可)", "広告グループ名(編集不可)", "メディアID1(画像・アセット)", "メディアID2(画像・アセット)", "メディアID3(画像・アセット)", "メディアID4(画像・アセット)", "メディアID5(画像・アセット)", "メディアID6(画像・アセット)", "メディアID7(画像・アセット)", "メディアID8(画像・アセット)", "メディアID9(画像・アセット)", "メディアID10(画像・アセット)", "メディアID11(画像・アセット)", "メディアID12(画像・アセット)", "メディアID13(画像・アセット)", "メディアID14(画像・アセット)", "メディアID15(画像・アセット)", "タイトル1(アセット)", "タイトル2(アセット)", "タイトル3(アセット)", "タイトル4(アセット)", "タイトル5(アセット)", "説明文1(アセット)", "説明文2(アセット)", "説明文3(アセット)", "説明文4(アセット)", "説明文5(アセット)", "メディアID(カルーセルカード1)", "タイトル(カルーセルカード1)", "説明文(カルーセルカード1)", "最終リンク先URL(カルーセルカード1)", "スマートフォン向けURL(カルーセルカード1)", "メディアID(カルーセルカード2)", "タイトル(カルーセルカード2)", "説明文(カルーセルカード2)", "最終リンク先URL(カルーセルカード2)", "スマートフォン向けURL(カルーセルカード2)", "メディアID(カルーセルカード3)", "タイトル(カルーセルカード3)", "説明文(カルーセルカード3)", "最終リンク先URL(カルーセルカード3)", "スマートフォン向けURL(カルーセルカード3)", "メディアID(カルーセルカード4)", "タイトル(カルーセルカード4)", "説明文(カルーセルカード4)", "最終リンク先URL(カルーセルカード4)", "スマートフォン向けURL(カルーセルカード4)", "メディアID(カルーセルカード5)", "タイトル(カルーセルカード5)", "説明文(カルーセルカード5)", "最終リンク先URL(カルーセルカード5)", "スマートフォン向けURL(カルーセルカード5)", "メディアID(カルーセルカード6)", "タイトル(カルーセルカード6)", "説明文(カルーセルカード6)", "最終リンク先URL(カルーセルカード6)", "スマートフォン向けURL(カルーセルカード6)", "メディアID(カルーセルカード7)", "タイトル(カルーセルカード7)", "説明文(カルーセルカード7)", "最終リンク先URL(カルーセルカード7)", "スマートフォン向けURL(カルーセルカード7)", "メディアID(カルーセルカード8)", "タイトル(カルーセルカード8)", "説明文(カルーセルカード8)", "最終リンク先URL(カルーセルカード8)", "スマートフォン向けURL(カルーセルカード8)", "メディアID(カルーセルカード9)", "タイトル(カルーセルカード9)", "説明文(カルーセルカード9)", "最終リンク先URL(カルーセルカード9)", "スマートフォン向けURL(カルーセルカード9)", "メディアID(カルーセルカード10)", "タイトル(カルーセルカード10)", "説明文(カルーセルカード10)", "最終リンク先URL(カルーセルカード10)", "スマートフォン向けURL(カルーセルカード10)", "インプレッション数(編集不可)", "クリック数(編集不可)", "クリック率(編集不可)", "コスト(編集不可)", "平均CPC(編集不可)", "ビューアブルインプレッション数(編集不可)", "平均掲載順位(編集不可)", "平均CPM(編集不可)", "平均vCPM(編集不可)", "コンバージョン数(編集不可)", "コンバージョン率(編集不可)", "コスト/コンバージョン数(編集不可)", "コンバージョンの価値(編集不可)", "コンバージョンの価値/コンバージョン数(編集不可)", "コンバージョンの価値/コスト(編集不可)", "クロスデバイスコンバージョン数(編集不可)", "ビュースルーコンバージョン数(編集不可)", "コンバージョン数(全て)(編集不可)", "コンバージョン率(全て)(編集不可)", "コスト/コンバージョン数(全て)(編集不可)", "コンバージョンの価値(全て)(編集不可)", "コンバージョンの価値(全て)/コンバージョン数(全て)(編集不可)", "コンバージョンの価値(全て)/コスト(編集不可)", "コンバージョン数(クリック経由)(編集不可)", "コンバージョン率(クリック経由)(編集不可)", "コスト/コンバージョン数(クリック経由)(編集不可)", "コンバージョンの価値(クリック経由)(編集不可)", "コンバージョンの価値(クリック経由)/コンバージョン数(クリック経由)(編集不可)", "コンバージョンの価値(クリック経由)/コスト(編集不可)", "ビューアブルインプレッション率(編集不可)", "ビューアブルクリック数(編集不可)", "ビューアブルクリック率(編集不可)", "メジャードインプレッション数(編集不可)", "メジャードインプレッション測定率(編集不可)", "動画の10秒再生数(編集不可)", "平均CPV(編集不可)", "動画の再生開始数(編集不可)", "動画の3秒再生数(編集不可)", "動画の25%再生数(編集不可)", "動画の50%再生数(編集不可)", "動画の75%再生数(編集不可)", "動画の95%再生数(編集不可)", "動画の100%再生数(編集不可)", "動画の平均再生率(編集不可)", "動画の平均再生時間(秒)(編集不可)", "動画の再生完了率(編集不可)", "", "", "", ""], "titles": [{"no": 1, "text": "CMで話題。腰痛薬が一度だけお得に買える"}, {"no": 2, "text": "神経痛に飲んで効く『80代もメキメキ』"}, {"no": 3, "text": "業界激震。神経痛のお薬が一度だけ買える"}, {"no": 4, "text": "業界激震。腰痛のお薬が一度だけお得に買える。"}, {"no": 5, "text": "業界激震。腰痛のお薬が一度だけ買える"}, {"no": 6, "text": "CM放送で話題。しびれに効くお薬半額以下"}, {"no": 7, "text": "CM放送で話題。腰痛に効くお薬が半額以下"}, {"no": 8, "text": "CM放送で話題。神経痛に効くお薬半額以下"}, {"no": 9, "text": "しびれにも効く腰痛のお薬。本日半額以下。"}, {"no": 10, "text": "『しびれの原因が…』放置NGの理由に驚愕"}, {"no": 11, "text": "『腰痛の原因が…』業界が震えた。飲むお薬"}, {"no": 12, "text": "『神経痛の原因が』業界が震えた。飲むお薬"}, {"no": 13, "text": "嘘みたいな腰のお薬。本日まさかの半額以下"}, {"no": 14, "text": "10年悩んだ腰痛が…富山のお薬が半額以下"}, {"no": 15, "text": "『治らなかった腰痛が』腰のお薬飲んでみて"}, {"no": 16, "text": "テレビCMで話題。腰のお薬が本日半額以下"}, {"no": 17, "text": "『神経痛に効く』嘘みたいなお薬が登場"}, {"no": 18, "text": "『関節痛に効く』嘘みたいなお薬が登場"}, {"no": 19, "text": "『しびれに効く』嘘みたいなお薬が登場"}, {"no": 20, "text": "『神経痛に効く』富山のお薬が本日半額以下"}, {"no": 21, "text": "『関節痛に効く』富山のお薬が本日半額以下"}, {"no": 22, "text": "『しびれに効く』富山のお薬が本日半額以下"}, {"no": 23, "text": "『腰痛に効く』富山のお薬が本日半額以下"}, {"no": 24, "text": "階段きつい人やって。腰痛に飲んで効くお薬"}, {"no": 25, "text": "起き上がれない人へ。腰痛に飲んで効くお薬"}, {"no": 26, "text": "肩が上がらない人へ。五十肩に効くお薬すご"}, {"no": 27, "text": "五十肩で眠れない人へ。飲んで効くお薬スゴ"}, {"no": 28, "text": "走れない70代はやって。腰痛のお薬が話題"}, {"no": 29, "text": "70歳過ぎたらやって。腰痛に飲んで効く薬"}, {"no": 30, "text": "歩けなくなる前にやって。腰痛のお薬飲むと"}, {"no": 31, "text": "正座できない70代やって。飲む腰痛改善薬"}, {"no": 32, "text": "「サプリではない」腰痛の裏ワザがすごすぎ"}, {"no": 33, "text": "「サプリではない」神経痛の裏ワザが凄すぎ"}, {"no": 34, "text": "「サプリではない」しびれの裏ワザが凄すぎ"}, {"no": 35, "text": "「サプリではない」関節痛の裏ワザが凄すぎ"}, {"no": 36, "text": "「サプリではない」しびれの裏ワザが凄すぎ"}, {"no": 37, "text": "「腰痛が嘘みたいに...」腰痛改善薬すご"}, {"no": 38, "text": "10年我慢した腰痛が…【飲んで効くお薬】"}, {"no": 39, "text": "腰痛の裏ワザが判明。70代に爆売れ"}, {"no": 40, "text": "腰痛の裏ワザが判明。80代に爆売れ"}, {"no": 41, "text": "腰痛の裏技が判明。テレビCM放送後爆売れ"}, {"no": 42, "text": "今すぐやめて。腰痛悪化の原因が判明"}, {"no": 43, "text": "『マジで80代?』腰痛改善薬飲むと…。"}, {"no": 44, "text": "『朝起きて仰天』腰に効くお薬飲んで寝ると"}, {"no": 45, "text": "「腰痛くて起きれない」CMで話題のお薬"}, {"no": 46, "text": "グルコサミンではない。CMで話題の腰の薬"}, {"no": 47, "text": "ギシギシ腰は飲んで。80代『改善薬すご』"}, {"no": 48, "text": "立ち上がる度に痛む腰に。飲む改善薬スゴ"}, {"no": 49, "text": "77歳「歩くたびに…」腰のSOS気づいて"}, {"no": 50, "text": "腰痛治らない理由やばい。60代過ぎたら"}, {"no": 51, "text": "腰痛治らない理由やばい。70代過ぎたら"}, {"no": 52, "text": "腰痛治らない理由やばい。80代過ぎても"}, {"no": 53, "text": "『腰痛に効く』嘘みたいなお薬。すごすぎ"}, {"no": 54, "text": "テレビCM放送後爆売れ。腰痛のお薬が一度だけお得に買える。"}, {"no": 55, "text": "CMで話題。腰のお薬一度だけお得に買える"}, {"no": 56, "text": "『神経痛に効く』一度だけお得に買えるお薬"}, {"no": 57, "text": "『関節痛に効く』一度だけお得に買えるお薬"}, {"no": 58, "text": "『しびれに効く』一度だけお得に買えるお薬"}, {"no": 59, "text": "『腰痛に効く』一度だけお得に買えるお薬"}, {"no": 60, "text": "『腰痛に効く』嘘みたいなお薬。一度だけお得に買えると話題。"}, {"no": 61, "text": "「朝から腰が痛い」CMで話題のお薬"}, {"no": 62, "text": "「腰が痛くて動けない」CMで話題のお薬"}, {"no": 63, "text": "「腰が痛くて眠れない」CMで話題のお薬"}, {"no": 64, "text": "「腰が痛くて歩けない」CMで話題のお薬"}, {"no": 65, "text": "「草むしりで腰痛に」CMで話題のお薬"}, {"no": 66, "text": "「孫を抱けない腰痛」CMで話題のお薬"}, {"no": 67, "text": "「腰がズキズキ痛む」CMで話題のお薬"}, {"no": 68, "text": "「腰がズーンと重い」CMで話題のお薬"}, {"no": 69, "text": "「腰がじんじん痛い」CMで話題のお薬"}, {"no": 70, "text": "「腰がガチガチに痛い」CMで話題のお薬"}, {"no": 71, "text": "「腰に鈍痛が続く」CMで話題のお薬"}, {"no": 72, "text": "「腰の奥が痛い」CMで話題のお薬"}, {"no": 73, "text": "「腰も膝も痛い」CMで話題のお薬"}, {"no": 74, "text": "「お尻から腰が痛い」CMで話題のお薬"}, {"no": 75, "text": "「しびれで眠れない」CMで話題のお薬"}, {"no": 76, "text": "「しびれで歩けない」CMで話題のお薬"}, {"no": 77, "text": "「足がしびれて立てない」CMで話題のお薬"}, {"no": 78, "text": "「腰から足がしびれる」CMで話題のお薬"}, {"no": 79, "text": "「足のしびれが続く」CMで話題のお薬"}, {"no": 80, "text": "「足がしびれて辛い」CMで話題のお薬"}, {"no": 81, "text": "「腰と足がしびれる」CMで話題のお薬"}, {"no": 82, "text": "「お尻から足がしびれる」CMで話題のお薬"}, {"no": 83, "text": ""}, {"no": 84, "text": "坐骨神経痛に伴うしびれに効く。神経のお薬"}, {"no": 85, "text": "ビリビリしびれる人やって。神経痛のお薬"}, {"no": 86, "text": "ピリピリしびれる人やって。神経痛のお薬"}, {"no": 87, "text": "チクチクしびれる人やって。神経痛のお薬"}, {"no": 88, "text": "ズキズキしびれる人やって。神経痛のお薬"}, {"no": 89, "text": "ジンジンしびれる人やって。神経痛のお薬"}, {"no": 90, "text": "しびれで夜目が覚める人やって。神経改善薬"}, {"no": 91, "text": "突然来るしびれに飲んで仰天。神経改善薬"}, {"no": 92, "text": "手足のしびれに飲んで効く。神経のお薬"}, {"no": 93, "text": "【CM放送で話題】神経に効くしびれ改善薬"}, {"no": 94, "text": "お得に買えるのは一度だけ。神経のお薬すご"}, {"no": 95, "text": ""}, {"no": 96, "text": "70歳過ぎたらやって。しびれに効くお薬"}, {"no": 97, "text": "70歳過ぎたらやって。1日約64円で…"}, {"no": 98, "text": "『サプリではない』神経の修復成分すご"}, {"no": 99, "text": "痛む腰簡単ケア。原因が判明『やばすぎ…』"}, {"no": 100, "text": "『腰痛が嘘みたいに…』富山のお薬すご"}, {"no": 101, "text": "薬都の瓶薬すご…『70代の腰はまかせて』"}, {"no": 102, "text": "10年我慢した腰痛が…【すごいお薬】でた"}, {"no": 103, "text": "腰痛の原因が判明。神経の痛みに効く方法"}, {"no": 104, "text": "走れない70代はやって。痛みのお薬スゴ"}, {"no": 105, "text": "腰痛の裏ワザが判明。70代に爆売れ"}, {"no": 106, "text": "腰痛の裏ワザが判明。80代に爆売れ"}, {"no": 107, "text": "今すぐやめて。神経痛が悪化する原因が判明"}, {"no": 108, "text": "『マジで80代?』腰痛のお薬飲むと…"}, {"no": 109, "text": "『マジで70代?』腰痛のお薬飲むと…"}, {"no": 110, "text": "薬都の会社「腰痛に効く」嘘みたいなお薬"}, {"no": 111, "text": "歩けなくなる前にやって。富山の飲み薬"}, {"no": 112, "text": "『しびれが嘘みたいに』富山のお薬すご"}, {"no": 113, "text": "まるで別人級『70代のシビレはまかせて』"}, {"no": 114, "text": "10年我慢したしびれが【すごいお薬】でた"}, {"no": 115, "text": "しびれの原因が判明。何歳でも効く方法"}, {"no": 116, "text": "走れない70代はやって。しびれのお薬スゴ"}, {"no": 117, "text": "しびれの裏ワザが判明。70代に爆売れ"}, {"no": 118, "text": "しびれの裏ワザが判明。80代に爆売れ"}, {"no": 119, "text": "今すぐやめて。しびれが治らない原因が…"}, {"no": 120, "text": "『マジで80代?』しびれのお薬飲むと…"}, {"no": 121, "text": "『マジで70代?』しびれのお薬飲むと…"}, {"no": 122, "text": "薬都の会社「しびれに効く」嘘みたいなお薬"}, {"no": 123, "text": "手足のしびれにスーッと。嘘みたいなお薬"}, {"no": 124, "text": "突然手足がしびれる人やって。富山のお薬"}, {"no": 125, "text": "立ち上がるとき痛い腰に。とんでもないお薬"}, {"no": 126, "text": "雨の日ひどくなる腰痛に。とんでもないお薬"}, {"no": 127, "text": "雨の日しびれる手足に。とんでもないお薬"}, {"no": 128, "text": "しびれ・腰痛に飲んで効く嘘みたいなお薬"}, {"no": 129, "text": "朝起きて腰痛い人へ。CMで話題のお薬"}, {"no": 130, "text": "神経に異常?しびれたら飲んで。富山のお薬"}, {"no": 131, "text": "富山の薬売りが開発!神経痛に効くお薬"}, {"no": 132, "text": "ジンジンしびれる人は使って。富山の瓶薬"}, {"no": 133, "text": "ズキズキ痛む腰にやって。神経痛に効くお薬"}, {"no": 134, "text": "年齢は関係なかった?飲んで衝撃のお薬"}, {"no": 135, "text": "しびれ放置してる人やって。富山の瓶薬"}, {"no": 136, "text": "腰の痛み放置してる人はやって。富山の瓶薬"}, {"no": 137, "text": "年々ひどくなる腰痛に。飲んで効くお薬すご"}, {"no": 138, "text": "年々ひどくなるしびれに飲んで。富山の瓶薬"}, {"no": 139, "text": "同じ体勢できない人はやって。薬都の瓶薬"}, {"no": 140, "text": "「正座がツラい」腰痛に飲んで。薬都の瓶薬"}, {"no": 141, "text": "手足のしびれは神経の悲鳴?薬都の瓶薬すご"}, {"no": 142, "text": "坐骨神経からくる痛みに!薬都の瓶薬すご"}, {"no": 143, "text": "坐骨神経からくるしびれに!薬都の瓶薬すご"}, {"no": 144, "text": "変形性関節からくる膝痛に!薬都の瓶薬すご"}, {"no": 145, "text": "頸椎からくる神経痛に!薬都の瓶薬がすごい"}, {"no": 146, "text": "頸椎からくるしびれに!薬都の瓶薬がすごい"}, {"no": 147, "text": "脊柱管狭窄からくる腰痛に!薬都の瓶薬すご"}, {"no": 148, "text": "腰にビリっとくる神経痛に!薬都の瓶薬すご"}, {"no": 149, "text": "原因は坐骨神経の圧迫?薬都の瓶薬が効く"}, {"no": 150, "text": "原因は神経の異常?しびれに効く瓶薬すご"}, {"no": 151, "text": "一度しか買えない。腰のお薬が1回約64円"}, {"no": 152, "text": "飲むだけで効く瓶薬!70代が使って感動"}, {"no": 153, "text": ""}, {"no": 154, "text": ""}, {"no": 155, "text": ""}, {"no": 156, "text": ""}, {"no": 157, "text": ""}, {"no": 158, "text": ""}, {"no": 159, "text": ""}, {"no": 160, "text": ""}, {"no": 161, "text": ""}, {"no": 162, "text": ""}, {"no": 163, "text": ""}, {"no": 164, "text": ""}, {"no": 165, "text": ""}, {"no": 166, "text": ""}, {"no": 167, "text": ""}, {"no": 168, "text": ""}, {"no": 169, "text": ""}, {"no": 170, "text": ""}, {"no": 171, "text": ""}, {"no": 172, "text": ""}, {"no": 173, "text": ""}, {"no": 174, "text": ""}, {"no": 175, "text": ""}, {"no": 176, "text": ""}, {"no": 177, "text": ""}, {"no": 178, "text": ""}, {"no": 179, "text": ""}, {"no": 180, "text": ""}, {"no": 181, "text": ""}, {"no": 182, "text": ""}, {"no": 183, "text": ""}, {"no": 184, "text": ""}, {"no": 185, "text": ""}, {"no": 186, "text": ""}, {"no": 187, "text": ""}, {"no": 188, "text": ""}, {"no": 189, "text": ""}, {"no": 190, "text": ""}, {"no": 191, "text": ""}, {"no": 192, "text": ""}, {"no": 193, "text": ""}, {"no": 194, "text": ""}, {"no": 195, "text": ""}, {"no": 196, "text": ""}, {"no": 197, "text": ""}, {"no": 198, "text": ""}, {"no": 199, "text": ""}, {"no": 200, "text": ""}, {"no": 201, "text": ""}, {"no": 202, "text": ""}, {"no": 203, "text": ""}, {"no": 204, "text": ""}, {"no": 205, "text": ""}, {"no": 206, "text": ""}, {"no": 207, "text": ""}, {"no": 208, "text": ""}, {"no": 209, "text": ""}, {"no": 210, "text": ""}, {"no": 211, "text": ""}, {"no": 212, "text": ""}, {"no": 213, "text": ""}, {"no": 214, "text": ""}, {"no": 215, "text": ""}, {"no": 216, "text": ""}, {"no": 217, "text": ""}, {"no": 218, "text": ""}, {"no": 219, "text": ""}, {"no": 220, "text": ""}, {"no": 221, "text": ""}, {"no": 222, "text": ""}, {"no": 223, "text": ""}, {"no": 224, "text": ""}, {"no": 225, "text": ""}, {"no": 226, "text": ""}, {"no": 227, "text": ""}, {"no": 228, "text": ""}, {"no": 229, "text": ""}, {"no": 230, "text": ""}, {"no": 231, "text": ""}, {"no": 232, "text": ""}, {"no": 233, "text": ""}, {"no": 234, "text": ""}, {"no": 235, "text": ""}, {"no": 236, "text": ""}, {"no": 237, "text": ""}, {"no": 238, "text": ""}, {"no": 239, "text": ""}, {"no": 240, "text": ""}, {"no": 241, "text": ""}, {"no": 242, "text": ""}, {"no": 243, "text": ""}, {"no": 244, "text": ""}, {"no": 245, "text": ""}, {"no": 246, "text": ""}, {"no": 247, "text": ""}, {"no": 248, "text": ""}, {"no": 249, "text": ""}, {"no": 250, "text": ""}, {"no": 251, "text": ""}, {"no": 252, "text": ""}, {"no": 253, "text": ""}, {"no": 254, "text": ""}, {"no": 255, "text": ""}, {"no": 256, "text": ""}, {"no": 257, "text": ""}, {"no": 258, "text": ""}, {"no": 259, "text": ""}, {"no": 260, "text": ""}, {"no": 261, "text": ""}, {"no": 262, "text": ""}, {"no": 263, "text": ""}, {"no": 264, "text": ""}, {"no": 265, "text": ""}, {"no": 266, "text": ""}, {"no": 267, "text": ""}, {"no": 268, "text": ""}, {"no": 269, "text": ""}, {"no": 270, "text": ""}, {"no": 271, "text": ""}, {"no": 272, "text": ""}, {"no": 273, "text": ""}, {"no": 274, "text": ""}, {"no": 275, "text": ""}, {"no": 276, "text": ""}, {"no": 277, "text": ""}, {"no": 278, "text": ""}, {"no": 279, "text": ""}, {"no": 280, "text": ""}, {"no": 281, "text": ""}, {"no": 282, "text": ""}, {"no": 283, "text": ""}, {"no": 284, "text": ""}, {"no": 285, "text": ""}, {"no": 286, "text": ""}, {"no": 287, "text": ""}, {"no": 288, "text": ""}, {"no": 289, "text": ""}, {"no": 290, "text": ""}, {"no": 291, "text": ""}, {"no": 292, "text": ""}, {"no": 293, "text": ""}, {"no": 294, "text": ""}, {"no": 295, "text": ""}, {"no": 296, "text": ""}, {"no": 297, "text": ""}, {"no": 298, "text": ""}, {"no": 299, "text": ""}, {"no": 300, "text": ""}, {"no": 301, "text": ""}, {"no": 302, "text": ""}, {"no": 303, "text": ""}, {"no": 304, "text": ""}, {"no": 305, "text": ""}, {"no": 306, "text": ""}, {"no": 307, "text": ""}, {"no": 308, "text": ""}, {"no": 309, "text": ""}, {"no": 310, "text": ""}, {"no": 311, "text": ""}, {"no": 312, "text": ""}, {"no": 313, "text": ""}, {"no": 314, "text": ""}, {"no": 315, "text": ""}, {"no": 316, "text": ""}, {"no": 317, "text": ""}, {"no": 318, "text": ""}, {"no": 319, "text": ""}, {"no": 320, "text": ""}, {"no": 321, "text": ""}, {"no": 322, "text": ""}, {"no": 323, "text": ""}, {"no": 324, "text": ""}, {"no": 325, "text": ""}, {"no": 326, "text": ""}, {"no": 327, "text": ""}, {"no": 328, "text": ""}, {"no": 329, "text": ""}, {"no": 330, "text": ""}, {"no": 331, "text": ""}, {"no": 332, "text": ""}, {"no": 333, "text": ""}, {"no": 334, "text": ""}, {"no": 335, "text": ""}, {"no": 336, "text": ""}, {"no": 337, "text": ""}, {"no": 338, "text": ""}, {"no": 339, "text": ""}, {"no": 340, "text": ""}, {"no": 341, "text": ""}, {"no": 342, "text": ""}, {"no": 343, "text": ""}, {"no": 344, "text": ""}, {"no": 345, "text": ""}, {"no": 346, "text": ""}, {"no": 347, "text": ""}, {"no": 348, "text": ""}, {"no": 349, "text": ""}, {"no": 350, "text": ""}, {"no": 351, "text": ""}, {"no": 352, "text": ""}, {"no": 353, "text": ""}, {"no": 354, "text": ""}, {"no": 355, "text": ""}, {"no": 356, "text": ""}, {"no": 357, "text": ""}, {"no": 358, "text": ""}, {"no": 359, "text": ""}, {"no": 360, "text": ""}, {"no": 361, "text": ""}, {"no": 362, "text": ""}, {"no": 363, "text": ""}, {"no": 364, "text": ""}, {"no": 365, "text": ""}, {"no": 366, "text": ""}, {"no": 367, "text": ""}, {"no": 368, "text": ""}, {"no": 369, "text": ""}, {"no": 370, "text": ""}, {"no": 371, "text": ""}, {"no": 372, "text": ""}, {"no": 373, "text": ""}, {"no": 374, "text": ""}, {"no": 375, "text": ""}, {"no": 376, "text": ""}, {"no": 377, "text": ""}, {"no": 378, "text": ""}, {"no": 379, "text": ""}, {"no": 380, "text": ""}, {"no": 381, "text": ""}, {"no": 382, "text": ""}, {"no": 383, "text": ""}, {"no": 384, "text": ""}, {"no": 385, "text": ""}, {"no": 386, "text": ""}, {"no": 387, "text": ""}, {"no": 388, "text": ""}, {"no": 389, "text": ""}, {"no": 390, "text": ""}, {"no": 391, "text": ""}, {"no": 392, "text": ""}, {"no": 393, "text": ""}, {"no": 394, "text": ""}, {"no": 395, "text": ""}, {"no": 396, "text": ""}, {"no": 397, "text": ""}, {"no": 398, "text": ""}, {"no": 399, "text": ""}, {"no": 400, "text": ""}, {"no": 401, "text": ""}, {"no": 402, "text": ""}, {"no": 403, "text": ""}, {"no": 404, "text": ""}, {"no": 405, "text": ""}, {"no": 406, "text": ""}, {"no": 407, "text": ""}, {"no": 408, "text": ""}, {"no": 409, "text": ""}, {"no": 410, "text": ""}, {"no": 411, "text": ""}, {"no": 412, "text": ""}, {"no": 413, "text": ""}, {"no": 414, "text": ""}, {"no": 415, "text": ""}, {"no": 416, "text": ""}, {"no": 417, "text": ""}, {"no": 418, "text": ""}, {"no": 419, "text": ""}, {"no": 420, "text": ""}, {"no": 421, "text": ""}, {"no": 422, "text": ""}, {"no": 423, "text": ""}, {"no": 424, "text": ""}, {"no": 425, "text": ""}, {"no": 426, "text": ""}, {"no": 427, "text": ""}, {"no": 428, "text": ""}, {"no": 429, "text": ""}, {"no": 430, "text": ""}, {"no": 431, "text": ""}, {"no": 432, "text": ""}, {"no": 433, "text": ""}, {"no": 434, "text": ""}, {"no": 435, "text": ""}, {"no": 436, "text": ""}, {"no": 437, "text": ""}, {"no": 438, "text": ""}, {"no": 439, "text": ""}, {"no": 440, "text": ""}, {"no": 441, "text": ""}, {"no": 442, "text": ""}, {"no": 443, "text": ""}, {"no": 444, "text": ""}, {"no": 445, "text": ""}, {"no": 446, "text": ""}, {"no": 447, "text": ""}, {"no": 448, "text": ""}, {"no": 449, "text": ""}, {"no": 450, "text": ""}, {"no": 451, "text": ""}, {"no": 452, "text": ""}, {"no": 453, "text": ""}, {"no": 454, "text": ""}, {"no": 455, "text": ""}, {"no": 456, "text": ""}, {"no": 457, "text": ""}, {"no": 458, "text": ""}, {"no": 459, "text": ""}, {"no": 460, "text": ""}, {"no": 461, "text": ""}, {"no": 462, "text": ""}, {"no": 463, "text": ""}, {"no": 464, "text": ""}, {"no": 465, "text": ""}, {"no": 466, "text": ""}, {"no": 467, "text": ""}, {"no": 468, "text": ""}, {"no": 469, "text": ""}, {"no": 470, "text": ""}, {"no": 471, "text": ""}, {"no": 472, "text": ""}, {"no": 473, "text": ""}, {"no": 474, "text": ""}, {"no": 475, "text": ""}, {"no": 476, "text": ""}, {"no": 477, "text": ""}, {"no": 478, "text": ""}, {"no": 479, "text": ""}, {"no": 480, "text": ""}, {"no": 481, "text": ""}, {"no": 482, "text": ""}, {"no": 483, "text": ""}, {"no": 484, "text": ""}, {"no": 485, "text": ""}, {"no": 486, "text": ""}, {"no": 487, "text": ""}, {"no": 488, "text": ""}, {"no": 489, "text": ""}, {"no": 490, "text": ""}, {"no": 491, "text": ""}, {"no": 492, "text": ""}, {"no": 493, "text": ""}, {"no": 494, "text": ""}, {"no": 495, "text": ""}, {"no": 496, "text": ""}, {"no": 497, "text": ""}, {"no": 498, "text": ""}, {"no": 499, "text": ""}, {"no": 500, "text": ""}, {"no": 501, "text": ""}, {"no": 502, "text": ""}, {"no": 503, "text": ""}, {"no": 504, "text": ""}, {"no": 505, "text": ""}, {"no": 506, "text": ""}, {"no": 507, "text": ""}, {"no": 508, "text": ""}, {"no": 509, "text": ""}, {"no": 510, "text": ""}, {"no": 511, "text": ""}, {"no": 512, "text": ""}, {"no": 513, "text": ""}, {"no": 514, "text": ""}, {"no": 515, "text": ""}, {"no": 516, "text": ""}, {"no": 517, "text": ""}, {"no": 518, "text": ""}, {"no": 519, "text": ""}, {"no": 520, "text": ""}, {"no": 521, "text": ""}, {"no": 522, "text": ""}, {"no": 523, "text": ""}, {"no": 524, "text": ""}, {"no": 525, "text": ""}, {"no": 526, "text": ""}, {"no": 527, "text": ""}, {"no": 528, "text": ""}, {"no": 529, "text": ""}, {"no": 530, "text": ""}, {"no": 531, "text": ""}, {"no": 532, "text": ""}, {"no": 533, "text": ""}, {"no": 534, "text": ""}, {"no": 535, "text": ""}, {"no": 536, "text": ""}, {"no": 537, "text": ""}, {"no": 538, "text": ""}, {"no": 539, "text": ""}, {"no": 540, "text": ""}, {"no": 541, "text": ""}, {"no": 542, "text": ""}, {"no": 543, "text": ""}, {"no": 544, "text": ""}, {"no": 545, "text": ""}, {"no": 546, "text": ""}, {"no": 547, "text": ""}, {"no": 548, "text": ""}, {"no": 549, "text": ""}, {"no": 550, "text": ""}, {"no": 551, "text": ""}, {"no": 552, "text": ""}, {"no": 553, "text": ""}, {"no": 554, "text": ""}, {"no": 555, "text": ""}, {"no": 556, "text": ""}, {"no": 557, "text": ""}, {"no": 558, "text": ""}, {"no": 559, "text": ""}, {"no": 560, "text": ""}, {"no": 561, "text": ""}, {"no": 562, "text": ""}, {"no": 563, "text": ""}, {"no": 564, "text": ""}, {"no": 565, "text": ""}, {"no": 566, "text": ""}, {"no": 567, "text": ""}, {"no": 568, "text": ""}, {"no": 569, "text": ""}, {"no": 570, "text": ""}, {"no": 571, "text": ""}, {"no": 572, "text": ""}, {"no": 573, "text": ""}, {"no": 574, "text": ""}, {"no": 575, "text": ""}, {"no": 576, "text": ""}, {"no": 577, "text": ""}, {"no": 578, "text": ""}, {"no": 579, "text": ""}, {"no": 580, "text": ""}, {"no": 581, "text": ""}, {"no": 582, "text": ""}, {"no": 583, "text": ""}, {"no": 584, "text": ""}, {"no": 585, "text": ""}, {"no": 586, "text": ""}, {"no": 587, "text": ""}, {"no": 588, "text": ""}, {"no": 589, "text": ""}, {"no": 590, "text": ""}, {"no": 591, "text": ""}, {"no": 592, "text": ""}, {"no": 593, "text": ""}, {"no": 594, "text": ""}, {"no": 595, "text": ""}, {"no": 596, "text": ""}, {"no": 597, "text": ""}, {"no": 598, "text": ""}, {"no": 599, "text": ""}, {"no": 600, "text": ""}, {"no": 601, "text": ""}, {"no": 602, "text": ""}, {"no": 603, "text": ""}, {"no": 604, "text": ""}, {"no": 605, "text": ""}, {"no": 606, "text": ""}, {"no": 607, "text": ""}, {"no": 608, "text": ""}, {"no": 609, "text": ""}, {"no": 610, "text": ""}, {"no": 611, "text": ""}, {"no": 612, "text": ""}, {"no": 613, "text": ""}, {"no": 614, "text": ""}, {"no": 615, "text": ""}, {"no": 616, "text": ""}, {"no": 617, "text": ""}, {"no": 618, "text": ""}, {"no": 619, "text": ""}, {"no": 620, "text": ""}, {"no": 621, "text": ""}, {"no": 622, "text": ""}, {"no": 623, "text": ""}, {"no": 624, "text": ""}, {"no": 625, "text": ""}, {"no": 626, "text": ""}, {"no": 627, "text": ""}, {"no": 628, "text": ""}, {"no": 629, "text": ""}, {"no": 630, "text": ""}, {"no": 631, "text": ""}, {"no": 632, "text": ""}, {"no": 633, "text": ""}, {"no": 634, "text": ""}, {"no": 635, "text": ""}, {"no": 636, "text": ""}, {"no": 637, "text": ""}, {"no": 638, "text": ""}, {"no": 639, "text": ""}, {"no": 640, "text": ""}, {"no": 641, "text": ""}, {"no": 642, "text": ""}, {"no": 643, "text": ""}, {"no": 644, "text": ""}, {"no": 645, "text": ""}, {"no": 646, "text": ""}, {"no": 647, "text": ""}, {"no": 648, "text": ""}, {"no": 649, "text": ""}, {"no": 650, "text": ""}, {"no": 651, "text": ""}, {"no": 652, "text": ""}, {"no": 653, "text": ""}, {"no": 654, "text": ""}, {"no": 655, "text": ""}, {"no": 656, "text": ""}, {"no": 657, "text": ""}, {"no": 658, "text": ""}, {"no": 659, "text": ""}, {"no": 660, "text": ""}, {"no": 661, "text": ""}, {"no": 662, "text": ""}, {"no": 663, "text": ""}, {"no": 664, "text": ""}, {"no": 665, "text": ""}, {"no": 666, "text": ""}, {"no": 667, "text": ""}, {"no": 668, "text": ""}, {"no": 669, "text": ""}, {"no": 670, "text": ""}, {"no": 671, "text": ""}, {"no": 672, "text": ""}, {"no": 673, "text": ""}, {"no": 674, "text": ""}, {"no": 675, "text": ""}, {"no": 676, "text": ""}, {"no": 677, "text": ""}, {"no": 678, "text": ""}, {"no": 679, "text": ""}, {"no": 680, "text": ""}, {"no": 681, "text": ""}, {"no": 682, "text": ""}, {"no": 683, "text": ""}, {"no": 684, "text": ""}, {"no": 685, "text": ""}, {"no": 686, "text": ""}, {"no": 687, "text": ""}, {"no": 688, "text": ""}, {"no": 689, "text": ""}, {"no": 690, "text": ""}, {"no": 691, "text": ""}, {"no": 692, "text": ""}, {"no": 693, "text": ""}, {"no": 694, "text": ""}, {"no": 695, "text": ""}, {"no": 696, "text": ""}, {"no": 697, "text": ""}, {"no": 698, "text": ""}, {"no": 699, "text": ""}, {"no": 700, "text": ""}, {"no": 701, "text": ""}, {"no": 702, "text": ""}, {"no": 703, "text": ""}, {"no": 704, "text": ""}, {"no": 705, "text": ""}, {"no": 706, "text": ""}, {"no": 707, "text": ""}, {"no": 708, "text": ""}, {"no": 709, "text": ""}, {"no": 710, "text": ""}, {"no": 711, "text": ""}, {"no": 712, "text": ""}, {"no": 713, "text": ""}, {"no": 714, "text": ""}, {"no": 715, "text": ""}, {"no": 716, "text": ""}, {"no": 717, "text": ""}, {"no": 718, "text": ""}, {"no": 719, "text": ""}, {"no": 720, "text": ""}, {"no": 721, "text": ""}, {"no": 722, "text": ""}, {"no": 723, "text": ""}, {"no": 724, "text": ""}, {"no": 725, "text": ""}, {"no": 726, "text": ""}, {"no": 727, "text": ""}, {"no": 728, "text": ""}, {"no": 729, "text": ""}, {"no": 730, "text": ""}, {"no": 731, "text": ""}, {"no": 732, "text": ""}, {"no": 733, "text": ""}, {"no": 734, "text": ""}, {"no": 735, "text": ""}, {"no": 736, "text": ""}, {"no": 737, "text": ""}, {"no": 738, "text": ""}, {"no": 739, "text": ""}, {"no": 740, "text": ""}, {"no": 741, "text": ""}, {"no": 742, "text": ""}, {"no": 743, "text": ""}, {"no": 744, "text": ""}, {"no": 745, "text": ""}, {"no": 746, "text": ""}, {"no": 747, "text": ""}, {"no": 748, "text": ""}, {"no": 749, "text": ""}, {"no": 750, "text": ""}, {"no": 751, "text": ""}, {"no": 752, "text": ""}, {"no": 753, "text": ""}, {"no": 754, "text": ""}, {"no": 755, "text": ""}, {"no": 756, "text": ""}, {"no": 757, "text": ""}, {"no": 758, "text": ""}, {"no": 759, "text": ""}, {"no": 760, "text": ""}, {"no": 761, "text": ""}, {"no": 762, "text": ""}, {"no": 763, "text": ""}, {"no": 764, "text": ""}, {"no": 765, "text": ""}, {"no": 766, "text": ""}, {"no": 767, "text": ""}, {"no": 768, "text": ""}, {"no": 769, "text": ""}, {"no": 770, "text": ""}, {"no": 771, "text": ""}, {"no": 772, "text": ""}, {"no": 773, "text": ""}, {"no": 774, "text": ""}, {"no": 775, "text": ""}, {"no": 776, "text": ""}, {"no": 777, "text": ""}, {"no": 778, "text": ""}, {"no": 779, "text": ""}, {"no": 780, "text": ""}, {"no": 781, "text": ""}, {"no": 782, "text": ""}, {"no": 783, "text": ""}, {"no": 784, "text": ""}, {"no": 785, "text": ""}, {"no": 786, "text": ""}, {"no": 787, "text": ""}, {"no": 788, "text": ""}, {"no": 789, "text": ""}, {"no": 790, "text": ""}, {"no": 791, "text": ""}, {"no": 792, "text": ""}, {"no": 793, "text": ""}, {"no": 794, "text": ""}, {"no": 795, "text": ""}, {"no": 796, "text": ""}, {"no": 797, "text": ""}, {"no": 798, "text": ""}, {"no": 799, "text": ""}, {"no": 800, "text": ""}, {"no": 801, "text": ""}, {"no": 802, "text": ""}, {"no": 803, "text": ""}, {"no": 804, "text": ""}, {"no": 805, "text": ""}, {"no": 806, "text": ""}, {"no": 807, "text": ""}, {"no": 808, "text": ""}, {"no": 809, "text": ""}, {"no": 810, "text": ""}, {"no": 811, "text": ""}, {"no": 812, "text": ""}, {"no": 813, "text": ""}, {"no": 814, "text": ""}, {"no": 815, "text": ""}, {"no": 816, "text": ""}, {"no": 817, "text": ""}, {"no": 818, "text": ""}, {"no": 819, "text": ""}, {"no": 820, "text": ""}, {"no": 821, "text": ""}, {"no": 822, "text": ""}, {"no": 823, "text": ""}, {"no": 824, "text": ""}, {"no": 825, "text": ""}, {"no": 826, "text": ""}, {"no": 827, "text": ""}, {"no": 828, "text": ""}, {"no": 829, "text": ""}, {"no": 830, "text": ""}, {"no": 831, "text": ""}, {"no": 832, "text": ""}, {"no": 833, "text": ""}, {"no": 834, "text": ""}, {"no": 835, "text": ""}, {"no": 836, "text": ""}, {"no": 837, "text": ""}, {"no": 838, "text": ""}, {"no": 839, "text": ""}, {"no": 840, "text": ""}, {"no": 841, "text": ""}, {"no": 842, "text": ""}, {"no": 843, "text": ""}, {"no": 844, "text": ""}, {"no": 845, "text": ""}, {"no": 846, "text": ""}, {"no": 847, "text": ""}, {"no": 848, "text": ""}, {"no": 849, "text": ""}, {"no": 850, "text": ""}, {"no": 851, "text": ""}, {"no": 852, "text": ""}, {"no": 853, "text": ""}, {"no": 854, "text": ""}, {"no": 855, "text": ""}, {"no": 856, "text": ""}, {"no": 857, "text": ""}, {"no": 858, "text": ""}, {"no": 859, "text": ""}, {"no": 860, "text": ""}, {"no": 861, "text": ""}, {"no": 862, "text": ""}, {"no": 863, "text": ""}, {"no": 864, "text": ""}, {"no": 865, "text": ""}, {"no": 866, "text": ""}, {"no": 867, "text": ""}, {"no": 868, "text": ""}, {"no": 869, "text": ""}, {"no": 870, "text": ""}, {"no": 871, "text": ""}, {"no": 872, "text": ""}, {"no": 873, "text": ""}, {"no": 874, "text": ""}, {"no": 875, "text": ""}, {"no": 876, "text": ""}, {"no": 877, "text": ""}, {"no": 878, "text": ""}, {"no": 879, "text": ""}, {"no": 880, "text": ""}, {"no": 881, "text": ""}, {"no": 882, "text": ""}, {"no": 883, "text": ""}, {"no": 884, "text": ""}, {"no": 885, "text": ""}, {"no": 886, "text": ""}, {"no": 887, "text": ""}, {"no": 888, "text": ""}, {"no": 889, "text": ""}, {"no": 890, "text": ""}, {"no": 891, "text": ""}, {"no": 892, "text": ""}, {"no": 893, "text": ""}, {"no": 894, "text": ""}, {"no": 895, "text": ""}, {"no": 896, "text": ""}, {"no": 897, "text": ""}, {"no": 898, "text": ""}, {"no": 899, "text": ""}, {"no": 900, "text": ""}, {"no": 901, "text": ""}, {"no": 902, "text": ""}, {"no": 903, "text": ""}, {"no": 904, "text": ""}, {"no": 905, "text": ""}, {"no": 906, "text": ""}, {"no": 907, "text": ""}, {"no": 908, "text": ""}, {"no": 909, "text": ""}, {"no": 910, "text": ""}, {"no": 911, "text": ""}, {"no": 912, "text": ""}, {"no": 913, "text": ""}, {"no": 914, "text": ""}, {"no": 915, "text": ""}, {"no": 916, "text": ""}, {"no": 917, "text": ""}, {"no": 918, "text": ""}, {"no": 919, "text": ""}, {"no": 920, "text": ""}, {"no": 921, "text": ""}, {"no": 922, "text": ""}, {"no": 923, "text": ""}, {"no": 924, "text": ""}, {"no": 925, "text": ""}, {"no": 926, "text": ""}, {"no": 927, "text": ""}, {"no": 928, "text": ""}, {"no": 929, "text": ""}, {"no": 930, "text": ""}, {"no": 931, "text": ""}, {"no": 932, "text": ""}, {"no": 933, "text": ""}, {"no": 934, "text": ""}, {"no": 935, "text": ""}, {"no": 936, "text": ""}, {"no": 937, "text": ""}, {"no": 938, "text": ""}, {"no": 939, "text": ""}, {"no": 940, "text": ""}, {"no": 941, "text": ""}, {"no": 942, "text": ""}, {"no": 943, "text": ""}, {"no": 944, "text": ""}, {"no": 945, "text": ""}, {"no": 946, "text": ""}, {"no": 947, "text": ""}, {"no": 948, "text": ""}, {"no": 949, "text": ""}, {"no": 950, "text": ""}, {"no": 951, "text": ""}, {"no": 952, "text": ""}, {"no": 953, "text": ""}, {"no": 954, "text": ""}, {"no": 955, "text": ""}, {"no": 956, "text": ""}, {"no": 957, "text": ""}, {"no": 958, "text": ""}, {"no": 959, "text": ""}, {"no": 960, "text": ""}, {"no": 961, "text": ""}, {"no": 962, "text": ""}, {"no": 963, "text": ""}, {"no": 964, "text": ""}, {"no": 965, "text": ""}, {"no": 966, "text": ""}, {"no": 967, "text": ""}, {"no": 968, "text": ""}, {"no": 969, "text": ""}, {"no": 970, "text": ""}, {"no": 971, "text": ""}, {"no": 972, "text": ""}, {"no": 973, "text": ""}, {"no": 974, "text": ""}, {"no": 975, "text": ""}, {"no": 976, "text": ""}, {"no": 977, "text": ""}, {"no": 978, "text": ""}, {"no": 979, "text": ""}, {"no": 980, "text": ""}, {"no": 981, "text": ""}, {"no": 982, "text": ""}, {"no": 983, "text": ""}, {"no": 984, "text": ""}, {"no": 985, "text": ""}, {"no": 986, "text": ""}, {"no": 987, "text": ""}, {"no": 988, "text": ""}, {"no": 989, "text": ""}, {"no": 990, "text": ""}, {"no": 991, "text": ""}, {"no": 992, "text": ""}, {"no": 993, "text": ""}, {"no": 994, "text": ""}, {"no": 995, "text": ""}, {"no": 996, "text": ""}, {"no": 997, "text": ""}, {"no": 998, "text": ""}, {"no": 999, "text": ""}], "media": {"199": {"card": "21300350", "sq": "21295932"}, "203": {"card": "21300349", "sq": "21295921"}, "209": {"card": "21300348", "sq": "21295909"}, "202": {"card": "21300347", "sq": "21295923"}, "201": {"card": "21300346", "sq": "21295926"}, "200": {"card": "21300345", "sq": "21295930"}, "197": {"card": "21300344", "sq": "21295936"}, "198": {"card": "21300343", "sq": "21295933"}, "196": {"card": "21300342", "sq": "21295938"}, "195": {"card": "21300287"}, "193": {"card": "21300289"}, "194": {"card": "21300288"}, "192": {"card": "21300290"}, "189": {"card": "21300293"}, "187": {"card": "21300295"}, "190": {"card": "21300292"}, "191": {"card": "21300291"}, "188": {"card": "21300294"}, "186": {"card": "21300296"}, "211": {"card": "21300331", "sq": "21295904"}, "207": {"card": "21300330", "sq": "21295913"}, "185": {"card": "21300297"}, "184": {"card": "21300298"}, "183": {"card": "21300301"}, "204": {"sq": "21295919"}, "205": {"sq": "21295917"}, "206": {"sq": "21295915"}, "208": {"sq": "21295911"}, "210": {"sq": "21295906"}, "212": {"sq": "21295902", "card": "21288098"}, "213": {"sq": "21295901", "card": "21288099"}, "214": {"sq": "21295898", "card": "21288102"}, "215": {"sq": "21295897"}, "216": {"card": "21288101"}, "250": {"card": "21295893"}, "256": {"card": "21295891"}, "255": {"card": "21295889"}, "257": {"card": "21295887"}, "259": {"card": "21295884"}, "253": {"card": "21295882"}, "269": {"sq": "21295880"}, "254": {"card": "21295878"}, "268": {"sq": "21295876"}, "252": {"card": "21295873"}, "251": {"card": "21295872"}, "267": {"sq": "21295869"}, "266": {"sq": "21295868"}, "265": {"sq": "21295866"}, "264": {"sq": "21295864"}, "262": {"sq": "21295862"}, "263": {"sq": "21295860"}, "261": {"sq": "21295858"}, "277": {"card": "21295856"}, "279": {"card": "21295854"}, "260": {"sq": "21295852"}, "278": {"card": "21295851"}, "275": {"card": "21295844"}, "272": {"card": "21295842"}, "271": {"card": "21295839"}, "270": {"card": "21295837"}, "273": {"card": "21295835"}, "40": {"card": "21288980"}, "38": {"card": "21288979"}, "36": {"card": "21288978"}, "39": {"card": "21288976"}, "37": {"card": "21288975"}, "114": {"card": "21257163", "sq": "21257157"}, "113": {"sq": "21257160", "card": "21257154"}, "112": {"card": "21257158", "sq": "21257155"}, "109": {"card": "21257153", "sq": "21257147"}, "107": {"card": "21257152", "sq": "21257149"}, "108": {"card": "21257151", "sq": "21257150"}, "106": {"card": "21257146", "sq": "21257145"}, "103": {"sq": "21257143", "card": "21257139"}, "104": {"card": "21257142", "sq": "21257141"}, "102": {"card": "21257140", "sq": "21257135"}, "101": {"sq": "21257137", "card": "21257133"}, "99": {"card": "21257136", "sq": "21257130"}, "100": {"sq": "21257134", "card": "21257132"}, "97": {"sq": "21257131", "card": "21257128"}, "98": {"sq": "21257129", "card": "21257127"}, "96": {"card": "21257126", "sq": "21257122"}, "95": {"sq": "21257125", "card": "21257124"}, "94": {"sq": "21257121", "card": "21257120"}, "93": {"card": "21257118", "sq": "21257114"}, "78": {"card": "21257117"}, "82": {"card": "21257116"}, "79": {"card": "21257115"}, "88": {"card": "21257113"}, "80": {"card": "21257112"}, "70": {"sq": "21214638"}, "65": {"sq": "21214613", "card": "21543680"}, "64": {"sq": "21214612"}, "62": {"sq": "21214611", "card": "21543684"}, "67": {"sq": "21214610"}, "68": {"sq": "21214609", "card": "21543691"}, "63": {"sq": "21214608", "card": "21543689"}, "66": {"sq": "21214607", "card": "21543682"}, "69": {"sq": "21214606", "card": "21543687"}, "87": {"card": "21118108"}, "92": {"sq": "21118107"}, "86": {"card": "21118105"}, "81": {"card": "21118104"}, "73": {"sq": "21118103"}, "76": {"sq": "21118102"}, "77": {"sq": "21118101"}, "72": {"sq": "21118099"}, "58": {"sq": "21118098"}, "59": {"sq": "21118097"}, "50": {"sq": "21118096"}, "52": {"sq": "21118095"}, "54": {"sq": "21118094"}, "53": {"sq": "21118093"}, "51": {"sq": "21118092"}, "49": {"sq": "21118091"}, "46": {"sq": "21118090"}, "48": {"sq": "21118089"}, "45": {"sq": "21118088"}, "43": {"sq": "21118087"}, "42": {"sq": "21118086"}, "35": {"card": "21118085"}, "30": {"card": "21118079"}, "28": {"card": "21118077"}, "27": {"card": "21118076"}, "24": {"card": "21118073"}, "10": {"card": "21112942"}, "9": {"card": "21112940"}, "8": {"card": "21112936"}, "5": {"card": "21112934"}, "2": {"card": "21112932"}, "4": {"card": "21112929"}, "1": {"card": "21112926"}, "3": {"card": "21112923"}, "26": {"card": "21112919"}, "32": {"card": "21112917"}, "33": {"card": "21112915"}, "31": {"card": "21112914"}, "29": {"card": "21112913"}, "25": {"card": "21112912"}, "23": {"card": "21112911"}, "21": {"card": "21112910"}, "22": {"card": "21112907"}, "20": {"card": "21112906"}, "19": {"card": "21112905"}, "18": {"card": "21112904"}, "16": {"card": "21112903"}, "12": {"card": "21112902"}, "15": {"card": "21112901"}, "17": {"card": "21112900"}, "14": {"card": "21112899"}, "13": {"card": "21112898"}, "340": {"card": "21397519"}, "343": {"card": "21397518"}, "342": {"card": "21397517"}, "338": {"card": "21397516"}, "339": {"card": "21397515"}, "344": {"card": "21397514"}, "335": {"card": "21397513"}, "336": {"card": "21397512"}, "332": {"card": "21397510"}, "333": {"card": "21397509"}, "337": {"card": "21397508"}, "334": {"card": "21397507"}, "328": {"card": "21397506"}, "327": {"card": "21397505"}, "331": {"card": "21397504"}, "330": {"card": "21397503"}, "326": {"card": "21397502"}, "329": {"card": "21397501"}, "321": {"card": "21397500"}, "323": {"card": "21397499"}, "325": {"card": "21397498"}, "322": {"card": "21397497"}, "320": {"card": "21397496"}, "324": {"card": "21397495"}, "316": {"card": "21397494"}, "317": {"card": "21397493"}, "313": {"card": "21397492"}, "318": {"card": "21397490"}, "314": {"card": "21397489"}, "315": {"card": "21397487"}, "310": {"card": "21397486"}, "309": {"card": "21397485"}, "312": {"card": "21397484"}, "311": {"card": "21397483"}, "307": {"card": "21397482"}, "308": {"card": "21397481"}, "305": {"card": "21397480"}, "302": {"card": "21397479"}, "306": {"card": "21397478"}, "301": {"card": "21397477"}, "304": {"card": "21397476"}, "303": {"card": "21397475"}, "347": {"card": "21397474"}, "345": {"card": "21397473"}, "346": {"card": "21397472"}, "348": {"card": "21397471"}, "299": {"card": "21397470"}, "300": {"card": "21397468"}, "282": {"card": "21397448"}, "284": {"card": "21397447"}, "283": {"card": "21397446"}, "449": {"card": "21397445"}, "281": {"card": "21397444"}, "280": {"card": "21397443"}, "446": {"card": "21397442"}, "448": {"card": "21397441"}, "447": {"card": "21397440"}, "441": {"card": "21397439"}, "445": {"card": "21397438"}, "439": {"card": "21397437"}, "442": {"card": "21397436"}, "443": {"card": "21397435"}, "440": {"card": "21397434"}, "437": {"card": "21397433"}, "436": {"card": "21397432"}, "434": {"card": "21397431"}, "438": {"card": "21397430"}, "435": {"card": "21397429"}, "433": {"card": "21397428"}, "431": {"card": "21397427"}, "429": {"card": "21397426"}, "427": {"card": "21397425"}, "430": {"card": "21397424"}, "428": {"card": "21397423"}, "432": {"card": "21397422"}, "426": {"card": "21397421"}, "421": {"card": "21397420"}, "420": {"card": "21397419"}, "425": {"card": "21397418"}, "423": {"card": "21397417"}, "424": {"card": "21397416"}, "418": {"card": "21397415"}, "415": {"card": "21397414"}, "414": {"card": "21397413"}, "416": {"card": "21397412"}, "419": {"card": "21397411"}, "417": {"card": "21397410"}, "410": {"card": "21397409"}, "408": {"card": "21397408"}, "413": {"card": "21397407"}, "411": {"card": "21397406"}, "412": {"card": "21397405"}, "409": {"card": "21397404"}, "402": {"card": "21397402"}, "406": {"card": "21397401"}, "405": {"card": "21397400"}, "407": {"card": "21397399"}, "404": {"card": "21397398"}, "403": {"card": "21397397"}, "397": {"card": "21397396"}, "396": {"card": "21397395"}, "398": {"card": "21397394"}, "399": {"card": "21397393"}, "401": {"card": "21397392"}, "400": {"card": "21397391"}, "391": {"card": "21397390"}, "390": {"card": "21397389"}, "395": {"card": "21397388"}, "392": {"card": "21397387"}, "394": {"card": "21397386"}, "393": {"card": "21397385"}, "386": {"card": "21397383"}, "384": {"card": "21397382"}, "387": {"card": "21397381"}, "388": {"card": "21397380"}, "389": {"card": "21397379"}, "385": {"card": "21397378"}, "381": {"card": "21397377"}, "382": {"card": "21397376"}, "379": {"card": "21397375"}, "378": {"card": "21397374"}, "383": {"card": "21397373"}, "380": {"card": "21397372"}, "374": {"card": "21397371"}, "373": {"card": "21397370"}, "377": {"card": "21397369"}, "372": {"card": "21397368"}, "375": {"card": "21397367"}, "376": {"card": "21397365"}, "370": {"card": "21397364"}, "368": {"card": "21397363"}, "369": {"card": "21397362"}, "366": {"card": "21397361"}, "371": {"card": "21397360"}, "367": {"card": "21397359"}, "363": {"card": "21397358"}, "364": {"card": "21397357"}, "362": {"card": "21397356"}, "360": {"card": "21397355"}, "361": {"card": "21397354"}, "365": {"card": "21397353"}, "358": {"card": "21397352"}, "353": {"card": "21397351"}, "357": {"card": "21397350"}, "355": {"card": "21397349"}, "359": {"card": "21397348"}, "356": {"card": "21397347"}, "352": {"card": "21397346"}, "349": {"card": "21397345"}, "350": {"card": "21397344"}, "351": {"card": "21397343"}, "297": {"card": "21397342"}, "298": {"card": "21397341"}, "294": {"card": "21397340"}, "292": {"card": "21397339"}, "295": {"card": "21397338"}, "296": {"card": "21397337"}, "291": {"card": "21397336"}, "293": {"card": "21397335"}, "290": {"card": "21397334"}, "286": {"card": "21397333"}, "285": {"card": "21397332"}, "287": {"card": "21397331"}, "289": {"card": "21397330"}, "288": {"card": "21397329"}, "460": {"card": "21409111"}, "466": {"card": "21409110"}, "459": {"card": "21409109"}, "461": {"card": "21409108"}, "457": {"card": "21409107"}, "455": {"card": "21409106"}, "454": {"card": "21409105"}, "453": {"card": "21409104"}, "458": {"card": "21409103"}, "456": {"card": "21409102"}, "450": {"card": "21409101"}, "470": {"card": "21409100"}, "469": {"card": "21409099"}, "452": {"card": "21409098"}, "471": {"card": "21409097"}, "451": {"card": "21409096"}, "468": {"card": "21409095"}, "464": {"card": "21409094"}, "463": {"card": "21409093"}, "462": {"card": "21409092"}, "465": {"card": "21409091"}, "467": {"card": "21409090"}, "578": {"card": "21541864"}, "579": {"card": "21541863"}, "580": {"card": "21541862"}, "572": {"card": "21541861"}, "581": {"card": "21541860"}, "583": {"card": "21541859"}, "573": {"card": "21541858"}, "577": {"card": "21541857"}, "582": {"card": "21541856"}, "576": {"card": "21541855"}, "574": {"card": "21541854"}, "575": {"card": "21541853"}, "761": {"card": "21600497"}, "755": {"card": "21600494"}, "759": {"card": "21600493"}, "758": {"card": "21600492"}, "756": {"card": "21600491"}, "760": {"card": "21600490"}, "757": {"card": "21600489"}, "752": {"card": "21600488"}, "754": {"card": "21600487"}, "751": {"card": "21600485"}, "750": {"card": "21600483"}, "749": {"card": "21600482"}, "753": {"card": "21600481"}, "745": {"card": "21600480"}, "744": {"card": "21600479"}, "746": {"card": "21600478"}, "747": {"card": "21600477"}, "748": {"card": "21600474"}, "743": {"card": "21600472"}, "738": {"card": "21600469"}, "739": {"card": "21600468"}, "737": {"card": "21600467"}, "740": {"card": "21600466"}, "741": {"card": "21600465"}, "742": {"card": "21600464"}, "731": {"card": "21600463"}, "732": {"card": "21600462"}, "734": {"card": "21600461"}, "733": {"card": "21600460"}, "736": {"card": "21600459"}, "735": {"card": "21600458"}, "730": {"card": "21600457"}, "727": {"card": "21600456"}, "729": {"card": "21600454"}, "725": {"card": "21600453"}, "728": {"card": "21600452"}, "726": {"card": "21600451"}, "723": {"card": "21600450"}, "721": {"card": "21600449"}, "720": {"card": "21600448"}, "724": {"card": "21600447"}, "719": {"card": "21600446"}, "722": {"card": "21600445"}, "715": {"card": "21600444"}, "717": {"card": "21600443"}, "713": {"card": "21600442"}, "718": {"card": "21600441"}, "714": {"card": "21600440"}, "716": {"card": "21600439"}, "708": {"card": "21600438"}, "709": {"card": "21600437"}, "710": {"card": "21600436"}, "712": {"card": "21600434"}, "711": {"card": "21600433"}, "707": {"card": "21600432"}, "669": {"sq": "21600431"}, "702": {"card": "21600430"}, "705": {"card": "21600429"}, "704": {"card": "21600428"}, "703": {"card": "21600427"}, "706": {"card": "21600425"}, "701": {"card": "21600756"}, "696": {"card": "21600755"}, "671": {"card": "21600754"}, "672": {"card": "21600753"}, "676": {"card": "21600751"}, "692": {"card": "21600750"}, "688": {"card": "21600748"}, "685": {"card": "21600747"}, "697": {"card": "21600746"}, "694": {"card": "21600745"}, "698": {"card": "21600744"}, "699": {"card": "21600742"}, "700": {"card": "21600741"}, "695": {"card": "21600740"}, "687": {"card": "21600739"}, "691": {"card": "21600738"}, "686": {"card": "21600736"}, "690": {"card": "21600735"}, "693": {"card": "21600734"}, "689": {"card": "21600733"}, "679": {"card": "21600731"}, "680": {"card": "21600730"}, "682": {"card": "21600729"}, "684": {"card": "21600728"}, "681": {"card": "21600727"}, "683": {"card": "21600725"}, "678": {"card": "21600724"}, "673": {"card": "21600723"}, "677": {"card": "21600722"}, "670": {"card": "21600721"}, "674": {"card": "21600720"}, "675": {"card": "21600718"}, "847": {"card": "21612301"}, "846": {"card": "21612300"}, "844": {"card": "21612299"}, "843": {"card": "21612298"}, "842": {"card": "21612297"}, "845": {"card": "21612296"}, "841": {"card": "21612295"}, "840": {"card": "21612294"}, "838": {"card": "21612293"}, "837": {"card": "21612292"}, "839": {"card": "21612291"}, "836": {"card": "21612290"}, "834": {"card": "21612289"}, "835": {"card": "21612288"}, "828": {"card": "21612287"}, "833": {"card": "21612286"}, "831": {"card": "21612285"}, "832": {"card": "21612284"}, "830": {"card": "21612283"}, "829": {"card": "21612282"}, "825": {"card": "21612281"}, "824": {"card": "21612280"}, "826": {"card": "21612279"}, "827": {"card": "21612278"}, "822": {"card": "21612277"}, "823": {"card": "21612276"}, "817": {"card": "21612275"}, "821": {"card": "21612274"}, "820": {"card": "21612273"}, "818": {"card": "21612272"}, "816": {"card": "21612271"}, "819": {"card": "21612270"}, "812": {"card": "21612269"}, "810": {"card": "21612266"}, "811": {"card": "21612265"}, "815": {"card": "21612264"}, "814": {"card": "21612263"}, "813": {"card": "21612262"}, "806": {"card": "21612261"}, "807": {"card": "21612260"}, "808": {"card": "21612259"}, "809": {"card": "21612258"}, "804": {"card": "21612257"}, "805": {"card": "21612256"}, "799": {"card": "21612255"}, "802": {"card": "21612254"}, "801": {"card": "21612253"}, "798": {"card": "21612252"}, "800": {"card": "21612251"}, "803": {"card": "21612250"}, "793": {"card": "21612249"}, "795": {"card": "21612247"}, "796": {"card": "21612246"}, "797": {"card": "21612245"}, "792": {"card": "21612244"}, "794": {"card": "21612243"}, "786": {"card": "21612242"}, "788": {"card": "21612241"}, "791": {"card": "21612240"}, "789": {"card": "21612239"}, "787": {"card": "21612238"}, "790": {"card": "21612237"}, "783": {"card": "21612236"}, "784": {"card": "21612235"}, "781": {"card": "21612234"}, "780": {"card": "21612233"}, "785": {"card": "21612232"}, "782": {"card": "21612231"}, "778": {"card": "21612230"}, "775": {"card": "21612229"}, "779": {"card": "21612228"}, "776": {"card": "21612227"}, "774": {"card": "21612226"}, "777": {"card": "21612225"}, "773": {"card": "21612224"}, "768": {"card": "21612223"}, "771": {"card": "21612222"}, "772": {"card": "21612221"}, "769": {"card": "21612220"}, "770": {"card": "21612219"}, "763": {"card": "21612218"}, "765": {"card": "21612217"}, "762": {"card": "21612216"}, "767": {"card": "21612215"}, "766": {"card": "21612214"}, "764": {"card": "21612213"}, "1040": {"card": "21667290"}, "1042": {"card": "21667287"}, "1036": {"card": "21667283"}, "1038": {"card": "21667282"}, "1035": {"card": "21667281"}, "1037": {"card": "21667280"}, "1039": {"card": "21667279"}, "1034": {"card": "21667278"}, "1033": {"card": "21667277"}, "1048": {"card": "21667276"}, "1050": {"card": "21667275"}, "1051": {"card": "21667274"}, "1032": {"card": "21667273"}, "1049": {"card": "21667272"}, "1043": {"card": "21667271"}, "1046": {"card": "21667270"}, "1044": {"card": "21667269"}, "1041": {"card": "21667268"}, "1047": {"card": "21667267"}, "1045": {"card": "21667265"}, "977": {"card": "21659293"}, "975": {"card": "21659290"}, "976": {"card": "21659286"}, "979": {"card": "21659283"}, "970": {"card": "21659281"}, "973": {"card": "21659279"}, "974": {"card": "21659277"}, "972": {"card": "21659275"}, "971": {"card": "21659274"}, "978": {"card": "21659273"}, "1002": {"card": "21659271"}, "1003": {"card": "21659270"}, "1005": {"card": "21659269"}, "1004": {"card": "21659268"}, "1001": {"card": "21659267"}, "1021": {"card": "21659266"}, "1024": {"card": "21659265"}, "1000": {"card": "21659264"}, "1023": {"card": "21659263"}, "1022": {"card": "21659262"}, "1016": {"card": "21659261"}, "1020": {"card": "21659260"}, "1017": {"card": "21659259"}, "1018": {"card": "21659258"}, "1015": {"card": "21659257"}, "1019": {"card": "21659256"}, "1013": {"card": "21659255"}, "1014": {"card": "21659254"}, "1011": {"card": "21659253"}, "1010": {"card": "21659252"}, "1012": {"card": "21659251"}, "1009": {"card": "21659250"}, "1031": {"card": "21659249"}, "1030": {"card": "21659248"}, "1008": {"card": "21659247"}, "1006": {"card": "21659246"}, "1029": {"card": "21659245"}, "1007": {"card": "21659244"}, "996": {"card": "21659243"}, "997": {"card": "21659242"}, "1028": {"card": "21659241"}, "1026": {"card": "21659240"}, "1025": {"card": "21659239"}, "1027": {"card": "21659238"}, "991": {"card": "21659237"}, "992": {"card": "21659236"}, "990": {"card": "21659235"}, "989": {"card": "21659234"}, "995": {"card": "21659232"}, "987": {"card": "21659231"}, "986": {"card": "21659230"}, "984": {"card": "21659229"}, "983": {"card": "21659228"}, "981": {"card": "21659225"}, "982": {"card": "21659222"}, "985": {"card": "21659220"}, "993": {"card": "21659218"}, "998": {"card": "21659216"}, "994": {"card": "21659215"}, "980": {"card": "21659212"}, "999": {"card": "21659209"}, "988": {"card": "21659207"}, "967": {"card": "21659037"}, "959": {"card": "21659036"}, "961": {"card": "21659035"}, "965": {"card": "21659034"}, "960": {"card": "21659033"}, "966": {"card": "21659032"}, "968": {"card": "21659031"}, "969": {"card": "21659029"}, "963": {"card": "21659028"}, "958": {"card": "21659027"}, "962": {"card": "21659026"}, "964": {"card": "21659025"}, "957": {"card": "21667629"}, "954": {"card": "21667628"}, "956": {"card": "21667627"}, "955": {"card": "21667626"}, "951": {"card": "21667625"}, "948": {"card": "21667624"}, "950": {"card": "21667621"}, "952": {"card": "21667619"}, "953": {"card": "21667616"}, "949": {"card": "21667612"}}, "groups": [{"paramCode": "ks_Cac_10", "campaignId": "32444865", "adGroupId": "451324015", "groupName": "【ks_Cac_10】電話番号全期間×Card画像固定テキスト検証_0605"}, {"paramCode": "jv_Cac_55", "campaignId": "32455449", "adGroupId": "451396524", "groupName": "【jv_Cac_55】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_56", "campaignId": "32455450", "adGroupId": "451396526", "groupName": "【jv_Cac_56】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_57", "campaignId": "32455451", "adGroupId": "451396528", "groupName": "【jv_Cac_57】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_58", "campaignId": "32455452", "adGroupId": "451396530", "groupName": "【jv_Cac_58】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_59", "campaignId": "32455453", "adGroupId": "451396532", "groupName": "【jv_Cac_59】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_60", "campaignId": "32455454", "adGroupId": "451396534", "groupName": "【jv_Cac_60】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_61", "campaignId": "32455455", "adGroupId": "451396536", "groupName": "【jv_Cac_61】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "jv_Cac_62", "campaignId": "32455456", "adGroupId": "451396538", "groupName": "【jv_Cac_62】TELスマタゲ_カード画像検証_20260612"}, {"paramCode": "", "campaignId": "32460012", "adGroupId": "451423856", "groupName": "jv_Cac_63】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460012", "adGroupId": "451423956", "groupName": "jv_Cac_64】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460015", "adGroupId": "451423873", "groupName": "jv_Cac_65】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460015", "adGroupId": "451424000", "groupName": "jv_Cac_66】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460015", "adGroupId": "451424002", "groupName": "jv_Cac_67】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460018", "adGroupId": "451423886", "groupName": "jv_Cac_68】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460018", "adGroupId": "451424006", "groupName": "jv_Cac_69】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "", "campaignId": "32460021", "adGroupId": "451423902", "groupName": "jv_Cac_70】TELスマタゲ_カード画像検証_20260616"}, {"paramCode": "jv_Cac_71", "campaignId": "32460342", "adGroupId": "451424838", "groupName": "【jv_Cac_71】ブロード年齢ちょい広_カード画像検証_20260616"}, {"paramCode": "jv_Cac_72", "campaignId": "32460342", "adGroupId": "451424837", "groupName": "【jv_Cac_72】ブロード年齢ちょい広_カード画像検証_20260616"}, {"paramCode": "jv_Cac_73", "campaignId": "32460345", "adGroupId": "451424847", "groupName": "【jv_Cac_73】ブロード年齢ちょい広_カード画像検証_20260616"}, {"paramCode": "jv_Cac_74", "campaignId": "32460345", "adGroupId": "451424846", "groupName": "【jv_Cac_74】ブロード年齢ちょい広_カード画像検証_20260616"}, {"paramCode": "jv_Cac_75", "campaignId": "32460345", "adGroupId": "451424845", "groupName": "【jv_Cac_75】ブロード年齢ちょい広_カード画像検証_20260616"}, {"paramCode": "jv_Cac_76", "campaignId": "32460344", "adGroupId": "451424843", "groupName": "【jv_Cac_76】ブロード年齢ちょい広_カード像検証_20260616"}, {"paramCode": "jv_Cac_77", "campaignId": "32460344", "adGroupId": "451424842", "groupName": "【jv_Cac_77】ブロード年齢ちょい広_カード画像検証_20260616"}, {"paramCode": "jv_Cac_78", "campaignId": "32460343", "adGroupId": "451424840", "groupName": "【jv_Cac_78】ブロード年齢ちょい広_カード像検証_20260616"}, {"paramCode": "jv_Cac_83", "campaignId": "32460946", "adGroupId": "451428809", "groupName": "【jv_Cac_83】女性_好調まとめ_サンクス類似10_20260616"}, {"paramCode": "jv_Cac_82", "campaignId": "32460946", "adGroupId": "451428807", "groupName": "【jv_Cac_82】女性_好調まとめ_サンクス類似10_20260616"}, {"paramCode": "jv_Cac_79", "campaignId": "32460932", "adGroupId": "451428754", "groupName": "【jv_Cac_79】男性_好調まとめ_サンクス類似10_20260616"}, {"paramCode": "jv_Cac_80", "campaignId": "32460932", "adGroupId": "451428721", "groupName": "【jv_Cac_80】男性_好調まとめ_サンクス類似10_20260616"}, {"paramCode": "jv_Cac_86", "campaignId": "32462465", "adGroupId": "451435537", "groupName": "【jv_Cac_86】男性_好調フォーマット画像検証_サンクス類似10_20260617"}, {"paramCode": "jv_Cac_87", "campaignId": "32462465", "adGroupId": "451435536", "groupName": "【jv_Cac_87】男性_好調フォーマット画像検証_サンクス類似10_20260617"}, {"paramCode": "jv_Cac_0", "campaignId": "32463816", "adGroupId": "451453510", "groupName": "【jv_Cac_0】DEMO_コピー用"}, {"paramCode": "jv_Cac_48", "campaignId": "32449955", "adGroupId": "451453944", "groupName": "【jv_Cac_48】DEMO_画像固定テキスト検証画像30_20260618"}, {"paramCode": "jv_Cac_49", "campaignId": "32449955", "adGroupId": "451453946", "groupName": "【jv_Cac_49】DEMO_画像固定テキスト検証画像30_20260618"}, {"paramCode": "jv_Cac_50", "campaignId": "32449955", "adGroupId": "451453948", "groupName": "【jv_Cac_50】DEMO_画像固定テキスト検証画像30_20260618"}, {"paramCode": "jv_Cac_51", "campaignId": "32449955", "adGroupId": "451453950", "groupName": "【jv_Cac_51】DEMO_画像固定テキスト検証画像30_20260618"}, {"paramCode": "jv_Cac_91", "campaignId": "32469296", "adGroupId": "451492129", "groupName": "【jv_Cac_91】男性_画像固定テキスト検証_サンクス類似10_20260622"}, {"paramCode": "jv_Cac_90", "campaignId": "32469296", "adGroupId": "451492095", "groupName": "【jv_Cac_90】男性_画像固定テキスト検証_サンクス類似10_20260622"}, {"paramCode": "jv_Cac_92", "campaignId": "32486920", "adGroupId": "451538014", "groupName": "【jv_Cac_92】新規検証_BGスマタゲ10_20260624"}, {"paramCode": "jv_Cac_93", "campaignId": "32486920", "adGroupId": "451538012", "groupName": "【jv_Cac_93】新規検証_BGスマタゲ10_20260624"}, {"paramCode": "jv_Cac_94", "campaignId": "32486920", "adGroupId": "451537987", "groupName": "【jv_Cac_94】新規検証_BGスマタゲ10_20260624"}, {"paramCode": "jv_Cac_95", "campaignId": "32505425", "adGroupId": "451647760", "groupName": "【jv_Cac_95】カード検証_男女CPA好調SQ→CD_サンクス類似10_20260703"}, {"paramCode": "jv_Cac_96", "campaignId": "32505660", "adGroupId": "451649708", "groupName": "【jv_Cac_96】カード検証_男女CPA好調SQ→CD_サンクス類似10_20260703"}, {"paramCode": "jv_Cac_97", "campaignId": "32505689", "adGroupId": "451649845", "groupName": "【jv_Cac_97】カード検証_男女CPA好調SQ→CD_サンクス類似10_20260703"}, {"paramCode": "jv_Cac_98", "campaignId": "32505690", "adGroupId": "451649847", "groupName": "【jv_Cac_98】カード検証_男女CPA好調SQ→CD_サンクス類似10_20260703"}, {"paramCode": "jv_Cac_99", "campaignId": "32505691", "adGroupId": "451649849", "groupName": "【jv_Cac_99】カード検証_男女CPA好調SQ→CD_BGスマ_20260703"}, {"paramCode": "jv_Cac_100", "campaignId": "32505695", "adGroupId": "451649876", "groupName": "【jv_Cac_100】カード検証_男女CPA好調SQ→CD_BGスマ_20260703"}, {"paramCode": "jv_Cac_101", "campaignId": "32505698", "adGroupId": "451649888", "groupName": "【jv_Cac_101】カード検証_男女CPA好調SQ→CD_BGスマ_20260703"}, {"paramCode": "jv_Cac_81", "campaignId": "32460932", "adGroupId": "451650333", "groupName": "【jv_Cac_81】男性_好調まとめ_サンクス類似10_20260703"}, {"paramCode": "jv_Cac_103", "campaignId": "32505842", "adGroupId": "451650396", "groupName": "【jv_Cac_103】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_102", "campaignId": "32505839", "adGroupId": "451650385", "groupName": "【jv_Cac_102】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_110", "campaignId": "32505872", "adGroupId": "451650476", "groupName": "【jv_Cac_110】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_109", "campaignId": "32505871", "adGroupId": "451650474", "groupName": "【jv_Cac_109】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_108", "campaignId": "32505869", "adGroupId": "451650470", "groupName": "【jv_Cac_108】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_107", "campaignId": "32505865", "adGroupId": "451650460", "groupName": "【jv_Cac_107】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_106", "campaignId": "32505863", "adGroupId": "451650456", "groupName": "【jv_Cac_106】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_105", "campaignId": "32505854", "adGroupId": "451650438", "groupName": "【jv_Cac_105】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_104", "campaignId": "32505844", "adGroupId": "451650404", "groupName": "【jv_Cac_104】カード検証_男女CPA好調テキスト×文字商品_BGスマ_20260703"}, {"paramCode": "jv_Cac_111", "campaignId": "32507244", "adGroupId": "451655449", "groupName": "【jv_Cac_111】カード検証_男女CPA好調SQ→CD__BGスマ_20260704"}, {"paramCode": "jv_Cac_112", "campaignId": "32512672", "adGroupId": "451686218", "groupName": "【jv_Cac_112】カード好調まとめ_男女CPA好調CD_LP90日_20260708"}, {"paramCode": "jv_Cac_113", "campaignId": "32512748", "adGroupId": "451686929", "groupName": "【jv_Cac_113】カード好調まとめ_男女CPA好調CD_LP90日_20260708"}, {"paramCode": "jv_Cac_2", "campaignId": "32430712", "adGroupId": "451453894", "groupName": "【jv_Cac_2】入稿用ぶち込み"}, {"paramCode": "jv_Cac_116", "campaignId": "32516630", "adGroupId": "451709964", "groupName": "【jv_Cac_116】テキスト検証_男女好調CD固定_BGスマ_20260710"}, {"paramCode": "jv_Cac_115", "campaignId": "32516625", "adGroupId": "451709954", "groupName": "【jv_Cac_115】テキスト検証_男女好調CD固定_BGスマ_20260710"}, {"paramCode": "jv_Cac_114", "campaignId": "32514816", "adGroupId": "451703205", "groupName": "【jv_Cac_114】テキスト検証_男女好調CD固定_BGスマ_20260710"}, {"paramCode": "jv_Cac_123", "campaignId": "32517824", "adGroupId": "451715117", "groupName": "【jv_Cac_123】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_122", "campaignId": "32517823", "adGroupId": "451715115", "groupName": "【jv_Cac_122】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_121", "campaignId": "32517822", "adGroupId": "451715113", "groupName": "【jv_Cac_121】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_120", "campaignId": "32517821", "adGroupId": "451715111", "groupName": "【jv_Cac_120】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_119", "campaignId": "32517820", "adGroupId": "451715109", "groupName": "【jv_Cac_119】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_118", "campaignId": "32517819", "adGroupId": "451715107", "groupName": "【jv_Cac_118】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_117", "campaignId": "32517808", "adGroupId": "451715075", "groupName": "【jv_Cac_117】テキスト検証_男女好調CD固定_サンクス類似10_20260710"}, {"paramCode": "jv_Cac_141", "campaignId": "32517933", "adGroupId": "451715457", "groupName": "【jv_Cac_141】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_140", "campaignId": "32517932", "adGroupId": "451715455", "groupName": "【jv_Cac_140】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_139", "campaignId": "32517931", "adGroupId": "451715453", "groupName": "【jv_Cac_139】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_138", "campaignId": "32517929", "adGroupId": "451715449", "groupName": "【jv_Cac_138】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_137", "campaignId": "32517928", "adGroupId": "451715447", "groupName": "【jv_Cac_137】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_136", "campaignId": "32517927", "adGroupId": "451715445", "groupName": "【jv_Cac_136】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_135", "campaignId": "32517926", "adGroupId": "451715443", "groupName": "【jv_Cac_135】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_134", "campaignId": "32517925", "adGroupId": "451715437", "groupName": "【jv_Cac_134】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_133", "campaignId": "32517924", "adGroupId": "451715435", "groupName": "【jv_Cac_133】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_132", "campaignId": "32517923", "adGroupId": "451715433", "groupName": "【jv_Cac_132】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_131", "campaignId": "32517922", "adGroupId": "451715431", "groupName": "【jv_Cac_131】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_130", "campaignId": "32517920", "adGroupId": "451715425", "groupName": "【jv_Cac_130】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_129", "campaignId": "32517918", "adGroupId": "451715421", "groupName": "【jv_Cac_129】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_128", "campaignId": "32517917", "adGroupId": "451715419", "groupName": "【jv_Cac_128】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_127", "campaignId": "32517916", "adGroupId": "451715417", "groupName": "【jv_Cac_127】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_126", "campaignId": "32517915", "adGroupId": "451715415", "groupName": "【jv_Cac_126】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_125", "campaignId": "32517913", "adGroupId": "451715411", "groupName": "【jv_Cac_125】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_124", "campaignId": "32517893", "adGroupId": "451715332", "groupName": "【jv_Cac_124】カード検証_男女古風CD_BGスマ_20260710"}, {"paramCode": "jv_Cac_143", "campaignId": "32522613", "adGroupId": "451740726", "groupName": "【jv_Cac_143】文字入りレトロ_男女CD_BGスマ_20260714"}, {"paramCode": "jv_Cac_144", "campaignId": "32522862", "adGroupId": "451742552", "groupName": "【jv_Cac_144】文字入りレトロ_男女CD_BG類似10_20260714"}, {"paramCode": "jv_Cac_145", "campaignId": "32460932", "adGroupId": "451757786", "groupName": "【jv_Cac_145】男性_カード画像検証_サンクス類似10_20260715"}, {"paramCode": "jv_Cac_146", "campaignId": "32460932", "adGroupId": "451759490", "groupName": "【jv_Cac_146】男性_カード画像検証_サンクス類似10_20260715"}, {"paramCode": "jv_Cac_147", "campaignId": "32525231", "adGroupId": "451759756", "groupName": "【jv_Cac_147】ワードショー画像検証_0715"}, {"paramCode": "jv_Cac_148", "campaignId": "32525263", "adGroupId": "451760490", "groupName": "【jv_Cac_148】ワードショー画像検証_男性_BG類似10_0715"}], "sampleAds": [{"format": "Card", "imageNo": "957", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "954", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "956", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "955", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "951", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "948", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "950", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "952", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "953", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}, {"format": "Card", "imageNo": "949", "titleNo": 45, "descNo": 60, "paramCode": "jv_Cac_148", "articleSlug": "jv_C_1"}], "advertiser": "富山常備薬"}</script>
602
+ <script>
603
+ "use strict";
604
+ /* ============================================================
605
+ YDA入稿プランナー — 単一state / シート・ツリー両ビュー投影
606
+ ============================================================ */
607
+ const RAW = JSON.parse(document.getElementById('embedData').textContent);
608
+ const LS_KEY = 'yda_planner_state_v1';
609
+ const HIST_KEY = 'yda_planner_history_v1';
610
+ const AUTHOR_KEY = 'yda_planner_author';
611
+ const SCHEMA_VERSION = 2;
612
+
613
+ /* ---- utils ---- */
614
+ let _uc = 0;
615
+ const uid = p => p + '_' + (Date.now().toString(36)) + (_uc++).toString(36);
616
+ const esc = s => String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
617
+ const glen = s => [...String(s==null?'':s)].length;
618
+ const today = () => { const d=new Date(); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); };
619
+ function toast(m){ const t=document.getElementById('toast'); t.textContent=m; t.classList.add('show'); clearTimeout(t._h); t._h=setTimeout(()=>t.classList.remove('show'),2200); }
620
+
621
+ /* ---- refs (VLOOKUP代替) ---- */
622
+ function buildRefs(){
623
+ const titles={}; RAW.titles.forEach(t=>{ if(String(t.text==null?'':t.text).trim()!=='') titles[t.no]=t.text; }); // 空タイトルは除外
624
+ const groups={}; RAW.groups.forEach(g=>groups[g.paramCode]={campaignId:g.campaignId,adGroupId:g.adGroupId,groupName:g.groupName});
625
+ return {titles, media:RAW.media, groups};
626
+ }
627
+ function defaultProject(){
628
+ return {
629
+ name:'リョウシンJVコンドロイ錠', advertiser: RAW.advertiser||'富山常備薬',
630
+ cta:'もっとみる', adType:'レスポンシブ', mainMediaFormat:'画像',
631
+ deliveryStatus:'オフ', displayUrlFormat:'ドメインのみ表示する',
632
+ attributionDays:1, biddingStrategyDefault:'tCPA', tcpaDefault:8500,
633
+ budgetNew:12000, budgetAsc:50000, baseArticleUrl:'https://iiiyah.com/ab/',
634
+ strategyMemo:'',
635
+ // 入稿設定 (プランに内包。入稿JSON書き出し時にそのまま使う)
636
+ accountId:'', goal:'CONVERSION', startDate:''
637
+ };
638
+ }
639
+ const MODES = {new:'新規', asc:'既存(ASC)', 'asc-add-group':'既存-AG追加', 'asc-add-ad':'既存-広告追加'};
640
+ /* 戦略メモのプレースホルダー(YDA運用戦略テンプレ / Web調査ベース・上書き用の記入例) */
641
+ const STRAT_PLACEHOLDER = [
642
+ '【YDA運用戦略メモ(記入例・上書きしてOK)】',
643
+ '',
644
+ '■ 目的 / KPI',
645
+ ' ・目標CPA 8,500円 / 日予算 新規12,000円・ASC 5〜10万円',
646
+ ' ・フェーズ: 立ち上げ(撒いて選ぶ) → 安定(伸びたものに寄せる)',
647
+ ' ・検索(YSA)で顕在層 × ディスプレイ(YDA)で再訪・認知の両輪',
648
+ '',
649
+ '■ ターゲティング(人 × 面)',
650
+ ' ・サイトリターゲティング(CVR高) + オーディエンスカテゴリから開始',
651
+ ' ・サーチKWターゲティング: 検索で成果の出たKWを流用/検索期間30日/「無料」「とは」除外',
652
+ ' ・類似ユーザー: 拡張レベル1〜3で精度重視 → 伸びたら拡張',
653
+ ' ・デバイス/性別/年齢/曜日時間で最適化(PCとSPは別媒体として検証)',
654
+ ' ・有効リーチ1,000件未満のリストは配信されない点に注意',
655
+ '',
656
+ '■ クリエイティブ検証(1キャンペーン = 1変数)',
657
+ ' ・変える変数は1つだけ(画像 or 訴求 or LP)、他は固定',
658
+ ' ・初期は 1:1:10 で撒く → データを見て好調に配信を寄せる',
659
+ ' ・レスポンシブは多様なアセットを入稿し組合せを自動最適化',
660
+ ' ・厳密比較はA/Bテスト機能(同一グループ内の並列は配信偏りで不正確)',
661
+ '',
662
+ '■ 入札 / 学習',
663
+ ' ・初期はCPCでデータ蓄積 → 条件を満たしたら自動入札へ',
664
+ ' ・tCPA導入目安: 広告グループ 直近30日 CV40件以上(CV最大化はCP 直近7日 20件以上)',
665
+ ' ・tCPAは直近7日の実績CPA基準/変更は±20%以内/頻繁な変更は禁止',
666
+ ' ・「学習中」表示中は設定を触らない(最低2週間・CPAは目標の1.5〜2倍に膨らむ)',
667
+ '',
668
+ '■ 評価 / 改善サイクル',
669
+ ' ・CTR / CPC / CVR に分解し、デバイス・配信面別に確認',
670
+ ' ・除外リスト整備/フリークエンシー調整/記事LP改善',
671
+ ' ・学習期間(1〜2週)は触らず、本判断は最低1ヶ月',
672
+ '',
673
+ '■ この検証の仮説・所感(後乗せ)',
674
+ ' ・'
675
+ ].join('\n');
676
+
677
+ /* ---- initial state ---- */
678
+ function newAd(seed){ return Object.assign({uid:uid('a'), format:'Card', imageNo:'', titleNo:'', descNo:'', articleSlug:'', isBanner:false, adIdOverride:'', desc2:''}, seed||{}); }
679
+ /* ---- schema v2: バージョンメタ ---- */
680
+ function defaultMeta(){ return {versionId:null, name:'', memo:'', author:'', platform:'yda', createdAt:null, updatedAt:null}; }
681
+ /* v1(メタなし) → v2 へのマイグレーション。v2以降はそのまま既定値マージ。
682
+ 旧v2の st.submit (2026-07-23の一時実装) は project の入稿設定に吸収する */
683
+ function migrateState(j){
684
+ const project=Object.assign({}, j.project);
685
+ if(j.submit){
686
+ if(j.submit.accountId && !project.accountId) project.accountId=j.submit.accountId;
687
+ if(j.submit.goal && !project.goal) project.goal=j.submit.goal;
688
+ if(j.submit.startDate && !project.startDate) project.startDate=j.submit.startDate;
689
+ }
690
+ return {
691
+ schemaVersion:SCHEMA_VERSION,
692
+ meta:Object.assign(defaultMeta(), j.meta||{}),
693
+ project, campaigns:j.campaigns, ui:j.ui, refs:j.refs
694
+ };
695
+ }
696
+ function initialState(){
697
+ const refs=buildRefs(), project=defaultProject();
698
+ const byParam={}; RAW.sampleAds.forEach(a=>{(byParam[a.paramCode]=byParam[a.paramCode]||[]).push(a);});
699
+ const campaigns=[];
700
+ Object.keys(byParam).forEach((param,i)=>{
701
+ const ads=byParam[param].map(a=>newAd({format:a.format,imageNo:String(a.imageNo),titleNo:a.titleNo,descNo:a.descNo,articleSlug:a.articleSlug}));
702
+ const g=refs.groups[param]; const nm=g?g.groupName.replace(/^【[^】]*】/,''):param;
703
+ const grp={uid:uid('g'), name:'AG1', paramCode:param, ads};
704
+ campaigns.push({uid:uid('c'), name:nm||param, mode:'asc', variable:'ワードショー画像検証(サンプル)', urlNumber:'', budget:null, biddingStrategy:'', tcpa:null, status:'オフ', strategyMemo:'', adGroups:[grp]});
705
+ });
706
+ return {schemaVersion:SCHEMA_VERSION, meta:defaultMeta(), project, refs, campaigns, ui:{expanded:{}}};
707
+ }
708
+
709
+ /* ---- persistence ---- */
710
+ function stateJson(){ const {meta,project,campaigns,ui,refs}=st; return {schemaVersion:SCHEMA_VERSION, meta, project, campaigns, ui, refs}; }
711
+ function persist(){ try{ localStorage.setItem(LS_KEY, JSON.stringify(stateJson())); }catch(e){} }
712
+ function load(){ try{ const j=JSON.parse(localStorage.getItem(LS_KEY)); if(!j||!j.campaigns) return null; const m=migrateState(j); const refs=(m.refs&&m.refs.titles&&m.refs.media&&m.refs.groups)?m.refs:buildRefs(); return {schemaVersion:SCHEMA_VERSION, meta:m.meta, project:Object.assign(defaultProject(),m.project), refs, campaigns:m.campaigns, ui:m.ui||{expanded:{}}}; }catch(e){ return null; } }
713
+
714
+ let st = load() || initialState();
715
+
716
+ /* ---- undo (bounded) ---- */
717
+ const undoStack=[];
718
+ function snapshot(){ undoStack.push(JSON.stringify({project:st.project,campaigns:st.campaigns,refs:st.refs})); if(undoStack.length>40) undoStack.shift(); }
719
+ function mutate(fn){ snapshot(); fn(); persist(); render(); }
720
+ function undo(){ const s=undoStack.pop(); if(!s){toast('これ以上戻せません'); return;} const j=JSON.parse(s); st.project=j.project; st.campaigns=j.campaigns; if(j.refs) st.refs=j.refs; persist(); if(typeof buildDatalists==='function') buildDatalists(); render(); toast('元に戻しました'); }
721
+
722
+ /* ---- lookups ---- */
723
+ function eachAd(cb){ st.campaigns.forEach(c=>c.adGroups.forEach(g=>g.ads.forEach(a=>cb(a,g,c)))); }
724
+ function findAd(u){ let r=null; st.campaigns.forEach(c=>c.adGroups.forEach(g=>g.ads.forEach((a,i)=>{ if(a.uid===u) r={a,g,c,i}; }))); return r; }
725
+ function findGroup(u){ let r=null; st.campaigns.forEach(c=>c.adGroups.forEach((g,i)=>{ if(g.uid===u) r={g,c,i}; })); return r; }
726
+ function findCp(u){ const i=st.campaigns.findIndex(c=>c.uid===u); return i<0?null:{c:st.campaigns[i],i}; }
727
+
728
+ /* ---- resolve (派生値 / 純関数) ---- */
729
+ function resolveAd(ad, group){
730
+ const p=st.project, refs=st.refs;
731
+ const sizeKey = ad.format==='SQ'?'sq':'card';
732
+ const crName = String(ad.imageNo||'') + (ad.format==='SQ'?'s':'c');
733
+ const adName = crName + '-' + (ad.titleNo??'') + 'L' + (ad.descNo??'') + '-' + (group.paramCode||'');
734
+ const g = refs.groups[group.paramCode];
735
+ const me = refs.media[String(ad.imageNo)];
736
+ const mediaId = me ? me[sizeKey] : undefined;
737
+ const titleText = refs.titles[ad.titleNo];
738
+ const descText = refs.titles[ad.descNo];
739
+ const finalUrl = (p.baseArticleUrl||'') + (ad.articleSlug||'') + '?ad=' + (group.paramCode||'') + '&creative=' + (group.paramCode||'') + '-' + adName;
740
+ const errs=[], warns=[];
741
+ if(!ad.isBanner){
742
+ if(String(ad.imageNo||'')==='' || mediaId===undefined) errs.push('画像未解決');
743
+ if(String(ad.titleNo||'')==='' || titleText===undefined) errs.push('タイトル未解決');
744
+ if(String(ad.descNo||'')==='' || descText===undefined) errs.push('説明文未解決');
745
+ if(titleText!==undefined && glen(titleText)>35) errs.push('タイトル35字超');
746
+ else if(titleText!==undefined && glen(titleText)>20) warns.push('タイトル20字超');
747
+ if(!g) warns.push('新規/未払出');
748
+ }
749
+ return {crName, adName, campaignId:g?g.campaignId:'', adGroupId:g?g.adGroupId:'', mediaId:mediaId||'', titleText:titleText===undefined?'':titleText, descText:descText===undefined?'':descText, finalUrl, errs, warns, resolvedGroup:!!g};
750
+ }
751
+ function validationCounts(){ let err=0,warn=0,banner=0,ads=0; eachAd((a,g)=>{ ads++; if(a.isBanner){banner++; return;} const r=resolveAd(a,g); if(r.errs.length) err++; else if(r.warns.length) warn++; }); return {err,warn,banner,ads}; }
752
+
753
+ /* ============================================================
754
+ RENDER
755
+ ============================================================ */
756
+ let activeView='sheet';
757
+ let planMode='sheet'; // プランビュー内の表示モード: sheet / tree
758
+ let sheetTab='ads'; // シート内サブタブ: ads / groups / campaigns
759
+ function render(){
760
+ document.getElementById('pjLabel').textContent = st.project.name + ' / 主体者:' + st.project.advertiser;
761
+ const si=document.getElementById('stratInput'); if(si && document.activeElement!==si) si.value=st.project.strategyMemo||'';
762
+ renderTplChips(); renderValidationBadge();
763
+ if(activeView==='sheet'){
764
+ document.getElementById('sheetArea').hidden = planMode!=='sheet';
765
+ document.getElementById('treeArea').hidden = planMode!=='tree';
766
+ if(planMode==='sheet') renderSheet(); else renderTree();
767
+ }
768
+ else if(activeView==='ref') renderRef();
769
+ else if(activeView==='hist') renderHist();
770
+ const c=validationCounts();
771
+ document.getElementById('cntCp').textContent=st.campaigns.length;
772
+ document.getElementById('cntAg').textContent=st.campaigns.reduce((n,c)=>n+c.adGroups.length,0);
773
+ document.getElementById('cntAd').textContent=c.ads;
774
+ }
775
+ function renderValidationBadge(){
776
+ const c=validationCounts(); const b=document.getElementById('vbadge');
777
+ b.className='vbadge '+(c.err?'err':(c.warn?'warn':'ok'));
778
+ b.textContent = c.err? ('⚠ エラー'+c.err+'件') : (c.warn? ('△ 警告'+c.warn+'件') : ('✓ '+c.ads+'件OK'));
779
+ b.title = 'エラー'+c.err+' / 警告'+c.warn+' / 手入稿'+c.banner+' / 広告'+c.ads;
780
+ }
781
+ function renderTplChips(){
782
+ const p=st.project;
783
+ const txt=(f,v,w)=>'<input class="chipin" data-proj="'+f+'" value="'+esc(v)+'" style="width:'+(w||'8ch')+'">';
784
+ const num=(f,v,w)=>'<input class="chipin" type="number" data-proj="'+f+'" value="'+esc(v)+'" style="width:'+(w||'6ch')+'">';
785
+ const sel=(f,v,opts)=>'<select class="chipin" data-proj="'+f+'">'+opts.map(o=>'<option'+(o===v?' selected':'')+'>'+esc(o)+'</option>').join('')+'</select>';
786
+ const selv=(f,v,opts)=>'<select class="chipin" data-proj="'+f+'">'+opts.map(o=>'<option value="'+esc(o.v)+'"'+(o.v===v?' selected':'')+'>'+esc(o.t)+'</option>').join('')+'</select>';
787
+ const chip=(label,ctrl)=>'<span class="chip">'+esc(label)+' '+ctrl+'</span>';
788
+ document.getElementById('tplChips').innerHTML =
789
+ chip('案件', txt('name',p.name,'13ch')) +
790
+ chip('アカウントID', txt('accountId',p.accountId||'','11ch')) +
791
+ chip('目的', selv('goal',p.goal||'CONVERSION',[{v:'CONVERSION',t:'コンバージョン'},{v:'CLICK',t:'クリック'},{v:'BRAND_AWARENESS',t:'ブランド認知'},{v:'VIDEO_VIEW',t:'動画再生'},{v:'VIEWABLE_IMPRESSION',t:'vインプ'}])) +
792
+ chip('開始日', txt('startDate',p.startDate||'','10ch')) +
793
+ chip('CTA', txt('cta',p.cta,'7ch')) +
794
+ chip('配信', sel('deliveryStatus',p.deliveryStatus,['オフ','オン'])) +
795
+ chip('タイプ', txt('adType',p.adType,'8ch')+'/'+txt('mainMediaFormat',p.mainMediaFormat,'4ch')) +
796
+ chip('入札', sel('biddingStrategyDefault',p.biddingStrategyDefault,['tCPA','最大配信'])+' '+num('tcpaDefault',p.tcpaDefault,'6ch')+'円') +
797
+ chip('計測', num('attributionDays',p.attributionDays,'3ch')+'日') +
798
+ chip('主体者', txt('advertiser',p.advertiser,'9ch')) +
799
+ chip('表示URL', txt('displayUrlFormat',p.displayUrlFormat,'13ch')) +
800
+ chip('記事URL', txt('baseArticleUrl',p.baseArticleUrl,'16ch')) +
801
+ chip('予算新規', num('budgetNew',p.budgetNew,'7ch')) +
802
+ chip('予算ASC', num('budgetAsc',p.budgetAsc,'7ch'));
803
+ }
804
+
805
+ /* ---- SHEET ---- */
806
+ function renderSheet(){
807
+ document.querySelectorAll('#sheetTabs button').forEach(b=>b.classList.toggle('active', b.dataset.st===sheetTab));
808
+ if(sheetTab==='groups') return renderSheetGroups();
809
+ if(sheetTab==='campaigns') return renderSheetCampaigns();
810
+ const filter=document.getElementById('sheetFilter').value;
811
+ const cols=['','パス','形式','画像番号','タイトル番号','説明文番号','paramCode(グループ)','記事slug','広告名(自動)','CP / AG ID','状態'];
812
+ let html='<table class="sheet"><thead><tr>'+cols.map(c=>'<th>'+esc(c)+'</th>').join('')+'<th></th></tr></thead><tbody>';
813
+ let shown=0, ri=-1;
814
+ st.campaigns.forEach(c=>{
815
+ const ratio = c.adGroups.length+':'+(c.adGroups.map(g=>g.ads.length).join('/')||'0');
816
+ html+='<tr class="cphead"><td colspan="12"><div class="cptitle"><span class="ratio">'+esc(c.adGroups.length===1?('1:1:'+ (c.adGroups[0].ads.length)) : ratio)+'</span> <b>'+esc(c.name)+'</b> <span class="pill '+(c.mode==='new'?'':'green')+'">'+esc(MODES[c.mode]||c.mode)+'</span>'+(c.variable?'<span class="pill purple">変数:'+esc(c.variable)+'</span>':'')+'</div></td></tr>';
817
+ c.adGroups.forEach(g=>{
818
+ g.ads.forEach(a=>{
819
+ const r=resolveAd(a,g);
820
+ const hasErr=r.errs.length>0;
821
+ if(filter==='err' && !hasErr) return;
822
+ if(filter==='banner' && !a.isBanner) return;
823
+ shown++; ri++;
824
+ const cls='adrow'+(hasErr?' hasErr':'')+(a.isBanner?' banner':'');
825
+ html+='<tr class="'+cls+'" data-adrow="'+a.uid+'">';
826
+ html+='<td class="rownum" data-rownum="'+ri+'" title="行を選択('+esc(g.name)+')">'+(ri+1)+'</td>';
827
+ html+='<td><span class="breadc">'+esc(g.name)+'</span></td>';
828
+ html+='<td><select class="cell-in" data-sel="1" data-r="'+ri+'" data-c="0" data-u="'+a.uid+'" data-f="format"><option'+(a.format==='Card'?' selected':'')+'>Card</option><option'+(a.format==='SQ'?' selected':'')+'>SQ</option></select></td>';
829
+ // 画像番号 : メディアID(1ボックス + ▼一覧)
830
+ html+='<td><div class="cellcombo'+(!a.isBanner&&r.errs.includes('画像未解決')?' err':'')+'"><input class="cell-in num" data-sel="1" data-r="'+ri+'" data-c="1" data-u="'+a.uid+'" data-f="imageNo" value="'+esc(a.imageNo)+'"><span class="combosep">:</span><span class="ghost" data-d="'+a.uid+'-media" title="メディアID '+esc(r.mediaId)+'">'+mediaInline(a,r)+'</span><button class="combodd" data-dd="media" data-u="'+a.uid+'" data-f="imageNo" tabindex="-1" title="登録画像から選択">▾</button></div></td>';
831
+ // タイトル番号 : 文言(1ボックス + 字数 + ▼一覧)
832
+ html+='<td><div class="cellcombo'+(!a.isBanner&&(r.errs.includes('タイトル未解決')||r.errs.includes('タイトル35字超'))?' err':'')+'"><input class="cell-in num" data-sel="1" data-r="'+ri+'" data-c="2" data-u="'+a.uid+'" data-f="titleNo" value="'+esc(a.titleNo)+'"><span class="combosep">:</span><span class="ghost" data-d="'+a.uid+'-title" title="'+esc(r.titleText)+'">'+titleGhost(r)+'</span><button class="combodd" data-dd="titles" data-u="'+a.uid+'" data-f="titleNo" tabindex="-1" title="タイトル一覧から選択">▾</button></div></td>';
833
+ // 説明文番号 : 文言(1ボックス + ▼一覧)
834
+ html+='<td><div class="cellcombo'+(!a.isBanner&&r.errs.includes('説明文未解決')?' err':'')+'"><input class="cell-in num" data-sel="1" data-r="'+ri+'" data-c="3" data-u="'+a.uid+'" data-f="descNo" value="'+esc(a.descNo)+'"><span class="combosep">:</span><span class="ghost" data-d="'+a.uid+'-desc" title="'+esc(r.descText)+'">'+descGhost(r)+'</span><button class="combodd" data-dd="titles" data-u="'+a.uid+'" data-f="descNo" tabindex="-1" title="説明文一覧から選択">▾</button></div></td>';
835
+ // paramCode (group-level)
836
+ html+='<td><input class="cell-in mono" data-sel="1" data-r="'+ri+'" data-c="4" data-u="'+a.uid+'" data-f="paramCode" value="'+esc(g.paramCode)+'" title="グループ共通(変更で同グループ全広告に反映)" style="width:120px"></td>';
837
+ // slug
838
+ html+='<td><input class="cell-in mono" data-sel="1" data-r="'+ri+'" data-c="5" data-u="'+a.uid+'" data-f="articleSlug" value="'+esc(a.articleSlug)+'" style="width:90px"></td>';
839
+ // adName
840
+ html+='<td><span class="idpill" data-d="'+a.uid+'-adname" title="'+esc(r.finalUrl)+'">'+esc(r.adName)+'</span></td>';
841
+ // ids
842
+ html+='<td><span class="idpill '+(a.isBanner?'':(r.resolvedGroup?'':'warn'))+'" data-d="'+a.uid+'-ids">'+idsCell(r)+'</span></td>';
843
+ // banner toggle
844
+ html+='<td><label class="minick"><input type="checkbox" data-u="'+a.uid+'" data-f="isBanner"'+(a.isBanner?' checked':'')+'>手入稿</label></td>';
845
+ // actions
846
+ html+='<td><span class="rowact"><button class="iconbtn" data-act="ad-dup" data-u="'+a.uid+'" title="複製">⧉</button><button class="iconbtn danger" data-act="ad-del" data-u="'+a.uid+'" title="削除">✕</button></span></td>';
847
+ html+='</tr>';
848
+ });
849
+ // add-ad row
850
+ html+='<tr><td></td><td colspan="11"><button class="addbtn" data-act="ag-addad" data-u="'+g.uid+'">+ '+esc(g.name)+' に広告を追加</button></td></tr>';
851
+ });
852
+ });
853
+ html+='</tbody></table>';
854
+ if(shown===0 && st.campaigns.length===0) html='<div class="empty">キャンペーンがありません。シートの「キャンペーン」タブ →「+ キャンペーンを追加」から。</div>';
855
+ const host=document.getElementById('sheetHost');
856
+ const focus=document.activeElement, fu=focus&&focus.dataset?focus.dataset.u:null, ff=focus&&focus.dataset?focus.dataset.f:null, selS=focus&&focus.selectionStart;
857
+ if(focus && host.contains(focus)){ try{ focus.blur(); }catch(e){} }
858
+ host.innerHTML=html;
859
+ if(fu&&ff){ const el=host.querySelector('[data-u="'+fu+'"][data-f="'+ff+'"]'); if(el){ el.focus(); try{ if(selS!=null&&el.setSelectionRange) el.setSelectionRange(selS,selS); }catch(e){} } }
860
+ applySelection();
861
+ }
862
+ function mediaInline(a,r){ if(a.isBanner) return '<span class="idpill">—</span>'; if(!a.imageNo) return '<span class="idpill" style="color:var(--faint)">—</span>'; const ok=r.mediaId!==''; return '<span class="idpill '+(ok?'':'err')+'">'+(ok?('ID '+esc(r.mediaId)):'#N/A')+'</span>'; }
863
+ function titleGhost(r){ if(r.errs.includes('タイトル未解決')) return '<span class="err">未解決</span>'; return esc(r.titleText.slice(0,40)); }
864
+ function descGhost(r){ if(r.errs.includes('説明文未解決')) return '<span class="err">— 未解決</span>'; return esc(r.descText.slice(0,44)); }
865
+ function idsCell(r){ if(!r.resolvedGroup) return '新規/未払出'; return r.campaignId+' / '+r.adGroupId; }
866
+
867
+ /* ---- TREE ---- */
868
+ function isCollapsed(u){ return !!st.ui.expanded[u]===false ? false : st.ui.expanded[u]==='c'; }
869
+ function focusRestoreG(host, attrU, attrF){
870
+ const fa=document.activeElement, fu=fa&&fa.dataset?fa.dataset[attrU]:null, ff=fa&&fa.dataset?fa.dataset[attrF]:null, ss=fa&&fa.selectionStart;
871
+ return function(){ if(fu&&ff){ const el=host.querySelector('['+'data-'+(attrU==='gu'?'gu':'cu')+'="'+fu+'"]['+'data-'+(attrU==='gu'?'gf':'cf')+'="'+ff+'"]'); if(el){ el.focus(); try{ if(ss!=null&&el.setSelectionRange) el.setSelectionRange(ss,ss);}catch(_){}}}};
872
+ }
873
+ /* ---- シート: 広告グループ タブ(変更列を一覧編集) ---- */
874
+ function renderSheetGroups(){
875
+ const host=document.getElementById('sheetHost'); const restore=focusRestoreG(host,'gu','gf');
876
+ const cols=['#','キャンペーン','広告グループ名','paramCode','広告グループID','入札','配信','デバイス','性別','年齢','地域','曜日時間','オーディエンス','サーチKW',''];
877
+ let html='<table class="sheet"><thead><tr>'+cols.map(c=>'<th>'+esc(c)+'</th>').join('')+'</tr></thead><tbody>'; let ri=-1;
878
+ const gin=(g,ci,w,req)=>{ const f=VARF_GROUPS[ci]; const v=g[f]; const empty=req&&(v==null||v===''); return '<td><input class="cell-in'+(empty?' warnempty':'')+'" data-sel="1" data-r="'+ri+'" data-c="'+ci+'" data-gu="'+g.uid+'" data-gf="'+f+'" value="'+esc(v==null?'':v)+'" style="width:'+w+'"></td>'; };
879
+ st.campaigns.forEach(c=>{
880
+ c.adGroups.forEach(g=>{ ri++;
881
+ const gr=st.refs.groups[g.paramCode]; const paramErr=g.paramCode&&!gr;
882
+ html+='<tr class="adrow" data-gu="'+g.uid+'">';
883
+ html+='<td class="rownum" data-rownum="'+ri+'">'+(ri+1)+'</td>';
884
+ html+='<td><span class="breadc" title="'+esc(c.name)+'">'+esc(c.name)+'</span></td>';
885
+ html+=gin(g,0,'120px',true);
886
+ html+='<td><input class="cell-in mono'+(!g.paramCode?' warnempty':(paramErr?' err':''))+'" data-sel="1" data-r="'+ri+'" data-c="1" data-gu="'+g.uid+'" data-gf="paramCode" value="'+esc(g.paramCode||'')+'" style="width:110px"><span class="ghost">'+(gr?('ID '+esc(gr.adGroupId)):(g.paramCode?'<span class="err">未払出</span>':'<span style="color:var(--faint)">未記入</span>'))+'</span></td>';
887
+ html+='<td><input class="cell-in mono" data-sel="1" data-r="'+ri+'" data-c="2" data-gu="'+g.uid+'" data-gf="adGroupId" value="'+esc(g.adGroupId||'')+'" placeholder="'+(gr?esc(gr.adGroupId):'')+'" style="width:90px"></td>';
888
+ html+=gin(g,3,'56px',false);
889
+ html+='<td><select class="cell-in" data-sel="1" data-r="'+ri+'" data-c="4" data-gu="'+g.uid+'" data-gf="deliveryStatus"><option value=""'+(!g.deliveryStatus?' selected':'')+'>(既定)</option><option'+(g.deliveryStatus==='オフ'?' selected':'')+'>オフ</option><option'+(g.deliveryStatus==='オン'?' selected':'')+'>オン</option></select></td>';
890
+ [5,6,7,8,9,10,11].forEach(ci=>{ const tf=VARF_GROUPS[ci]; if(TG_OPTIONS[tf]){ const val=g[tf]||''; const cnt=val?val.split(',').filter(s=>s.trim()).length:0; const disp = val? '<span class="tgval">'+esc(val)+'</span>' : '<span class="tgval"><span class="tghint">'+esc(TG_OPTIONS[tf].label)+'</span></span>'; html+='<td class="tgtd"><div class="tgpick'+(val?'':' tgempty')+'" data-sel="1" data-r="'+ri+'" data-c="'+ci+'" data-gu="'+g.uid+'" data-gf="'+tf+'" title="'+esc(val||(TG_OPTIONS[tf].label+'(クリックで選択)'))+'">'+disp+'<span class="tgcaret">▾</span></div></td>'; } else { html+=gin(g,ci,'120px',false); } });
891
+ html+='<td><span class="rowact"><button class="iconbtn" data-act="ag-dup" data-u="'+g.uid+'" title="複製">⧉</button><button class="iconbtn danger" data-act="ag-del" data-u="'+g.uid+'" title="削除">✕</button></span></td>';
892
+ html+='</tr>';
893
+ });
894
+ html+='<tr><td></td><td></td><td colspan="13"><button class="addbtn" data-act="cp-addag" data-u="'+c.uid+'">+ '+esc(c.name)+' に広告グループを追加</button></td></tr>';
895
+ });
896
+ html+='</tbody></table>'; if(!st.campaigns.length) html='<div class="empty">キャンペーンがありません。</div>';
897
+ host.innerHTML=html; restore(); applySelection();
898
+ }
899
+ /* ---- シート: キャンペーン タブ(変更列を一覧編集) ---- */
900
+ function renderSheetCampaigns(){
901
+ const host=document.getElementById('sheetHost'); const restore=focusRestoreG(host,'cu','cf');
902
+ const cols=['#','キャンペーン名','キャンペーンID','モード','予算/日','入札戦略','tCPA','配信','検証変数','リンク番号','戦略メモ',''];
903
+ let html='<table class="sheet"><thead><tr>'+cols.map(c=>'<th>'+esc(c)+'</th>').join('')+'</tr></thead><tbody>';
904
+ let RIDX=-1;
905
+ st.campaigns.forEach((c)=>{ RIDX++;
906
+ const cpId = c.campaignId || resolveGroupIds(firstParamOf(c)).campaignId;
907
+ const a=(f)=>'data-sel="1" data-r="'+RIDX+'" data-c="'+VARF_CAMPAIGNS.indexOf(f)+'" data-cu="'+c.uid+'" data-cf="'+f+'"';
908
+ html+='<tr class="adrow" data-cu="'+c.uid+'">';
909
+ html+='<td class="rownum" data-rownum="'+RIDX+'">'+(RIDX+1)+'</td>';
910
+ html+='<td><input class="cell-in'+(!c.name?' warnempty':'')+'" '+a('name')+' value="'+esc(c.name||'')+'" style="width:170px"></td>';
911
+ html+='<td><input class="cell-in mono" '+a('campaignId')+' value="'+esc(c.campaignId||'')+'" placeholder="'+esc(cpId)+'" style="width:90px"></td>';
912
+ html+='<td><select class="cell-in" '+a('mode')+'>'+Object.keys(MODES).map(k=>'<option value="'+k+'"'+(c.mode===k?' selected':'')+'>'+esc(MODES[k])+'</option>').join('')+'</select></td>';
913
+ html+='<td><input class="cell-in num" type="number" '+a('budget')+' value="'+esc(c.budget??'')+'" placeholder="'+(c.mode==='new'?st.project.budgetNew:st.project.budgetAsc)+'" style="width:78px"></td>';
914
+ html+='<td><select class="cell-in" '+a('biddingStrategy')+'><option value="">(既定)</option><option'+(c.biddingStrategy==='tCPA'?' selected':'')+'>tCPA</option><option'+(c.biddingStrategy==='最大配信'?' selected':'')+'>最大配信</option></select></td>';
915
+ html+='<td><input class="cell-in num" type="number" '+a('tcpa')+' value="'+esc(c.tcpa??'')+'" placeholder="'+st.project.tcpaDefault+'" style="width:70px"></td>';
916
+ html+='<td><select class="cell-in" '+a('status')+'><option'+(c.status==='オフ'?' selected':'')+'>オフ</option><option'+(c.status==='オン'?' selected':'')+'>オン</option></select></td>';
917
+ html+='<td><input class="cell-in" '+a('variable')+' value="'+esc(c.variable||'')+'" style="width:140px"></td>';
918
+ html+='<td><input class="cell-in mono" '+a('urlNumber')+' value="'+esc(c.urlNumber||'')+'" style="width:54px"></td>';
919
+ html+='<td><input class="cell-in" '+a('strategyMemo')+' value="'+esc(c.strategyMemo||'')+'" style="width:160px"></td>';
920
+ html+='<td><span class="rowact"><button class="iconbtn" data-act="cp-dup" data-u="'+c.uid+'" title="複製/量産">⧉</button><button class="iconbtn danger" data-act="cp-del" data-u="'+c.uid+'" title="削除">✕</button></span></td>';
921
+ html+='</tr>';
922
+ });
923
+ html+='<tr><td></td><td colspan="11"><button class="addbtn" data-act="add-campaign">+ キャンペーンを追加</button></td></tr>';
924
+ html+='</tbody></table>'; if(!st.campaigns.length) html='<div class="empty">キャンペーンがありません。「+ キャンペーンを追加」から。</div>';
925
+ host.innerHTML=html; restore(); applySelection();
926
+ }
927
+ function renderTree(){
928
+ if(st.campaigns.length===0){ document.getElementById('treeHost').innerHTML='<div class="empty">キャンペーンがありません。シートの「キャンペーン」タブ →「+ キャンペーンを追加」から。</div>'; return; }
929
+ let html='';
930
+ st.campaigns.forEach(c=>{
931
+ const col = st.ui.expanded[c.uid]==='c';
932
+ const totalAds=c.adGroups.reduce((n,g)=>n+g.ads.length,0);
933
+ const ratio = c.adGroups.length===1 ? ('1:1:'+ (c.adGroups[0].ads.length)) : (c.adGroups.length+':'+c.adGroups.map(g=>g.ads.length).join('/'));
934
+ const cerr = c.adGroups.some(g=>g.ads.some(a=>!a.isBanner&&resolveAd(a,g).errs.length));
935
+ html+='<div class="cpnode'+(col?' collapsed':'')+'" data-cp="'+c.uid+'">';
936
+ html+='<div class="noderow"><button class="caret" data-act="toggle" data-u="'+c.uid+'"><span class="a"></span></button>';
937
+ html+='<span class="nodename" contenteditable="true" data-rename="cp" data-u="'+c.uid+'">'+esc(c.name)+'</span>';
938
+ html+='<span class="metae"><span class="ratio">'+esc(ratio)+'</span><span class="pill '+(c.mode==='new'?'':'green')+'">'+esc(MODES[c.mode]||c.mode)+'</span>';
939
+ html+='<span class="chip">予算 <b>'+esc((c.budget??(c.mode==='new'?st.project.budgetNew:st.project.budgetAsc)).toLocaleString())+'</b>円</span>';
940
+ html+='<span class="chip">'+esc(c.biddingStrategy||st.project.biddingStrategyDefault)+' <b>'+esc((c.tcpa??st.project.tcpaDefault))+'</b></span>';
941
+ if(c.variable) html+='<span class="pill purple">変数:'+esc(c.variable)+'</span>';
942
+ if(cerr) html+='<span class="pill danger">要修正</span>';
943
+ html+='</span>';
944
+ html+='<span class="nodeact"><button class="iconbtn" data-act="cp-edit" data-u="'+c.uid+'" title="設定">⚙</button><button class="iconbtn" data-act="cp-dup" data-u="'+c.uid+'" title="複製/量産">⧉</button><button class="iconbtn danger" data-act="cp-del" data-u="'+c.uid+'" title="削除">✕</button></span>';
945
+ html+='</div>';
946
+ html+='<div class="agwrap">';
947
+ c.adGroups.forEach(g=>{
948
+ const gcol = st.ui.expanded[g.uid]==='c';
949
+ html+='<div class="agnode'+(gcol?' collapsed':'')+'" data-ag="'+g.uid+'">';
950
+ html+='<div class="noderow"><button class="caret" data-act="toggle" data-u="'+g.uid+'"><span class="a"></span></button>';
951
+ html+='<span class="nodename" contenteditable="true" data-rename="ag" data-u="'+g.uid+'">'+esc(g.name)+'</span>';
952
+ html+='<span class="metae"><span class="pill mono">【'+esc(g.paramCode||'—')+'】</span><span class="chip">広告 <b>'+g.ads.length+'</b></span>';
953
+ const gr=st.refs.groups[g.paramCode]; html+= gr? ('<span class="idpill">'+esc(gr.campaignId)+' / '+esc(gr.adGroupId)+'</span>') : '<span class="idpill warn">新規/未払出</span>';
954
+ html+='</span>';
955
+ html+='<span class="nodeact"><button class="iconbtn" data-act="ag-param" data-u="'+g.uid+'" title="paramCode編集">#</button><button class="iconbtn" data-act="ag-dup" data-u="'+g.uid+'" title="複製">⧉</button><button class="iconbtn danger" data-act="ag-del" data-u="'+g.uid+'" title="削除">✕</button></span>';
956
+ html+='</div><div class="adwrap">';
957
+ g.ads.forEach(a=>{
958
+ const r=resolveAd(a,g); const dot=a.isBanner?'warn':(r.errs.length?'err':(r.warns.length?'warn':'ok'));
959
+ html+='<div class="adnode'+(r.errs.length?' hasErr':'')+(a.isBanner?' banner':'')+'" data-ad="'+a.uid+'">';
960
+ html+='<span class="dot '+dot+'"></span>';
961
+ html+='<span class="cbadge">'+esc(a.imageNo||'—')+a.format[0].toLowerCase()+'</span>';
962
+ html+='<span class="adttl">'+(r.errs.includes('タイトル未解決')?'<span style="color:var(--danger)">タイトル未解決</span>':esc(r.titleText||'(未入力)'))+' <span class="sm">'+esc(a.titleNo)+'L'+esc(a.descNo)+'</span></span>';
963
+ if(a.isBanner) html+='<span class="pill">手入稿</span>';
964
+ html+='<span class="nodeact"><button class="iconbtn" data-act="ad-dup" data-u="'+a.uid+'" title="複製">⧉</button><button class="iconbtn danger" data-act="ad-del" data-u="'+a.uid+'" title="削除">✕</button></span>';
965
+ html+='</div>';
966
+ });
967
+ html+='<button class="addbtn" data-act="ag-addad" data-u="'+g.uid+'">+ 広告を追加</button>';
968
+ html+='<button class="addbtn" data-act="ag-addn" data-u="'+g.uid+'" style="margin-left:6px">+ N本まとめて</button>';
969
+ html+='</div></div>';
970
+ });
971
+ html+='<button class="addbtn" data-act="cp-addag" data-u="'+c.uid+'">+ 広告グループを追加</button>';
972
+ html+='</div></div>';
973
+ });
974
+ document.getElementById('treeHost').innerHTML=html;
975
+ }
976
+
977
+ /* ============================================================
978
+ REFERENCE DATA (タイトル/説明一覧・登録画像一覧) — 閲覧+登録
979
+ ============================================================ */
980
+ let refTab='titles';
981
+ const newRefIds={titles:new Set(), media:new Set()}; // 新規追加に一時的な new ラベル(リロードで消える)
982
+ function newBadge(kind,no){ return newRefIds[kind].has(String(no)) ? '<span class="newbadge">new</span>' : ''; }
983
+ function buildDatalists(){
984
+ const host=document.getElementById('dlHost'); if(!host) return;
985
+ const tks=Object.keys(st.refs.titles).sort((a,b)=>a-b);
986
+ const mks=Object.keys(st.refs.media).sort((a,b)=>a-b);
987
+ const t=tks.map(no=>'<option value="'+esc(no)+'">'+esc(st.refs.titles[no])+'</option>').join('');
988
+ const m=mks.map(no=>{ const e=st.refs.media[no]; return '<option value="'+esc(no)+'">'+esc((e.card?'Card':'')+(e.card&&e.sq?'/':'')+(e.sq?'SQ':''))+'</option>'; }).join('');
989
+ host.innerHTML='<datalist id="dl-titles">'+t+'</datalist><datalist id="dl-media">'+m+'</datalist>';
990
+ }
991
+ function renderRef(){
992
+ const q=(document.getElementById('refSearch').value||'').trim().toLowerCase();
993
+ const host=document.getElementById('refHost'); let html='';
994
+ if(refTab==='titles'){
995
+ const all=Object.keys(st.refs.titles).map(no=>({no:+no,text:st.refs.titles[no]})).filter(e=>String(e.text||'').trim()!=='');
996
+ const f=all.filter(e=>!q||String(e.no).includes(q)||String(e.text).toLowerCase().includes(q)).sort((a,b)=>a.no-b.no);
997
+ document.getElementById('refCount').textContent='タイトル/説明 '+f.length+'/'+all.length+'件';
998
+ html='<table class="reftable"><thead><tr><th style="width:70px">番号</th><th>文言(タイトル/説明で共有)</th><th style="width:70px">文字数</th><th style="width:70px"></th></tr></thead><tbody>';
999
+ f.forEach(e=>{ const L=glen(e.text); const cls=L>35?'err':(L>20?'warn':''); html+='<tr><td class="mono">'+esc(e.no)+'</td><td class="reftext" title="'+esc(e.text)+'">'+esc(e.text)+'</td><td class="reflen '+cls+'">'+L+'</td><td><button class="iconbtn" data-refedit="t" data-no="'+esc(e.no)+'" title="編集">✎</button><button class="iconbtn danger" data-refdel="t" data-no="'+esc(e.no)+'" title="削除">✕</button></td></tr>'; });
1000
+ html+='</tbody></table>'; if(!f.length) html='<div class="empty">該当する文言がありません</div>';
1001
+ } else {
1002
+ const all=Object.keys(st.refs.media).map(no=>({no:+no,m:st.refs.media[no]}));
1003
+ const f=all.filter(e=>!q||String(e.no).includes(q)||String(e.m.card||'').includes(q)||String(e.m.sq||'').includes(q)).sort((a,b)=>a.no-b.no);
1004
+ document.getElementById('refCount').textContent='登録画像 '+f.length+'/'+all.length+'件';
1005
+ html='<table class="reftable"><thead><tr><th style="width:80px">画像番号</th><th>Card mediaID (1200)</th><th>SQ mediaID (1080)</th><th>ファイル名</th><th style="width:70px"></th></tr></thead><tbody>';
1006
+ f.forEach(e=>{ html+='<tr><td><span class="sw" style="display:inline-flex;width:26px;height:26px;vertical-align:middle">'+esc(e.no)+'</span> '+newBadge('media',e.no)+'</td><td class="mono">'+esc(e.m.card||'—')+'</td><td class="mono">'+esc(e.m.sq||'—')+'</td><td class="mono" style="color:var(--faint);font-size:11px">'+esc(e.no)+'-1200.jpg / '+esc(e.no)+'-1080.jpg</td><td><button class="iconbtn" data-refedit="m" data-no="'+esc(e.no)+'" title="編集">✎</button><button class="iconbtn danger" data-refdel="m" data-no="'+esc(e.no)+'" title="削除">✕</button></td></tr>'; });
1007
+ html+='</tbody></table>'; if(!f.length) html='<div class="empty">該当する画像がありません</div>';
1008
+ }
1009
+ host.innerHTML=html;
1010
+ }
1011
+ function refCommit(fn,msg){ snapshot(); fn(); persist(); buildDatalists(); render(); toast(msg); }
1012
+ function refAdd(){
1013
+ if(refTab==='titles'){
1014
+ openModal({title:'タイトル/説明を登録', sub:'番号→文言。タイトル枠は20字目安・35字上限(説明文は長文可)', fields:[
1015
+ {key:'no',label:'番号',type:'number',value:''},{key:'text',label:'文言',type:'text',value:''}
1016
+ ], onOk:v=>{ const no=parseInt(v.no); if(!no){toast('番号を入力してください');return;} const dup=st.refs.titles[no]!=null; refCommit(()=>{ st.refs.titles[no]=v.text; }, dup?('番号'+no+'を上書き'):'登録しました'); }});
1017
+ } else {
1018
+ openModal({title:'画像を登録', sub:'画像番号→Card/SQ の mediaID。新規作成した画像をメディアに登録', fields:[
1019
+ {key:'no',label:'画像番号',type:'number',value:''},{key:'card',label:'Card mediaID (1200)',type:'text',value:''},{key:'sq',label:'SQ mediaID (1080)',type:'text',value:''}
1020
+ ], onOk:v=>{ const no=parseInt(v.no); if(!no){toast('画像番号を入力してください');return;} const isNew=st.refs.media[no]==null; refCommit(()=>{ const e=Object.assign({},st.refs.media[no]||{}); if(v.card!=='')e.card=v.card.trim(); if(v.sq!=='')e.sq=v.sq.trim(); st.refs.media[no]=e; if(isNew) newRefIds.media.add(String(no)); }, '登録しました'); }});
1021
+ }
1022
+ }
1023
+ function refEdit(kind,no){
1024
+ if(kind==='t'){ const cur=st.refs.titles[no]; openModal({title:'文言を編集', sub:'番号 '+no, fields:[{key:'text',label:'文言',type:'text',value:cur}], onOk:v=>{ refCommit(()=>{ st.refs.titles[no]=v.text; }, '更新しました'); }}); }
1025
+ else { const e=st.refs.media[no]||{}; openModal({title:'画像を編集', sub:'画像番号 '+no, fields:[{key:'card',label:'Card mediaID',type:'text',value:e.card||''},{key:'sq',label:'SQ mediaID',type:'text',value:e.sq||''}], onOk:v=>{ refCommit(()=>{ st.refs.media[no]={card:v.card.trim(),sq:v.sq.trim()}; }, '更新しました'); }}); }
1026
+ }
1027
+ function refDel(kind,no){ refCommit(()=>{ if(kind==='t') delete st.refs.titles[no]; else delete st.refs.media[no]; }, '削除しました'); }
1028
+
1029
+ /* ============================================================
1030
+ SPREADSHEET SELECTION / COPY / PASTE
1031
+ ============================================================ */
1032
+ const VARF=['format','imageNo','titleNo','descNo','paramCode','articleSlug']; // 広告タブ列
1033
+ const VARF_GROUPS=['name','paramCode','adGroupId','bid','deliveryStatus','tg_device','tg_gender','tg_age','tg_region','tg_dayHour','tg_audience','tg_searchKw'];
1034
+ const VARF_CAMPAIGNS=['name','campaignId','mode','budget','biddingStrategy','tcpa','status','variable','urlNumber','strategyMemo'];
1035
+ function tabCols(){ return sheetTab==='groups'?VARF_GROUPS : sheetTab==='campaigns'?VARF_CAMPAIGNS : VARF; }
1036
+ function getVar(item,c){ const f=tabCols()[c]; if(item.kind==='g') return item.g[f]==null?'':String(item.g[f]); if(item.kind==='c') return item.c[f]==null?'':String(item.c[f]); if(f==='paramCode') return item.g.paramCode||''; const v=item.a[f]; return v==null?'':String(v); }
1037
+ function setVar(item,c,val){ const f=tabCols()[c]; if(item.kind==='g'){ if(f==='deliveryStatus'){ const s=String(val).trim(); val=(s==='オフ'||s==='オン')?s:''; } item.g[f]=val; return; } if(item.kind==='c'){ if(f==='budget'||f==='tcpa') val=(val===''?null:Number(val)); item.c[f]=val; return; } if(f==='paramCode') item.g.paramCode=val; else if(f==='format') item.a.format=(String(val).toUpperCase()==='SQ'?'SQ':'Card'); else item.a[f]=val; }
1038
+ function flatVisible(){ const out=[]; if(sheetTab==='groups'){ st.campaigns.forEach(c=>c.adGroups.forEach(g=>out.push({kind:'g',g,c}))); return out; } if(sheetTab==='campaigns'){ st.campaigns.forEach(c=>out.push({kind:'c',c})); return out; } const filter=document.getElementById('sheetFilter').value; st.campaigns.forEach(c=>c.adGroups.forEach(g=>g.ads.forEach(a=>{ const r=resolveAd(a,g); const hasErr=r.errs.length>0; if(filter==='err'&&!hasErr)return; if(filter==='banner'&&!a.isBanner)return; out.push({kind:'a',a,g,c}); }))); return out; }
1039
+ let selA=null, selB=null, dragging=false;
1040
+ function clearSel(){ selA=selB=null; applySelection(); }
1041
+ function selRange(){ if(!selA||!selB) return null; return {r0:Math.min(selA.r,selB.r),r1:Math.max(selA.r,selB.r),c0:Math.min(selA.c,selB.c),c1:Math.max(selA.c,selB.c)}; }
1042
+ function applySelection(){ const host=document.getElementById('sheetHost'); if(!host) return; host.querySelectorAll('.selcell,.selanchor').forEach(el=>el.classList.remove('selcell','selanchor')); host.querySelectorAll('.cellcombo.comboSel').forEach(el=>el.classList.remove('comboSel')); host.querySelectorAll('td.rownum.rowsel').forEach(el=>el.classList.remove('rowsel')); const rg=selRange(); if(!rg) return; host.querySelectorAll('[data-sel]').forEach(el=>{ const r=+el.dataset.r, c=+el.dataset.c; if(r>=rg.r0&&r<=rg.r1&&c>=rg.c0&&c<=rg.c1){ el.classList.add('selcell'); const cb=el.closest('.cellcombo'); if(cb) cb.classList.add('comboSel'); if(selA&&r===selA.r&&c===selA.c) el.classList.add('selanchor'); } }); host.querySelectorAll('td.rownum').forEach(el=>{ const r=+el.dataset.rownum; if(r>=rg.r0&&r<=rg.r1) el.classList.add('rowsel'); }); }
1043
+ function copySelText(){ const rg=selRange(); if(!rg) return null; const vis=flatVisible(); const lines=[]; for(let r=rg.r0;r<=rg.r1;r++){ const it=vis[r]; if(!it) continue; const cells=[]; for(let c=rg.c0;c<=rg.c1;c++) cells.push(getVar(it,c)); lines.push(cells.join('\t')); } return lines.join('\n'); }
1044
+ function pasteGrid(txt){
1045
+ const rg=selRange(); if(!rg){ toast('貼付先セルを選択してください'); return; }
1046
+ const rows=txt.replace(/\r/g,'').replace(/\n+$/,'').split('\n').map(l=>l.split('\t'));
1047
+ const vis=flatVisible(); const maxc=tabCols().length-1; if(!vis[rg.r0]){ toast('貼付先がありません'); return; }
1048
+ mutate(()=>{
1049
+ if(sheetTab==='ads'){ const anchor=vis[rg.r0]; const group=anchor.g; const startIdx=group.ads.indexOf(anchor.a); rows.forEach((cols,i)=>{ let ad=group.ads[startIdx+i]; if(!ad){ ad=newAd(); group.ads.push(ad); } cols.forEach((val,j)=>{ const cc=rg.c0+j; if(cc>maxc) return; setVar({kind:'a',a:ad,g:group},cc,val); }); }); }
1050
+ else { const anchor=vis[rg.r0]; rows.forEach((cols,i)=>{ let it=vis[rg.r0+i]; if(!it){ if(sheetTab==='groups'){ const ng={uid:uid('g'),name:'AG'+(anchor.c.adGroups.length+1),paramCode:'',ads:[newAd()]}; anchor.c.adGroups.push(ng); it={kind:'g',g:ng,c:anchor.c}; } else { const nc={uid:uid('c'),name:'新規キャンペーン',campaignId:'',mode:'new',variable:'',urlNumber:'',budget:null,biddingStrategy:'',tcpa:null,status:'オフ',strategyMemo:'',adGroups:[{uid:uid('g'),name:'AG1',paramCode:'',ads:[newAd()]}]}; st.campaigns.push(nc); it={kind:'c',c:nc}; } } cols.forEach((val,j)=>{ const cc=rg.c0+j; if(cc>maxc) return; setVar(it,cc,val); }); }); }
1051
+ });
1052
+ clearSel(); toast(rows.length+'行を貼り付け');
1053
+ }
1054
+ function dupSelected(){
1055
+ const rg=selRange(); if(!rg){ toast('複製する行を選択してください'); return; } const vis=flatVisible(); const items=[]; for(let r=rg.r0;r<=rg.r1;r++){ if(vis[r]) items.push(vis[r]); } if(!items.length){ toast('対象なし'); return; }
1056
+ mutate(()=>{ items.forEach(it=>{
1057
+ if(it.kind==='g'){ const ng=JSON.parse(JSON.stringify(it.g)); ng.uid=uid('g'); ng.name=it.g.name+'_copy'; ng.adGroupId=''; ng.ads.forEach(a=>a.uid=uid('a')); const idx=it.c.adGroups.indexOf(it.g); it.c.adGroups.splice(idx+1,0,ng); }
1058
+ else if(it.kind==='c'){ const nc=cloneCampaign(it.c,false,false); const idx=st.campaigns.indexOf(it.c); st.campaigns.splice(idx+1,0,nc); }
1059
+ else { const idx=it.g.ads.indexOf(it.a); it.g.ads.splice(idx+1,0,cloneAd(it.a,null)); }
1060
+ }); }); clearSel(); toast(items.length+'件を複製');
1061
+ }
1062
+
1063
+ /* ---- ▼ 右スライドサイドバー(一覧から番号を選ぶ) ---- */
1064
+ let drawerTarget=null;
1065
+ function drawerTypeForField(f){ return f==='imageNo'?'media':'titles'; }
1066
+ function openDrawerFor(u,f){
1067
+ if(!u||!f) return;
1068
+ const isOpen = document.getElementById('refDrawer').classList.contains('open');
1069
+ const same = drawerTarget && drawerTarget.u===u && drawerTarget.f===f;
1070
+ if(isOpen && same){ closeDrawer(); return; } // 同じセルを再度押すと閉じる(トグル)
1071
+ drawerTarget={u,f,type:drawerTypeForField(f)};
1072
+ if(!same) document.getElementById('drawerSearch').value='';
1073
+ renderDrawer();
1074
+ document.getElementById('refDrawer').classList.add('open'); // 背景は暗くしない(バックドロップ不使用)
1075
+ }
1076
+ function openRefDrawer(btn){ openDrawerFor(btn.dataset.u, btn.dataset.f); }
1077
+ /* YDA広告グループ ターゲティング設定値(Web調査ベース・複数選択可) */
1078
+ const TG_OPTIONS = {
1079
+ tg_device:{label:'デバイス', opts:['全てのデバイス','PC','スマートフォン','タブレット']},
1080
+ tg_gender:{label:'性別', opts:['男性','女性','不明']},
1081
+ tg_age:{label:'年齢', opts:['18-19歳','20-24歳','25-29歳','30-34歳','35-39歳','40-44歳','45-49歳','50-54歳','55-59歳','60-64歳','65-69歳','70歳以上','不明']},
1082
+ tg_region:{label:'地域', opts:['全国','北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県','新潟県','富山県','石川県','福井県','山梨県','長野県','岐阜県','静岡県','愛知県','三重県','滋賀県','京都府','大阪府','兵庫県','奈良県','和歌山県','鳥取県','島根県','岡山県','広島県','山口県','徳島県','香川県','愛媛県','高知県','福岡県','佐賀県','長崎県','熊本県','大分県','宮崎県','鹿児島県','沖縄県']},
1083
+ tg_dayHour:{label:'曜日・時間帯', opts:['全ての曜日・時間帯','平日','土日祝','月','火','水','木','金','土','日','午前(6-12時)','午後(12-18時)','夜間(18-24時)','深夜(0-6時)']},
1084
+ tg_audience:{label:'オーディエンスリスト', opts:['サイトリターゲティング','類似ユーザー','オーディエンスカテゴリー','サーチキーワード','購買データ','属性・興味関心']}
1085
+ };
1086
+ function openTargetingDrawer(gUid, field){
1087
+ const isOpen=document.getElementById('refDrawer').classList.contains('open');
1088
+ const same=drawerTarget && drawerTarget.type==='tg' && drawerTarget.gUid===gUid && drawerTarget.field===field;
1089
+ if(isOpen && same){ closeDrawer(); return; }
1090
+ drawerTarget={type:'tg', gUid, field};
1091
+ if(!same) document.getElementById('drawerSearch').value='';
1092
+ renderDrawer();
1093
+ document.getElementById('refDrawer').classList.add('open');
1094
+ }
1095
+ function drawerTgToggle(val){
1096
+ if(!drawerTarget||drawerTarget.type!=='tg') return; const r=findGroup(drawerTarget.gUid); if(!r) return; const f=drawerTarget.field;
1097
+ let cur=(r.g[f]?String(r.g[f]):'').split(',').map(s=>s.trim()).filter(Boolean);
1098
+ const i=cur.indexOf(val); if(i>=0) cur.splice(i,1); else cur.push(val);
1099
+ mutate(()=>{ r.g[f]=cur.join(', '); });
1100
+ renderDrawer();
1101
+ }
1102
+ function closeDrawer(){ document.getElementById('refDrawer').classList.remove('open'); drawerTarget=null; }
1103
+ function renderDrawer(){
1104
+ if(!drawerTarget) return;
1105
+ const q=(document.getElementById('drawerSearch').value||'').trim().toLowerCase();
1106
+ const titleEl=document.getElementById('drawerTitle'), listEl=document.getElementById('drawerList'), tgtEl=document.getElementById('drawerTarget');
1107
+ if(drawerTarget.type==='tg'){
1108
+ const cfg=TG_OPTIONS[drawerTarget.field]; const r=findGroup(drawerTarget.gUid); const g=r?r.g:null;
1109
+ const cur=(g&&g[drawerTarget.field]?String(g[drawerTarget.field]):'').split(',').map(s=>s.trim()).filter(Boolean);
1110
+ titleEl.textContent=cfg.label+' を選択(複数可)';
1111
+ tgtEl.textContent='入力先: '+(g&&g.name?g.name:'広告グループ')+' / '+cfg.label+(cur.length?('('+cur.length+'件選択中)'):'(未設定)');
1112
+ let h=''; cfg.opts.forEach(o=>{ if(q&&!o.toLowerCase().includes(q)) return; const on=cur.indexOf(o)>=0; h+='<div class="ddrow'+(on?' cur':'')+'" data-tgval="'+esc(o)+'"><span class="ddno">'+(on?'✓':'')+'</span><span class="ddtx">'+esc(o)+'</span></div>'; });
1113
+ listEl.innerHTML=h||'<div class="empty" style="padding:24px">該当なし</div>'; return;
1114
+ }
1115
+ const fieldName={imageNo:'画像番号',titleNo:'タイトル番号',descNo:'説明文番号'}[drawerTarget.f]||'';
1116
+ let curNo=''; const fa=findAd(drawerTarget.u); if(fa) curNo=String(fa.a[drawerTarget.f]||'');
1117
+ tgtEl.textContent='入力先: '+fieldName+(curNo?(' / 現在 '+curNo):' / 未設定');
1118
+ let html='';
1119
+ if(drawerTarget.type==='titles'){
1120
+ titleEl.textContent=(drawerTarget.f==='descNo'?'説明文':'タイトル')+'一覧(文言ライブラリ)';
1121
+ Object.keys(st.refs.titles).sort((a,b)=>a-b).forEach(no=>{ const t=st.refs.titles[no]; if(String(t||'').trim()==='') return; if(q&&!(String(no).includes(q)||String(t).toLowerCase().includes(q))) return; html+='<div class="ddrow'+(String(no)===curNo?' cur':'')+'" data-no="'+esc(no)+'"><span class="ddno">'+esc(no)+'</span><span class="ddtx">'+esc(t)+'</span></div>'; });
1122
+ } else {
1123
+ titleEl.textContent='登録画像 一覧';
1124
+ Object.keys(st.refs.media).sort((a,b)=>a-b).forEach(no=>{ const m=st.refs.media[no]; if(q&&!(String(no).includes(q)||String(m.card||'').includes(q)||String(m.sq||'').includes(q))) return; html+='<div class="ddrow'+(String(no)===curNo?' cur':'')+'" data-no="'+esc(no)+'"><span class="ddno">'+esc(no)+'</span>'+newBadge('media',no)+'<span class="ddtx">Card '+esc(m.card||'—')+' / SQ '+esc(m.sq||'—')+'</span></div>'; });
1125
+ }
1126
+ listEl.innerHTML=html||'<div class="empty" style="padding:24px">該当なし</div>';
1127
+ }
1128
+ function drawerPick(no){
1129
+ if(!drawerTarget) return;
1130
+ const f=drawerTarget.f, num=/^\d+$/.test(no)?Number(no):no;
1131
+ const compat = drawerTarget.type==='media'?[1]:[2,3]; // media=画像番号列 / titles=タイトル・説明列
1132
+ const rg=selRange(); let applied=0;
1133
+ mutate(()=>{
1134
+ if(rg){ const vis=flatVisible(); for(let rr=rg.r0; rr<=rg.r1; rr++){ const item=vis[rr]; if(!item) continue; for(let cc=rg.c0; cc<=rg.c1; cc++){ if(compat.indexOf(cc)>=0){ setVar(item,cc,num); applied++; } } } }
1135
+ if(applied===0){ setField(drawerTarget.u,f,num); applied=1; }
1136
+ });
1137
+ toast('番号 '+no+' を'+applied+'セルに入力');
1138
+ closeDrawer();
1139
+ }
1140
+
1141
+ /* ============================================================
1142
+ EDIT HANDLERS
1143
+ ============================================================ */
1144
+ function setField(u,f,val){
1145
+ const found=findAd(u); if(!found) return;
1146
+ if(f==='paramCode'){ found.g.paramCode=val; } // group-level
1147
+ else if(f==='isBanner'){ found.a.isBanner=val; }
1148
+ else { found.a[f]=val; }
1149
+ }
1150
+ document.addEventListener('change', e=>{
1151
+ const t=e.target; if(!t.dataset||!t.dataset.u||!t.dataset.f) return;
1152
+ const f=t.dataset.f; const val = t.type==='checkbox'? t.checked : t.value;
1153
+ mutate(()=>setField(t.dataset.u,f,val));
1154
+ });
1155
+ /* 広告グループ/キャンペーン タブの一覧編集 */
1156
+ document.addEventListener('change', e=>{
1157
+ const t=e.target; if(!t.dataset) return;
1158
+ if(t.dataset.gu && t.dataset.gf){ const gf=t.dataset.gf, val=t.value; mutate(()=>{ const r=findGroup(t.dataset.gu); if(r) r.g[gf]=val; }); return; }
1159
+ if(t.dataset.cu && t.dataset.cf){ const cf=t.dataset.cf; let val=t.value; if(cf==='budget'||cf==='tcpa') val=(val===''?null:Number(val)); mutate(()=>{ const r=findCp(t.dataset.cu); if(r) r.c[cf]=val; }); return; }
1160
+ });
1161
+
1162
+ /* rename via contenteditable (tree) */
1163
+ document.addEventListener('blur', e=>{
1164
+ const t=e.target; if(!t.dataset||!t.dataset.rename) return;
1165
+ const val=t.textContent.trim();
1166
+ mutate(()=>{ if(t.dataset.rename==='cp'){ const r=findCp(t.dataset.u); if(r) r.c.name=val; } else { const r=findGroup(t.dataset.u); if(r) r.g.name=val; } });
1167
+ }, true);
1168
+ document.addEventListener('keydown', e=>{ if(e.target.dataset&&e.target.dataset.rename&&e.key==='Enter'){ e.preventDefault(); e.target.blur(); } });
1169
+
1170
+ /* action clicks */
1171
+ document.addEventListener('click', e=>{
1172
+ const btn=e.target.closest('[data-act]'); if(!btn) return;
1173
+ const act=btn.dataset.act, u=btn.dataset.u;
1174
+ if(act==='toggle'){ st.ui.expanded[u]= (st.ui.expanded[u]==='c')?undefined:'c'; persist(); renderTree(); return; }
1175
+ if(act==='ad-dup'){ dupAd(u,false); return; }
1176
+ if(act==='ad-del'){ const r=findAd(u); if(r) mutate(()=>r.g.ads.splice(r.i,1)); return; }
1177
+ if(act==='ag-addad'){ const r=findGroup(u); if(r) mutate(()=>r.g.ads.push(newAd())); return; }
1178
+ if(act==='ag-addn'){ addNAds(u); return; }
1179
+ if(act==='ag-dup'){ dupGroup(u); return; }
1180
+ if(act==='ag-del'){ const r=findGroup(u); if(r) mutate(()=>r.c.adGroups.splice(r.i,1)); return; }
1181
+ if(act==='ag-param'){ editParam(u); return; }
1182
+ if(act==='cp-addag'){ const r=findCp(u); if(r) mutate(()=>r.c.adGroups.push({uid:uid('g'),name:'AG'+(r.c.adGroups.length+1),paramCode:'',ads:[newAd()]})); return; }
1183
+ if(act==='cp-edit'){ editCampaign(u); return; }
1184
+ if(act==='cp-dup'){ dupCampaign(u); return; }
1185
+ if(act==='cp-del'){ const r=findCp(u); if(r) mutate(()=>st.campaigns.splice(r.i,1)); return; }
1186
+ if(act==='add-campaign'){ addCampaign(); return; }
1187
+ });
1188
+ function addCampaign(){ mutate(()=>st.campaigns.push({uid:uid('c'),name:'新規キャンペーン',campaignId:'',mode:'new',variable:'',urlNumber:suggestUrlNumber(),budget:null,biddingStrategy:'',tcpa:null,status:'オフ',strategyMemo:'',adGroups:[{uid:uid('g'),name:'AG1',paramCode:'',ads:[newAd()]}]})); toast('キャンペーンを追加'); }
1189
+
1190
+ /* ---- clone helpers ---- */
1191
+ function cloneAd(a, clear){ const n=newAd(Object.assign({},a)); n.uid=uid('a'); if(clear){ clear.forEach(k=>{ if(k==='creative'){n.imageNo='';n.titleNo='';n.descNo='';} else n[k]=''; }); } return n; }
1192
+ function dupAd(u, clear){ const r=findAd(u); if(!r) return; mutate(()=>r.g.ads.splice(r.i+1,0,cloneAd(r.a, clear?['creative']:null))); }
1193
+ function addNAds(u){
1194
+ const r=findGroup(u); if(!r) return;
1195
+ openModal({title:'N本まとめて追加', sub:'空の広告を指定本数追加します(1:1:N 量産)', fields:[{key:'n',label:'本数',type:'number',value:10}], onOk:v=>{ const raw=parseInt(v.n); if(!Number.isFinite(raw)||raw<1){ toast('本数は1以上を入力してください'); return; } const n=Math.min(50,raw); mutate(()=>{ for(let i=0;i<n;i++) r.g.ads.push(newAd()); }); toast(n+'本追加しました'); }});
1196
+ }
1197
+ function dupGroup(u){
1198
+ const r=findGroup(u); if(!r) return;
1199
+ openModal({title:'広告グループを複製', sub:r.g.name+' を複製します', fields:[
1200
+ {key:'param',label:'新しい paramCode(空ならコピー)',type:'text',value:''},
1201
+ {key:'clear',label:'',type:'check',value:true,text:'広告の中身(画像/タイトル/説明文)をクリアして器だけ残す'}
1202
+ ], onOk:v=>{ mutate(()=>{ const ng=JSON.parse(JSON.stringify(r.g)); ng.uid=uid('g'); ng.name=r.g.name+'_copy'; ng.paramCode=(v.param||r.g.paramCode); ng.adGroupId=''; ng.ads=r.g.ads.map(a=>cloneAd(a, v.clear?['creative']:null)); r.c.adGroups.splice(r.i+1,0,ng); }); toast('複製しました'); }});
1203
+ }
1204
+ function editParam(u){
1205
+ const r=findGroup(u); if(!r) return; const g=r.g;
1206
+ openModal({title:'広告グループ設定', sub:'paramCode(【code】)でキャンペーン/広告グループIDを解決。既存ID・ターゲティングは任意(広告グループCSVの列)', fields:[
1207
+ {key:'name',label:'広告グループ名',type:'text',value:g.name},
1208
+ {key:'param',label:'paramCode(【code】)',type:'text',value:g.paramCode||''},
1209
+ {key:'adGroupId',label:'広告グループID(既存・空=paramCodeで解決)',type:'text',value:g.adGroupId||''},
1210
+ {key:'bid',label:'入札価格(任意)',type:'text',value:g.bid||''},
1211
+ {key:'tg_device',label:'ターゲティング: デバイス',type:'text',value:g.tg_device||''},
1212
+ {key:'tg_gender',label:'ターゲティング: 性別',type:'text',value:g.tg_gender||''},
1213
+ {key:'tg_age',label:'ターゲティング: 年齢',type:'text',value:g.tg_age||''},
1214
+ {key:'tg_region',label:'ターゲティング: 地域',type:'text',value:g.tg_region||''},
1215
+ {key:'tg_dayHour',label:'ターゲティング: 曜日・時間帯',type:'text',value:g.tg_dayHour||''},
1216
+ {key:'tg_audience',label:'ターゲティング: オーディエンスリスト',type:'text',value:g.tg_audience||''},
1217
+ {key:'tg_searchKw',label:'ターゲティング: サーチキーワード',type:'text',value:g.tg_searchKw||''}
1218
+ ], onOk:v=>{ mutate(()=>{ g.name=v.name; g.paramCode=(v.param||'').trim(); g.adGroupId=v.adGroupId; g.bid=v.bid; g.tg_device=v.tg_device; g.tg_gender=v.tg_gender; g.tg_age=v.tg_age; g.tg_region=v.tg_region; g.tg_dayHour=v.tg_dayHour; g.tg_audience=v.tg_audience; g.tg_searchKw=v.tg_searchKw; }); }});
1219
+ }
1220
+ function suggestUrlNumber(){ let mx=0; st.campaigns.forEach(c=>{ const n=parseInt(c.urlNumber); if(!isNaN(n)&&n>mx) mx=n; }); return String(mx+1).padStart(2,'0'); }
1221
+ function editCampaign(u){
1222
+ const r=findCp(u); if(!r) return; const c=r.c;
1223
+ openModal({title:'キャンペーン設定', sub:c.name, fields:[
1224
+ {key:'name',label:'キャンペーン名',type:'text',value:c.name},
1225
+ {key:'campaignId',label:'キャンペーンID(既存・空=paramCodeで解決)',type:'text',value:c.campaignId||''},
1226
+ {key:'mode',label:'モード',type:'select',value:c.mode,options:Object.keys(MODES).map(k=>({v:k,t:MODES[k]}))},
1227
+ {key:'variable',label:'検証する変数(1キャンペーン=1変数)',type:'text',value:c.variable},
1228
+ {key:'urlNumber',label:'入稿リンク末尾番号(空き: '+suggestUrlNumber()+')',type:'text',value:c.urlNumber},
1229
+ {key:'budget',label:'予算/日(空でモード既定)',type:'number',value:c.budget??''},
1230
+ {key:'biddingStrategy',label:'配信戦略',type:'select',value:c.biddingStrategy||st.project.biddingStrategyDefault,options:[{v:'tCPA',t:'tCPA'},{v:'最大配信',t:'最大配信'}]},
1231
+ {key:'tcpa',label:'tCPA(円)',type:'number',value:c.tcpa??st.project.tcpaDefault},
1232
+ {key:'strategyMemo',label:'戦略メモ(後乗せ可)',type:'textarea',value:c.strategyMemo}
1233
+ ], extra:'<button class="act" id="genName" style="margin-right:auto">キャンペーン名を自動生成</button>', onMount:(root,getVals,setVal)=>{
1234
+ root.querySelector('#genName').addEventListener('click',()=>{ const num=getVals().urlNumber||suggestUrlNumber(); const first=c.adGroups[0]&&c.adGroups[0].ads[0]; const cr=first?(String(first.imageNo||'CR')+(first.format==='SQ'?'s':'c')):'CR'; setVal('name', num+'_'+cr+'_'+today()); });
1235
+ }, onOk:v=>{ mutate(()=>{ c.name=v.name; c.campaignId=v.campaignId; c.mode=v.mode; c.variable=v.variable; c.urlNumber=v.urlNumber; c.budget=v.budget===''?null:Number(v.budget); c.biddingStrategy=v.biddingStrategy; c.tcpa=v.tcpa===''?null:Number(v.tcpa); c.strategyMemo=v.strategyMemo; }); }});
1236
+ }
1237
+ function cloneCampaign(c, clearCreative, clearParam){
1238
+ const nc=JSON.parse(JSON.stringify(c)); nc.uid=uid('c'); nc.strategyMemo=''; nc.variable='';
1239
+ nc.adGroups.forEach(g=>{ g.uid=uid('g'); if(clearParam) g.paramCode=''; g.ads.forEach(a=>{ a.uid=uid('a'); if(clearCreative){a.imageNo='';a.titleNo='';a.descNo='';} }); });
1240
+ return nc;
1241
+ }
1242
+ function dupCampaign(u){
1243
+ const r=findCp(u); if(!r) return; const c=r.c;
1244
+ openModal({title:'キャンペーンを複製 / 量産', sub:'北さんの「テンプレ複製→中身クリア→変数差し替え」。'+c.name+' をテンプレに複製します', fields:[
1245
+ {key:'count',label:'複製する本数(変数の数だけ)',type:'number',value:1},
1246
+ {key:'clearCreative',label:'',type:'check',value:true,text:'クリエイティブ(画像/タイトル/説明文)をクリア'},
1247
+ {key:'clearParam',label:'',type:'check',value:false,text:'paramCode もクリア(新規グループとして作り直す)'},
1248
+ {key:'autoname',label:'',type:'check',value:true,text:'キャンペーン名を {空き番号}_{CR名}_'+today()+' で自動採番'}
1249
+ ], onOk:v=>{ const raw=parseInt(v.count); if(!Number.isFinite(raw)||raw<1){ toast('本数は1以上を入力してください'); return; } const n=Math.min(50,raw); mutate(()=>{ const base=r.i; const startNum=parseInt(suggestUrlNumber())||1; for(let i=0;i<n;i++){ const nc=cloneCampaign(c, v.clearCreative, v.clearParam); if(v.autoname){ const num=String(startNum+i).padStart(2,'0'); nc.urlNumber=num; const f=nc.adGroups[0]&&nc.adGroups[0].ads[0]; const cr=f?(String(f.imageNo||'CR')+(f.format==='SQ'?'s':'c')):'CR'; nc.name=num+'_'+cr+'_'+today(); } else { nc.name=c.name+'_copy'+(n>1?(i+1):''); } st.campaigns.splice(base+1+i,0,nc); } }); toast(n+'本のキャンペーンを生成'); }});
1250
+ }
1251
+
1252
+ /* ============================================================
1253
+ MODAL
1254
+ ============================================================ */
1255
+ function openModal(cfg){
1256
+ const root=document.getElementById('modalRoot');
1257
+ const fid=k=>'mf_'+k;
1258
+ let body='';
1259
+ (cfg.fields||[]).forEach(f=>{
1260
+ if(f.type==='check'){ body+='<label class="mcheck"><input type="checkbox" id="'+fid(f.key)+'"'+(f.value?' checked':'')+'> '+esc(f.text||f.label)+'</label>'; return; }
1261
+ body+='<div class="mfield"><label>'+esc(f.label)+'</label>';
1262
+ if(f.type==='select'){ body+='<select id="'+fid(f.key)+'">'+f.options.map(o=>'<option value="'+esc(o.v)+'"'+(o.v==f.value?' selected':'')+'>'+esc(o.t)+'</option>').join('')+'</select>'; }
1263
+ else if(f.type==='textarea'){ body+='<textarea id="'+fid(f.key)+'">'+esc(f.value)+'</textarea>'; }
1264
+ else { body+='<input type="'+(f.type==='number'?'number':'text')+'" id="'+fid(f.key)+'" value="'+esc(f.value)+'">'; }
1265
+ if(f.hint) body+='<div class="hint">'+esc(f.hint)+'</div>';
1266
+ body+='</div>';
1267
+ });
1268
+ root.innerHTML='<div class="overlay"><div class="modal"><h3>'+esc(cfg.title)+'</h3>'+(cfg.sub?'<div class="msub">'+esc(cfg.sub)+'</div>':'')+'<div class="mbody">'+body+'</div><div class="mfoot">'+(cfg.extra||'')+'<button class="act" data-mo="cancel">キャンセル</button><button class="act primary" data-mo="ok">OK</button></div></div></div>';
1269
+ const getVals=()=>{ const o={}; (cfg.fields||[]).forEach(f=>{ const el=document.getElementById(fid(f.key)); if(!el) return; o[f.key]= f.type==='check'? el.checked : el.value; }); return o; };
1270
+ const setVal=(k,val)=>{ const el=document.getElementById(fid(k)); if(el) el.value=val; };
1271
+ const close=()=>root.innerHTML='';
1272
+ root.querySelector('[data-mo="cancel"]').addEventListener('click',close);
1273
+ root.querySelector('.overlay').addEventListener('mousedown',ev=>{ if(ev.target.classList.contains('overlay')) close(); });
1274
+ root.querySelector('[data-mo="ok"]').addEventListener('click',()=>{ const v=getVals(); close(); if(cfg.onOk) cfg.onOk(v); });
1275
+ if(cfg.onMount) cfg.onMount(root, getVals, setVal);
1276
+ const first=root.querySelector('input,select,textarea'); if(first) first.focus();
1277
+ }
1278
+
1279
+ /* 案件テンプレは上部チップで直接編集(モーダル廃止・renderTplChips参照) */
1280
+
1281
+ /* ============================================================
1282
+ JSON I/O — バージョン保存 / 履歴 / 入稿JSON書き出し
1283
+ (CSV書き出しは2026-07-23に完全廃止。入稿受け渡しはJSONに統一)
1284
+ ============================================================ */
1285
+ function firstParamOf(c){ const g=c.adGroups.find(g=>g.paramCode); return g?g.paramCode:''; }
1286
+ function resolveGroupIds(paramCode){ const g=st.refs.groups[paramCode]; return {campaignId:g?g.campaignId:'', adGroupId:g?g.adGroupId:''}; }
1287
+ function download(filename, text, mime){ const blob=new Blob([text],{type:mime||'text/plain;charset=utf-8'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=filename; document.body.appendChild(a); a.click(); setTimeout(()=>{URL.revokeObjectURL(a.href); a.remove();},100); }
1288
+ function tsCompact(){ const d=new Date(); const p=n=>String(n).padStart(2,'0'); return ''+d.getFullYear()+p(d.getMonth()+1)+p(d.getDate())+'-'+p(d.getHours())+p(d.getMinutes())+p(d.getSeconds()); }
1289
+
1290
+ /* ---- 保存フォルダ連携 (File System Access API / Chrome系のみ) ----
1291
+ スキル同梱の「入稿計画/」フォルダを一度指定すると、以後は直接書き込み・読込・削除。
1292
+ 非対応環境(file://等)は自動でダウンロード動作にフォールバックする。 */
1293
+ let dirHandle=null, pendingDirHandle=null;
1294
+ const fsSupported = typeof window.showDirectoryPicker==='function';
1295
+ function idb(){ return new Promise((res,rej)=>{ const r=indexedDB.open('yda_planner_fs',1); r.onupgradeneeded=()=>r.result.createObjectStore('handles'); r.onsuccess=()=>res(r.result); r.onerror=()=>rej(r.error); }); }
1296
+ async function idbSet(k,v){ const db=await idb(); return new Promise((res,rej)=>{ const tx=db.transaction('handles','readwrite'); tx.objectStore('handles').put(v,k); tx.oncomplete=res; tx.onerror=()=>rej(tx.error); }); }
1297
+ async function idbGet(k){ const db=await idb(); return new Promise((res,rej)=>{ const tx=db.transaction('handles','readonly'); const rq=tx.objectStore('handles').get(k); rq.onsuccess=()=>res(rq.result); rq.onerror=()=>rej(rq.error); }); }
1298
+ async function connectFolder(){
1299
+ if(!fsSupported){ toast('このブラウザはフォルダ連携非対応です(保存はダウンロード動作)'); return; }
1300
+ try{
1301
+ if(pendingDirHandle){ const p=await pendingDirHandle.requestPermission({mode:'readwrite'}); if(p==='granted'){ dirHandle=pendingDirHandle; pendingDirHandle=null; renderHist(); toast('保存フォルダに再接続しました'); return; } }
1302
+ dirHandle=await window.showDirectoryPicker({id:'yda-planner-plans', mode:'readwrite'});
1303
+ pendingDirHandle=null; await idbSet('dir',dirHandle); renderHist(); toast('保存フォルダに接続しました: '+dirHandle.name);
1304
+ }catch(e){ if(e && e.name!=='AbortError') toast('フォルダ接続に失敗: '+e.message); }
1305
+ }
1306
+ async function restoreFolder(){
1307
+ if(!fsSupported) return;
1308
+ try{ const h=await idbGet('dir'); if(!h) return;
1309
+ const p=await h.queryPermission({mode:'readwrite'});
1310
+ if(p==='granted'){ dirHandle=h; toast('入稿計画フォルダ('+h.name+'/)に自動接続しました'); }
1311
+ else pendingDirHandle=h; // 権限失効時はボタン押下(ユーザー操作)で1クリック再要求
1312
+ }catch(e){}
1313
+ if(activeView==='hist') renderHist();
1314
+ }
1315
+ async function writePlanFile(name, text){
1316
+ if(!dirHandle) return false;
1317
+ try{ const fh=await dirHandle.getFileHandle(name,{create:true}); const w=await fh.createWritable(); await w.write(text); await w.close(); return true; }
1318
+ catch(e){ toast('フォルダ書き込み失敗: '+e.message+'(ダウンロードに切替)'); return false; }
1319
+ }
1320
+
1321
+ /* ---- 履歴 (localStorage: メタ一覧 + バージョン本体) ----
1322
+ 本体もブラウザ内に自動保存する → フォルダ選択なしで一覧・再読込・削除が完結する。
1323
+ フォルダ(入稿計画/)接続時はファイルにも書き出す(二重保存)。 */
1324
+ const BODY_KEY = 'yda_planner_histbody_';
1325
+ function histList(){ try{ return JSON.parse(localStorage.getItem(HIST_KEY))||[]; }catch(e){ return []; } }
1326
+ function histSave(list){ try{ localStorage.setItem(HIST_KEY, JSON.stringify(list)); }catch(e){} }
1327
+ function nextVersionId(){ return histList().reduce((m,h)=>Math.max(m,h.versionId||0),0)+1; }
1328
+ function histGetBody(vid){ try{ return localStorage.getItem(BODY_KEY+vid); }catch(e){ return null; } }
1329
+ function histDelBody(vid){ try{ localStorage.removeItem(BODY_KEY+vid); }catch(e){} }
1330
+ /* 容量超過時は古い本体から捨てる (メタ一覧は残す = ファイル側から読み直せる) */
1331
+ function histPutBody(vid, json){
1332
+ for(;;){
1333
+ try{ localStorage.setItem(BODY_KEY+vid, json); return true; }
1334
+ catch(e){
1335
+ const stored=histList().filter(h=>h.versionId!==vid && histGetBody(h.versionId)!=null)
1336
+ .sort((a,b)=>(a.versionId||0)-(b.versionId||0));
1337
+ if(!stored.length) return false;
1338
+ histDelBody(stored[0].versionId);
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ /* ---- バージョン保存: 保存のたびに常に新バージョン (上書きなし) ---- */
1344
+ function saveVersion(){
1345
+ const prevAuthor = st.meta.author || localStorage.getItem(AUTHOR_KEY) || '';
1346
+ openModal({title:'バージョン保存', sub:'常に新バージョンとして保存します(上書きなし)/ 媒体: YDA / 次: v'+nextVersionId()+' / 保存先: '+(dirHandle?dirHandle.name+'/':'ダウンロード(フォルダ未接続)'), fields:[
1347
+ {key:'name',label:'名称',type:'text',value:st.meta.name||st.project.name,hint:'履歴一覧に表示される名前'},
1348
+ {key:'memo',label:'メモ',type:'textarea',value:st.meta.memo||''},
1349
+ {key:'author',label:'作成者',type:'text',value:prevAuthor,hint:'空欄可。次回保存時も記憶されます'}
1350
+ ], onOk: async v=>{
1351
+ const now=new Date().toISOString();
1352
+ const versionId=nextVersionId();
1353
+ st.meta={versionId, name:v.name||st.project.name, memo:v.memo||'', author:v.author||'', platform:'yda', createdAt:now, updatedAt:now};
1354
+ try{ localStorage.setItem(AUTHOR_KEY, v.author||''); }catch(e){}
1355
+ const c=validationCounts();
1356
+ const fileName=(st.project.name||'plan')+'_v'+versionId+'_'+tsCompact()+'.json';
1357
+ const json=JSON.stringify(stateJson(),null,2);
1358
+ const inLocal=histPutBody(versionId, json); // ブラウザ内に常時保存 (フォルダ不要で再読込可)
1359
+ const wrote=await writePlanFile(fileName,json); // フォルダ(入稿計画/)接続時はファイルにも書き出し
1360
+ if(!wrote) download(fileName,json,'application/json'); // 未接続時はDLでファイルも残す(入稿計画へ手動格納可)
1361
+ const list=histList();
1362
+ list.push({versionId, name:st.meta.name, memo:st.meta.memo, author:st.meta.author, platform:'yda', createdAt:now, fileName, inFolder:wrote, inLocal, campaigns:st.campaigns.length, ads:c.ads});
1363
+ histSave(list); persist();
1364
+ if(activeView==='hist') renderHist();
1365
+ toast('v'+versionId+' を保存しました'+(wrote?'(入稿計画フォルダにも書き出し)':''));
1366
+ }});
1367
+ }
1368
+
1369
+ /* ---- 履歴ビュー ---- */
1370
+ function renderHist(){
1371
+ const s=document.getElementById('fsStatus');
1372
+ if(!fsSupported) s.textContent='履歴はブラウザ内に自動保存(このブラウザはフォルダ連携非対応)';
1373
+ else if(dirHandle) s.textContent='📁 入稿計画フォルダ接続中: '+dirHandle.name+'/(履歴はブラウザ内+ファイルの二重保存)';
1374
+ else if(pendingDirHandle) s.textContent='履歴はブラウザ内に自動保存。フォルダ再接続待ち(右のボタンで1クリック再接続)';
1375
+ else s.textContent='履歴はブラウザ内に自動保存。「保存フォルダに接続」でスキル内の 入稿計画/ にも書き出せます(初回のみ選択)';
1376
+ const list=histList().slice().sort((a,b)=>(b.versionId||0)-(a.versionId||0));
1377
+ const host=document.getElementById('histHost');
1378
+ if(!list.length){ host.innerHTML='<div class="hinttxt" style="padding:24px; font-size:13px">保存履歴はまだありません。「バージョン保存」で保存すると、バージョン・日時・媒体・名称がここに一覧表示されます。</div>'; return; }
1379
+ host.innerHTML='<table class="g"><thead><tr><th>Ver</th><th>保存日時</th><th>媒体</th><th>名称</th><th>メモ</th><th>作成者</th><th>規模</th><th>ファイル</th><th style="width:200px"></th></tr></thead><tbody>'+
1380
+ list.map(h=>'<tr data-hv="'+h.versionId+'">'+
1381
+ '<td class="mono"><b>v'+h.versionId+'</b>'+(st.meta.versionId===h.versionId?' <span class="pill green">現在</span>':'')+'</td>'+
1382
+ '<td class="mono">'+esc(String(h.createdAt||'').replace('T',' ').slice(0,19))+'</td>'+
1383
+ '<td><span class="pill">'+esc((h.platform||'yda').toUpperCase())+'</span></td>'+
1384
+ '<td>'+esc(h.name||'')+'</td><td style="max-width:260px">'+esc(h.memo||'')+'</td><td>'+esc(h.author||'')+'</td>'+
1385
+ '<td class="mono">CP '+(h.campaigns??'—')+' / 広告 '+(h.ads??'—')+'</td>'+
1386
+ '<td class="mono" style="font-size:11px">'+esc(h.fileName||'')+(h.inLocal!==false?' <span class="pill">ブラウザ</span>':'')+(h.inFolder?' <span class="pill green">フォルダ</span>':'')+((h.inLocal===false&&!h.inFolder)?' <span class="pill amber">DL</span>':'')+'</td>'+
1387
+ '<td><button class="act ghost" data-hload="'+h.versionId+'">読込</button> <button class="act ghost" data-hedit="'+h.versionId+'">編集</button> <button class="act ghost" data-hdel="'+h.versionId+'">削除</button></td>'+
1388
+ '</tr>').join('')+'</tbody></table>';
1389
+ }
1390
+ async function histLoadVersion(vid){
1391
+ const h=histList().find(x=>x.versionId===vid); if(!h) return;
1392
+ // 1) ブラウザ内保存 → 即復元 (フォルダ不要)
1393
+ const local=histGetBody(vid);
1394
+ if(local){ try{ applyLoadedJson(JSON.parse(local)); toast('v'+vid+' を読み込みました'); return; }catch(e){} }
1395
+ // 2) フォルダ(入稿計画/) → 3) ファイル選択
1396
+ if(dirHandle && h.fileName){
1397
+ try{ const fh=await dirHandle.getFileHandle(h.fileName); const f=await fh.getFile(); applyLoadedJson(JSON.parse(await f.text())); toast('v'+vid+' を読み込みました'); return; }
1398
+ catch(e){ toast('フォルダから読み込めません('+(e&&e.name||'エラー')+')。ファイル選択で読み込んでください'); }
1399
+ } else {
1400
+ toast('保存ファイルをファイル選択で読み込んでください('+(h.fileName||'保存したJSON')+')');
1401
+ }
1402
+ document.getElementById('fileInput').click();
1403
+ }
1404
+ function histEditVersion(vid){
1405
+ const list=histList(); const h=list.find(x=>x.versionId===vid); if(!h) return;
1406
+ openModal({title:'v'+vid+' の名称・メモを編集', fields:[
1407
+ {key:'name',label:'名称',type:'text',value:h.name||''},
1408
+ {key:'memo',label:'メモ',type:'textarea',value:h.memo||''}
1409
+ ], onOk: async v=>{
1410
+ h.name=v.name; h.memo=v.memo; histSave(list);
1411
+ const patch=j=>{ j.meta=Object.assign({},j.meta,{name:v.name,memo:v.memo,updatedAt:new Date().toISOString()}); return j; };
1412
+ const local=histGetBody(vid);
1413
+ if(local){ try{ histPutBody(vid, JSON.stringify(patch(JSON.parse(local)),null,2)); }catch(e){} }
1414
+ if(h.inFolder && dirHandle && h.fileName){
1415
+ try{ const fh=await dirHandle.getFileHandle(h.fileName); const f=await fh.getFile(); const j=patch(JSON.parse(await f.text()));
1416
+ const w=await(await dirHandle.getFileHandle(h.fileName,{create:true})).createWritable(); await w.write(JSON.stringify(j,null,2)); await w.close();
1417
+ }catch(e){}
1418
+ }
1419
+ if(st.meta.versionId===vid){ st.meta.name=v.name; st.meta.memo=v.memo; persist(); }
1420
+ renderHist(); toast('v'+vid+' を更新しました');
1421
+ }});
1422
+ }
1423
+ function histDeleteVersion(vid){
1424
+ const list=histList(); const h=list.find(x=>x.versionId===vid); if(!h) return;
1425
+ openModal({title:'v'+vid+' を削除しますか?', sub:(h.name||'')+'('+(h.fileName||'')+')'+(h.inFolder&&dirHandle?' — フォルダ内のファイルも削除されます':' — 履歴から削除します(ダウンロード済みファイルは残ります)'), fields:[], onOk: async ()=>{
1426
+ if(h.inFolder && dirHandle && h.fileName){ try{ await dirHandle.removeEntry(h.fileName); }catch(e){} }
1427
+ histDelBody(vid);
1428
+ histSave(list.filter(x=>x.versionId!==vid)); renderHist(); toast('v'+vid+' を削除しました');
1429
+ }});
1430
+ }
1431
+
1432
+ /* ---- 入稿JSON書き出し (CLI submit 用ボディ) ----
1433
+ 画面のプラン (案件設定チップ / キャンペーン行 / 広告グループ行のターゲティング列) を
1434
+ そのまま backend の ydaSubmitSchema 準拠のボディに変換する。設定モーダルは持たない。
1435
+ 手入稿(バナー)と未解決(参照表で引けない)広告は除外する。 */
1436
+
1437
+ /* 画面の日本語ラベル → API enum 変換表 (正: frontend/src/pages/submission/types.ts) */
1438
+ const TG_ENUM = {
1439
+ device:{'スマートフォン':'SMARTPHONE','PC':'DESKTOP','タブレット':'TABLET'},
1440
+ gender:{'男性':'ST_MALE','女性':'ST_FEMALE','不明':'ST_UNKNOWN'},
1441
+ age:{'18-19歳':'GT_RANGE18_19','20-24歳':'GT_RANGE20_24','25-29歳':'GT_RANGE25_29','30-34歳':'GT_RANGE30_34','35-39歳':'GT_RANGE35_39','40-44歳':'GT_RANGE40_44','45-49歳':'GT_RANGE45_49','50-54歳':'GT_RANGE50_54','55-59歳':'GT_RANGE55_59','60-64歳':'GT_RANGE60_64','65-69歳':'GT_RANGE65_69','70歳以上':'GT_RANGE70_UL2','不明':'GT_UNKNOWN2'},
1442
+ day:{'月':'MONDAY','火':'TUESDAY','水':'WEDNESDAY','木':'THURSDAY','金':'FRIDAY','土':'SATURDAY','日':'SUNDAY'},
1443
+ // tCPA=コンバージョン数最大化+目標CPA / 最大配信=コンバージョン数最大化(目標なし・要実機確認)
1444
+ bidding:{'tCPA':'MAXIMIZE_CONVERSIONS','最大配信':'MAXIMIZE_CONVERSIONS'}
1445
+ };
1446
+ function tgList(s){ return String(s||'').split(',').map(x=>x.trim()).filter(Boolean); }
1447
+ function mapDevices(s){ const l=tgList(s); if(!l.length||l.indexOf('全てのデバイス')>=0) return []; return l.map(x=>TG_ENUM.device[x]).filter(Boolean); }
1448
+ /* 曜日・時間帯セル → {days[], hours{start,end}|null}。「平日」「土日祝」は展開、
1449
+ 時間帯(午前(6-12時)等)は startHour/endHour に変換 (複数選択時は最初の1つ) */
1450
+ function mapDays(s){
1451
+ const l=tgList(s); const days=[]; let hours=null;
1452
+ if(!l.length||l.indexOf('全ての曜日・時間帯')>=0) return {days:[], hours:null};
1453
+ const add=d=>{ if(days.indexOf(d)<0) days.push(d); };
1454
+ l.forEach(x=>{
1455
+ if(x==='平日'){ ['MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY'].forEach(add); }
1456
+ else if(x==='土日祝'){ add('SATURDAY'); add('SUNDAY'); }
1457
+ else if(TG_ENUM.day[x]){ add(TG_ENUM.day[x]); }
1458
+ else { const m=x.match(/\((\d+)-(\d+)時\)/); if(m&&!hours) hours={start:+m[1], end:+m[2]}; }
1459
+ });
1460
+ return {days, hours};
1461
+ }
1462
+ const toStatus=s=>s==='オン'?'ACTIVE':'PAUSED';
1463
+
1464
+ function buildSubmitBody(){
1465
+ const p=st.project;
1466
+ const campaigns=st.campaigns.map(c=>{
1467
+ const bidLabel=String(c.biddingStrategy||p.biddingStrategyDefault||'').trim();
1468
+ const biddingStrategy=TG_ENUM.bidding[bidLabel]||bidLabel||'MAXIMIZE_CONVERSIONS';
1469
+ const adGroups=c.adGroups.map(g=>{
1470
+ const dh=mapDays(g.tg_dayHour);
1471
+ const ag={
1472
+ name:g.name,
1473
+ status:toStatus(g.deliveryStatus||p.deliveryStatus),
1474
+ smartTargeting:false, // GUI未対応 (将来ターゲティング列に追加)
1475
+ devices:mapDevices(g.tg_device),
1476
+ ages:tgList(g.tg_age).map(x=>TG_ENUM.age[x]).filter(Boolean),
1477
+ genders:tgList(g.tg_gender).map(x=>TG_ENUM.gender[x]).filter(Boolean),
1478
+ days:dh.days,
1479
+ placementsInclude:[], placementsExclude:[],
1480
+ // 地域/オーディエンス/サーチKW列はAPIのID解決が必要なためJSON未反映 (仕様ページ参照)
1481
+ audiencesInclude:[], audiencesExclude:[],
1482
+ ads:g.ads
1483
+ .map(a=>({a, r:resolveAd(a,g)}))
1484
+ .filter(x=>!x.a.isBanner && !x.r.errs.length)
1485
+ .map(x=>({
1486
+ name:x.r.adName,
1487
+ mediaId:String(x.r.mediaId),
1488
+ title:String(x.r.titleText),
1489
+ description:String(x.r.descText),
1490
+ finalUrl:(p.baseArticleUrl||'')+(x.a.articleSlug||'')+'?ad='+(g.paramCode||'')
1491
+ }))
1492
+ };
1493
+ if(dh.hours){ ag.startHour=dh.hours.start; ag.endHour=dh.hours.end; }
1494
+ return ag;
1495
+ });
1496
+ const cp={
1497
+ name:c.name,
1498
+ goal:p.goal||'CONVERSION',
1499
+ status:toStatus(c.status||p.deliveryStatus),
1500
+ startDate:p.startDate||today(),
1501
+ budget:Number(c.budget==null?(c.mode==='new'?p.budgetNew:p.budgetAsc):c.budget),
1502
+ biddingStrategy,
1503
+ adGroups
1504
+ };
1505
+ if(bidLabel==='tCPA'){ cp.targetCpa=Number(c.tcpa??p.tcpaDefault)||0; }
1506
+ else if(biddingStrategy==='CPC'){ cp.cpc=Number(c.adGroups[0]&&c.adGroups[0].bid)||0; }
1507
+ return cp;
1508
+ });
1509
+ return {accountId:String(p.accountId||'').trim(), campaigns};
1510
+ }
1511
+ function submitAdCount(body){ return body.campaigns.reduce((n,c)=>n+c.adGroups.reduce((m,g)=>m+g.ads.length,0),0); }
1512
+ async function exportSubmitJson(){
1513
+ const body=buildSubmitBody();
1514
+ if(!body.accountId){
1515
+ toast('案件設定の「アカウントID」を入力してください');
1516
+ const el=document.querySelector('#tplChips [data-proj="accountId"]'); if(el){ document.getElementById('cfgBox').open=true; el.focus(); }
1517
+ return;
1518
+ }
1519
+ const n=submitAdCount(body);
1520
+ if(n===0){ toast('入稿対象の広告が0件です(空入稿を防止しました)'); return; }
1521
+ const fileName=(st.project.name||'plan')+'_submit-body_'+tsCompact()+'.json';
1522
+ const json=JSON.stringify(body,null,2);
1523
+ const wrote=await writePlanFile(fileName,json);
1524
+ if(!wrote) download(fileName,json,'application/json');
1525
+ toast('入稿JSONを書き出しました(広告'+n+'件・'+(wrote?'フォルダ':'ダウンロード')+')— webgram submit --platform yda で入稿(dry-run から)');
1526
+ }
1527
+
1528
+ /* ---- JSON読込 (v1/v2 両対応) ---- */
1529
+ function applyLoadedJson(j){
1530
+ if(!j||!j.campaigns) throw 0;
1531
+ const m=migrateState(j);
1532
+ snapshot();
1533
+ st.project=Object.assign(defaultProject(),m.project);
1534
+ st.campaigns=m.campaigns;
1535
+ st.ui=m.ui||{expanded:{}};
1536
+ if(m.refs){ st.refs=Object.assign(st.refs,m.refs); }
1537
+ st.meta=m.meta;
1538
+ if(typeof buildDatalists==='function') buildDatalists();
1539
+ persist(); render();
1540
+ }
1541
+ function loadJson(file){ const rd=new FileReader(); rd.onload=()=>{ try{ applyLoadedJson(JSON.parse(rd.result)); toast('JSONを読み込みました'+(st.meta&&st.meta.versionId?('(v'+st.meta.versionId+')'):'')); }catch(e){ toast('読み込み失敗: 不正なJSON'); } }; rd.readAsText(file); }
1542
+
1543
+ /* ============================================================
1544
+ WIRING
1545
+ ============================================================ */
1546
+ document.getElementById('viewSeg').addEventListener('click',e=>{ const b=e.target.closest('button[data-v]'); if(!b) return; clearSel(); closeDrawer(); activeView=b.dataset.v; document.querySelectorAll('#viewSeg button').forEach(x=>x.classList.toggle('active',x===b)); document.querySelectorAll('.view').forEach(v=>v.classList.toggle('active', v.id==='view-'+activeView)); render(); });
1547
+ document.getElementById('sheetFilter').addEventListener('change',()=>{ clearSel(); renderSheet(); });
1548
+ /* プランビュー: シート / ツリー モード切替 (1階層下) */
1549
+ document.getElementById('modeSeg').addEventListener('click',e=>{ const b=e.target.closest('button[data-m]'); if(!b) return; clearSel(); closeDrawer(); planMode=b.dataset.m; document.querySelectorAll('#modeSeg button').forEach(x=>x.classList.toggle('active',x===b)); render(); });
1550
+ /* 媒体要件ビュー: YDA / Meta タブ切替 */
1551
+ document.getElementById('mediaTabs').addEventListener('click',e=>{ const b=e.target.closest('button[data-mt]'); if(!b) return; document.querySelectorAll('#mediaTabs button').forEach(x=>x.classList.toggle('active',x===b)); document.getElementById('mt-yda').style.display = b.dataset.mt==='yda'?'':'none'; document.getElementById('mt-meta').style.display = b.dataset.mt==='meta'?'':'none'; });
1552
+ /* 案件テンプレ: チップ直接編集 */
1553
+ document.getElementById('tplChips').addEventListener('change',e=>{ const el=e.target.closest('[data-proj]'); if(!el) return; const f=el.dataset.proj; const v = el.type==='number'? Number(el.value) : el.value; mutate(()=>{ st.project[f]=v; }); });
1554
+ /* 戦略メモ(開閉式・テーブル上) */
1555
+ document.getElementById('stratInput').placeholder = STRAT_PLACEHOLDER;
1556
+ document.getElementById('stratInput').addEventListener('input',e=>{ st.project.strategyMemo=e.target.value; persist(); });
1557
+ document.getElementById('btnSubmitJson').addEventListener('click',exportSubmitJson);
1558
+ document.getElementById('btnSave').addEventListener('click',saveVersion);
1559
+ document.getElementById('btnConnectFolder').addEventListener('click',connectFolder);
1560
+ document.getElementById('histHost').addEventListener('click',e=>{
1561
+ const ld=e.target.closest('[data-hload]'); if(ld){ histLoadVersion(+ld.dataset.hload); return; }
1562
+ const ed=e.target.closest('[data-hedit]'); if(ed){ histEditVersion(+ed.dataset.hedit); return; }
1563
+ const dl=e.target.closest('[data-hdel]'); if(dl){ histDeleteVersion(+dl.dataset.hdel); }
1564
+ });
1565
+ document.getElementById('btnLoad').addEventListener('click',()=>document.getElementById('fileInput').click());
1566
+ document.getElementById('fileInput').addEventListener('change',e=>{ if(e.target.files[0]) loadJson(e.target.files[0]); e.target.value=''; });
1567
+ document.getElementById('btnUndo').addEventListener('click',undo);
1568
+ document.getElementById('sheetTabs').addEventListener('click',e=>{ const b=e.target.closest('button[data-st]'); if(!b) return; sheetTab=b.dataset.st; clearSel(); closeDrawer(); renderSheet(); });
1569
+ document.getElementById('btnExpandAll').addEventListener('click',()=>{ st.ui.expanded={}; persist(); renderTree(); });
1570
+ document.getElementById('btnCollapseAll').addEventListener('click',()=>{ st.campaigns.forEach(c=>{ st.ui.expanded[c.uid]='c'; }); persist(); renderTree(); });
1571
+ document.addEventListener('keydown',e=>{ if((e.metaKey||e.ctrlKey)&&e.key==='z'){ e.preventDefault(); undo(); } });
1572
+
1573
+ /* ---- 参照データ wiring ---- */
1574
+ document.getElementById('refTabs').addEventListener('click',e=>{ const b=e.target.closest('button[data-rt]'); if(!b) return; refTab=b.dataset.rt; document.querySelectorAll('#refTabs button').forEach(x=>x.classList.toggle('active',x===b)); renderRef(); });
1575
+ document.getElementById('refSearch').addEventListener('input',renderRef);
1576
+ document.getElementById('btnRefAdd').addEventListener('click',refAdd);
1577
+ document.getElementById('btnRefReset').addEventListener('click',()=>{ snapshot(); st.refs=buildRefs(); persist(); buildDatalists(); render(); toast('参照データを初期値に戻しました'); });
1578
+ document.getElementById('refHost').addEventListener('click',e=>{ const ed=e.target.closest('[data-refedit]'); if(ed){ refEdit(ed.dataset.refedit, ed.dataset.no); return; } const dl=e.target.closest('[data-refdel]'); if(dl){ refDel(dl.dataset.refdel, dl.dataset.no); } });
1579
+ document.getElementById('btnDupSel').addEventListener('click',dupSelected);
1580
+
1581
+ /* ---- シート セル選択 (spreadsheet) ---- */
1582
+ const sheetHostEl=document.getElementById('sheetHost');
1583
+ function cellFromEvent(e){ let cell=e.target.closest('[data-sel]'); if(!cell){ const cc=e.target.closest('.cellcombo'); if(cc) cell=cc.querySelector('[data-sel]'); } if(!cell){ const td=e.target.closest('td'); if(td && !td.classList.contains('rownum')) cell=td.querySelector('[data-sel]'); } return cell; } // td全体を当たり判定に(広告タブに踏襲)
1584
+ let pendingDrawer=null, dragMoved=false; // ドラッグ範囲選択中はドロワーを開かない(mouseup時に単一クリックのみ開く)
1585
+ sheetHostEl.addEventListener('mousedown',e=>{
1586
+ if(e.target.closest('.combodd')) return; // ▼ はドロップダウン専用
1587
+ const rn=e.target.closest('td.rownum'); if(rn){ const r=+rn.dataset.rownum; if(isNaN(r)) return; selA={r,c:0}; selB={r,c:tabCols().length-1}; dragging='row'; pendingDrawer=null; applySelection(); return; }
1588
+ const cell=cellFromEvent(e); if(!cell) return; const r=+cell.dataset.r, c=+cell.dataset.c;
1589
+ pendingDrawer=null; dragMoved=false;
1590
+ if(e.shiftKey && selA){ selB={r,c}; e.preventDefault(); } else { selA={r,c}; selB={r,c}; dragging='cell'; if(sheetTab==='ads' && [1,2,3].indexOf(c)>=0) pendingDrawer={kind:'ad', u:cell.dataset.u, f:cell.dataset.f}; else if(sheetTab==='groups' && cell.dataset.gf && TG_OPTIONS[cell.dataset.gf]) pendingDrawer={kind:'tg', gu:cell.dataset.gu, gf:cell.dataset.gf}; }
1591
+ applySelection();
1592
+ });
1593
+ sheetHostEl.addEventListener('mouseover',e=>{
1594
+ if(!dragging) return;
1595
+ if(dragging==='row'){ const rn=e.target.closest('td.rownum'); if(rn){ selB={r:+rn.dataset.rownum,c:tabCols().length-1}; applySelection(); } return; }
1596
+ const cell=cellFromEvent(e); if(cell){ const r=+cell.dataset.r,c=+cell.dataset.c; if(!selB||selB.r!==r||selB.c!==c){ dragMoved=true; pendingDrawer=null; if(!selA||r!==selA.r||c!==selA.c) document.body.classList.add('noselect'); selB={r,c}; applySelection(); } }
1597
+ });
1598
+ sheetHostEl.addEventListener('click',e=>{ const dd=e.target.closest('.combodd'); if(!dd) return; e.preventDefault(); if(dd.dataset.tg){ openTargetingDrawer(dd.dataset.gu, dd.dataset.tg); } else { openRefDrawer(dd); } });
1599
+ /* ---- 右ドロワー wiring ---- */
1600
+ document.getElementById('drawerClose').addEventListener('click',closeDrawer);
1601
+ document.getElementById('drawerBackdrop').addEventListener('click',closeDrawer);
1602
+ document.getElementById('drawerSearch').addEventListener('input',renderDrawer);
1603
+ document.getElementById('drawerList').addEventListener('click',e=>{ const row=e.target.closest('.ddrow'); if(!row) return; if(drawerTarget&&drawerTarget.type==='tg') drawerTgToggle(row.dataset.tgval); else drawerPick(row.dataset.no); });
1604
+ document.addEventListener('mouseup',()=>{ if(pendingDrawer && !dragMoved){ if(pendingDrawer.kind==='ad') openDrawerFor(pendingDrawer.u,pendingDrawer.f); else openTargetingDrawer(pendingDrawer.gu,pendingDrawer.gf); } pendingDrawer=null; if(dragging){ dragging=false; document.body.classList.remove('noselect'); } });
1605
+
1606
+ /* ---- コピー / 貼付 ---- */
1607
+ document.addEventListener('copy',e=>{
1608
+ if(activeView!=='sheet'||planMode!=='sheet') return;
1609
+ const rg=selRange();
1610
+ if(rg){ const t=copySelText(); if(t!=null){ e.clipboardData.setData('text/plain',t); e.preventDefault(); } return; } // セル選択→常に自前TSV(ラベル/ボタンのDOMテキスト混入を防ぐ)
1611
+ const ae=document.activeElement; // セル選択なし: 単一inputの部分テキスト選択のみnative許可
1612
+ if(ae&&ae.classList&&ae.classList.contains('cell-in')&&ae.selectionStart!=null&&ae.selectionStart!==ae.selectionEnd) return;
1613
+ e.preventDefault();
1614
+ });
1615
+ document.addEventListener('paste',e=>{ if(activeView!=='sheet'||planMode!=='sheet') return; if(!selRange()) return; const txt=(e.clipboardData||window.clipboardData).getData('text'); if(txt==null) return; e.preventDefault(); pasteGrid(txt); });
1616
+ document.addEventListener('keydown',e=>{
1617
+ if(activeView!=='sheet'||planMode!=='sheet') return;
1618
+ if(e.key==='Escape'){ closeDrawer(); clearSel(); return; }
1619
+ if(e.shiftKey && selB && ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.key)){
1620
+ e.preventDefault(); const vis=flatVisible(); let r=selB.r,c=selB.c;
1621
+ if(e.key==='ArrowUp')r=Math.max(0,r-1); if(e.key==='ArrowDown')r=Math.min(vis.length-1,r+1);
1622
+ if(e.key==='ArrowLeft')c=Math.max(0,c-1); if(e.key==='ArrowRight')c=Math.min(tabCols().length-1,c+1);
1623
+ selB={r,c}; applySelection();
1624
+ }
1625
+ });
1626
+
1627
+ buildDatalists();
1628
+ render();
1629
+ restoreFolder(); // 保存フォルダのhandleをIndexedDBから復元 (権限失効時は再接続待ちに)
1630
+ </script>
1631
+ </body>
1632
+ </html>