@revclamp/google-ads-transparency-mcp-actor 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ {
2
+ "actorSpecification": 1,
3
+ "name": "google-ads-transparency-mcp-actor",
4
+ "title": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)",
5
+ "description": "Find verified Google Ads advertiser entities from any brand or domain (e.g. nike.com -> Nike, Inc.) and extract active and historical ad creatives (Text, Image, Video), preview URLs, and run dates with zero browser or proxy overhead. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.",
6
+ "version": "1.0",
7
+ "buildTag": "latest",
8
+ "minMemoryMbytes": 256,
9
+ "maxMemoryMbytes": 512,
10
+ "defaultRunOptions": {
11
+ "build": "latest",
12
+ "timeoutSecs": 25,
13
+ "memoryMbytes": 256
14
+ },
15
+ "input": "./input_schema.json",
16
+ "dockerfile": "../Dockerfile"
17
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "title": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)",
3
+ "type": "object",
4
+ "schemaVersion": 1,
5
+ "properties": {
6
+ "query": {
7
+ "title": "Brand Name or Domain",
8
+ "type": "string",
9
+ "description": "Brand name or domain to match against verified Google Ads Transparency advertiser entities (e.g. 'nike.com', 'Stripe', 'shopify.com').",
10
+ "maxLength": 512,
11
+ "editor": "textfield",
12
+ "example": "nike.com"
13
+ },
14
+ "advertiserId": {
15
+ "title": "Advertiser ID (Optional)",
16
+ "type": "string",
17
+ "description": "Optional Google Ads Transparency Advertiser ID (e.g. 'AR16735076323512287233'). When provided, queries this exact advertiser directly.",
18
+ "editor": "textfield"
19
+ },
20
+ "countryCode": {
21
+ "title": "Country Filter (Optional)",
22
+ "type": "string",
23
+ "description": "Optional 2-letter ISO country code filter (e.g. 'US'). Leave blank for all regions.",
24
+ "editor": "textfield"
25
+ },
26
+ "formatFilter": {
27
+ "title": "Ad Format",
28
+ "type": "string",
29
+ "description": "Filter returned ad creatives by format (ALL, TEXT, IMAGE, or VIDEO).",
30
+ "enum": ["ALL", "TEXT", "IMAGE", "VIDEO"],
31
+ "default": "ALL"
32
+ },
33
+ "includeSubsidiaries": {
34
+ "title": "Include Regional Subsidiaries",
35
+ "type": "boolean",
36
+ "description": "When enabled, matches up to 3 verified regional legal entities for the same brand (e.g. Stripe, Inc. US and Stripe Payments Europe, Limited IE).",
37
+ "default": true
38
+ },
39
+ "includeCreativeDetails": {
40
+ "title": "Include Creative Region & Legal Details",
41
+ "type": "boolean",
42
+ "description": "When enabled, fetches detailed regional presence and legal entity records for up to 10 returned creatives (default 5).",
43
+ "default": false
44
+ },
45
+ "includeAdCopy": {
46
+ "title": "Include Static Ad Copy (Optional)",
47
+ "type": "boolean",
48
+ "description": "When enabled alongside Include Creative Region & Legal Details, extracts static headline, body text, CTA, and destination domain from allowlisted preview scripts without eval.",
49
+ "default": false
50
+ },
51
+ "maxEntities": {
52
+ "title": "Max Advertiser Entities",
53
+ "type": "integer",
54
+ "description": "Maximum number of verified advertiser entities to query when regional subsidiaries are included (1 to 3).",
55
+ "minimum": 1,
56
+ "maximum": 3,
57
+ "default": 3
58
+ },
59
+ "maxResults": {
60
+ "title": "Max Creatives to Extract",
61
+ "type": "integer",
62
+ "description": "Maximum number of deduplicated ad creatives to extract in this run (1 to 200).",
63
+ "minimum": 1,
64
+ "maximum": 200,
65
+ "default": 40
66
+ },
67
+ "pageToken": {
68
+ "title": "Next-Page Cursor Token (Optional)",
69
+ "type": "string",
70
+ "description": "Optional continuation token returned by a previous run to resume from the next creative.",
71
+ "maxLength": 2048,
72
+ "editor": "textfield"
73
+ }
74
+ },
75
+ "required": []
76
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "maintainers": [
4
+ "lhphong09t2"
5
+ ]
6
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RevClamp Enterprise (https://revclamp.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Disclaimer:** Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.
4
+
5
+ Find verified Google Ads advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates directly from the Google Ads Transparency Center (`adstransparency.google.com`) with zero browser or residential proxy overhead. Available as both an Apify Pay-per-Event Actor and a Model Context Protocol (MCP) server (`@revclamp/google-ads-transparency-mcp-actor`).
6
+
7
+ - **Canonical Publisher Website:** [https://revclamp.com](https://revclamp.com)
8
+ - **Official MCP Package:** `@revclamp/google-ads-transparency-mcp-actor` (`com.revclamp/google-ads-transparency-mcp-actor`)
9
+
10
+ ## Key Capabilities
11
+
12
+ 1. **Smart Advertiser Entity Matching (`SearchSuggestions`):**
13
+ - Matches internationalized domains (`nike.com`, `stripe.com`, `shopify.co.uk`, `brand.myshopify.com`, `on-running.com`, `bankofamerica.com`, `thenorthface.com`) and multi-token or Unicode brand queries (CJK/Hangul corporate names, `"The Coca-Cola Company"`, `L'Oréal`) to verified Google Ads Transparency legal entities (`AR...`).
14
+ - Prevents substring lookalike mismatches where querying `nike.com` selects `NIKETA'S COMPANY S.R.L.` instead of `Nike, Inc.` (`AR16735076323512287233`), and enforces a strict leading-word identity gate so middle-word lookalikes (`Big Apple Pizza LLC`) or unrelated same-first-word companies (`Nike AI Solutions`) are never admitted as corporate subsidiaries.
15
+ - Supports regional subsidiary inclusion (`includeSubsidiaries: true`, up to 3 verified regional corporate entities such as `Stripe, Inc.` US + `Stripe Payments Europe, Limited` IE).
16
+ 2. **Paginated Creative & Asset Extraction (`SearchCreatives` & `LookupService/GetCreativeById`):**
17
+ - Maps ISO-2 `countryCode` filters to Google Ads Geo Target Criterion IDs (`US -> 2840`, `GB -> 2826`, `DE -> 2276`, `SG -> 2702`, `VN -> 2704`) and extracts normalized `TEXT`, `IMAGE`, and `VIDEO` ad creatives with decoded `previewUrl`, `archiveImageUrls`, `firstSeenAtIso`, `lastSeenAtIso`, `daysActive`, `timestampsRepaired`, `regionalPresence`, and `transparencyUrl`.
18
+ - Preserves mid-page pagination resume via opaque continuation cursors (`rcp1_...`, `maxLength: 2048`, with query-scope hash `qh`, raw page length `rl`, and anchor `creativeId` `ac`) and automatically rewinds `pagination.nextPageToken` whenever `maxResults`, Apify `CHARGE_LIMIT_REACHED`, `DEADLINE_EXCEEDED_BEFORE_CHARGE`, or the 30,000-byte MCP response ceiling truncates a page. Pass the returned `pagination.nextPageToken` back as `pageToken` in the next Actor input or MCP tool call to resume without duplicate charges or skipped creatives.
19
+ 3. **Zero-Charge Schema Guard, Rate-Limit Safety & Crash-Safe Apify Billing:**
20
+ - Separates legitimate empty results (`EMPTY_RESULT`) from upstream HTTP errors (`UPSTREAM_ERROR`), rate-limiting/CAPTCHA challenges (`UPSTREAM_RATE_LIMITED`), and protobuf-JSON schema changes (`SCHEMA_DRIFT_HALT`), halting before any Pay-per-Event billing charge is emitted.
21
+ - Integrates natively with Apify Cloud (`ACTOR_RUN_ID`, `ACTOR_DEFAULT_DATASET_ID`, `ACTOR_DEFAULT_KEY_VALUE_STORE_ID`, `ACTOR_MEMORY_MBYTES`, with fallback to `APIFY_*` legacy aliases), writes the run summary and `pagination.nextPageToken` to the default Key-Value Store `OUTPUT` record on every exit path as well as stdout, and tracks `inFlightIds`, `chargedIds`, `deliveredIds`, `unknownOutcomeIds`, and `stagedItemsById` with bounded `pushData` retry so transient failures or same-run resumptions never double-charge or lose staged paid payloads.
22
+
23
+ ## CLI & Verification
24
+
25
+ ```bash
26
+ # Run MCP stdio server
27
+ npx @revclamp/google-ads-transparency-mcp-actor --mcp-stdio
28
+
29
+ # Start local interactive web playground on http://127.0.0.1:4410
30
+ npm run playground
31
+
32
+ # Run full unit, golden-corpus, metamorphic, mutation, and CDP E2E test suites
33
+ npm test
34
+ ```
35
+
36
+ ## Terms of Use, Data Provenance & Independent Tool Disclaimer
37
+
38
+ - **Public Transparency Records Only:** RevClamp AdPulse queries publicly accessible transparency disclosures published on the Google Ads Transparency Center (`adstransparency.google.com`) for research, brand-safety auditing, regulatory compliance, and competitive ad intelligence.
39
+ - **No Circumvention or Personal Data:** This tool operates strictly against unauthenticated public transparency endpoints, enforces a strict hostname allowlist (`adstransparency.google.com`, `displayads-formats.googleusercontent.com`, `tpc.googlesyndication.com`), strips cookies and credentials on egress, and halts immediately (`UPSTREAM_RATE_LIMITED`) if upstream rate limits or CAPTCHA challenges (`HTTP 429` / `/sorry/`) are encountered.
40
+ - **Trademark & Affiliation Notice:** "Google" and "Google Ads" are trademarks of Google LLC. RevClamp AdPulse is an independent software tool developed by RevClamp and is not affiliated with, sponsored by, or endorsed by Google LLC.
@@ -0,0 +1,29 @@
1
+ {
2
+ "actorType": "apify_actor",
3
+ "pricingModel": {
4
+ "type": "pay_per_event",
5
+ "priceUsdc": 0.0012,
6
+ "costCeilingUsdc": 0.0001,
7
+ "platformFeeRate": 0.2,
8
+ "minMarginMultiplier": 2.5
9
+ },
10
+ "resourceLimits": {
11
+ "maxTimeoutSec": 25,
12
+ "maxMemoryMb": 256,
13
+ "maxPayloadBytes": 30000
14
+ },
15
+ "allowedEgressUrls": [
16
+ "https://adstransparency.google.com/anji/_/rpc/SearchService/SearchSuggestions",
17
+ "https://adstransparency.google.com/anji/_/rpc/SearchService/SearchCreatives",
18
+ "https://adstransparency.google.com/anji/_/rpc/LookupService/GetCreativeById",
19
+ "https://displayads-formats.googleusercontent.com/ads/preview/content.js",
20
+ "https://tpc.googlesyndication.com/archive/simgad/1"
21
+ ],
22
+ "manifest": {
23
+ "name": "google-ads-transparency-mcp-actor",
24
+ "version": "1.0.1",
25
+ "title": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)",
26
+ "websiteUrl": "https://revclamp.com",
27
+ "description": "Find verified Google Ads advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates without browser or proxy overhead. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC."
28
+ }
29
+ }
Binary file
Binary file
@@ -0,0 +1,26 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-labelledby="rc-title">
2
+ <title id="rc-title">RevClamp AdPulse</title>
3
+ <defs>
4
+ <linearGradient id="rc-fill" gradientUnits="userSpaceOnUse" x1="32" y1="6" x2="32" y2="58">
5
+ <stop offset="0" stop-color="#111C38"/>
6
+ <stop offset="1" stop-color="#0B1020"/>
7
+ </linearGradient>
8
+ <linearGradient id="rc-edge" gradientUnits="userSpaceOnUse" x1="11" y1="6" x2="53" y2="58">
9
+ <stop offset="0" stop-color="#10B981"/>
10
+ <stop offset="1" stop-color="#06B6D4"/>
11
+ </linearGradient>
12
+ </defs>
13
+ <rect width="64" height="64" rx="14" fill="#0B1020"/>
14
+ <path d="M32 6L53 13.5V31C53 44 44.5 52.5 32 58C19.5 52.5 11 44 11 31V13.5Z" fill="url(#rc-fill)" stroke="url(#rc-edge)" stroke-width="2" stroke-linejoin="round"/>
15
+ <path d="M38.5 36.5L44.5 42.5" stroke="#F59E0B" stroke-width="4" stroke-linecap="round" fill="none"/>
16
+ <circle cx="30.5" cy="28.5" r="12.5" fill="#0B1020"/>
17
+ <path d="M30.5 17.5V39.5M19.5 28.5H41.5" stroke="#06B6D4" stroke-opacity="0.35" stroke-width="1.2" fill="none"/>
18
+ <circle cx="30.5" cy="28.5" r="6" stroke="#06B6D4" stroke-opacity="0.5" stroke-width="1.2" fill="none"/>
19
+ <path d="M30.5 28.5V17.5A11 11 0 0 1 40.03 23Z" fill="#10B981" fill-opacity="0.38"/>
20
+ <path d="M30.5 28.5V17.5" stroke="#10B981" stroke-width="1.6" stroke-linecap="round" fill="none"/>
21
+ <circle cx="34" cy="22.4" r="4" fill="#F59E0B" fill-opacity="0.25"/>
22
+ <circle cx="34" cy="22.4" r="2.4" fill="#F59E0B"/>
23
+ <circle cx="25.5" cy="33" r="4.2" fill="#10B981"/>
24
+ <path d="M23.4 33.2L24.9 34.7L27.7 31.4" stroke="#0B1020" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
25
+ <circle cx="30.5" cy="28.5" r="12.5" stroke="#F8FAFC" stroke-width="2.5" fill="none"/>
26
+ </svg>
@@ -0,0 +1,468 @@
1
+ :root {
2
+ --bg-obsidian: #0b1020;
3
+ --bg-panel: #111c38;
4
+ --bg-card: #162344;
5
+ --border-subtle: #243560;
6
+ --text-primary: #f8fafc;
7
+ --text-secondary: #cbd5e1;
8
+ --text-muted: #94a3b8;
9
+ --accent-emerald: #10b981;
10
+ --accent-emerald-hover: #059669;
11
+ --accent-cyan: #06b6d4;
12
+ --accent-amber: #f59e0b;
13
+ --danger-rose: #f43f5e;
14
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
15
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
16
+ }
17
+
18
+ *,
19
+ *::before,
20
+ *::after {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ html {
25
+ scroll-padding-top: 80px;
26
+ background-color: var(--bg-obsidian);
27
+ color: var(--text-primary);
28
+ font-family: var(--font-sans);
29
+ line-height: 1.5;
30
+ -webkit-text-size-adjust: 100%;
31
+ }
32
+
33
+ body {
34
+ margin: 0;
35
+ padding: 0;
36
+ min-height: 100vh;
37
+ display: flex;
38
+ flex-direction: column;
39
+ overflow-x: hidden;
40
+ background: radial-gradient(circle at 15% 0%, #15254b 0%, var(--bg-obsidian) 65%);
41
+ }
42
+
43
+ .site-header {
44
+ background: rgba(11, 16, 32, 0.96);
45
+ border-bottom: 1px solid var(--border-subtle);
46
+ padding: 12px 16px;
47
+ }
48
+
49
+ .header-inner {
50
+ max-width: 1360px;
51
+ margin: 0 auto;
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ align-items: center;
55
+ justify-content: space-between;
56
+ gap: 12px;
57
+ }
58
+
59
+ .brand-group {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 12px;
63
+ min-width: 0;
64
+ }
65
+
66
+ .brand-logo {
67
+ width: 36px;
68
+ height: 36px;
69
+ flex-shrink: 0;
70
+ border-radius: 8px;
71
+ }
72
+
73
+ .brand-titles {
74
+ min-width: 0;
75
+ }
76
+
77
+ .brand-title {
78
+ margin: 0;
79
+ font-size: 1.05rem;
80
+ font-weight: 700;
81
+ letter-spacing: -0.01em;
82
+ color: var(--text-primary);
83
+ overflow-wrap: anywhere;
84
+ }
85
+
86
+ .brand-subtitle {
87
+ margin: 2px 0 0;
88
+ font-size: 0.8rem;
89
+ color: var(--text-secondary);
90
+ overflow-wrap: anywhere;
91
+ }
92
+
93
+ .header-badges {
94
+ display: flex;
95
+ flex-wrap: wrap;
96
+ align-items: center;
97
+ gap: 8px;
98
+ }
99
+
100
+ .badge {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ padding: 4px 10px;
104
+ border-radius: 999px;
105
+ font-size: 0.75rem;
106
+ font-weight: 600;
107
+ border: 1px solid var(--border-subtle);
108
+ background: var(--bg-panel);
109
+ color: var(--text-secondary);
110
+ }
111
+
112
+ .badge-emerald {
113
+ border-color: rgba(16, 185, 129, 0.45);
114
+ background: rgba(16, 185, 129, 0.14);
115
+ color: #6ee7b7;
116
+ }
117
+
118
+ .badge-cyan {
119
+ border-color: rgba(6, 182, 212, 0.45);
120
+ background: rgba(6, 182, 212, 0.14);
121
+ color: #67e8f9;
122
+ }
123
+
124
+ .badge-amber {
125
+ border-color: rgba(245, 158, 11, 0.45);
126
+ background: rgba(245, 158, 11, 0.14);
127
+ color: #fde68a;
128
+ }
129
+
130
+ .workbench-main {
131
+ flex: 1;
132
+ width: 100%;
133
+ max-width: 1360px;
134
+ margin: 0 auto;
135
+ padding: 16px;
136
+ display: grid;
137
+ grid-template-columns: 1fr;
138
+ gap: 16px;
139
+ }
140
+
141
+ @media (min-width: 720px) {
142
+ .workbench-main {
143
+ grid-template-columns: minmax(290px, 380px) minmax(0, 1fr);
144
+ align-items: start;
145
+ }
146
+ }
147
+
148
+ .panel {
149
+ background: var(--bg-panel);
150
+ border: 1px solid var(--border-subtle);
151
+ border-radius: 12px;
152
+ padding: 16px;
153
+ min-width: 0;
154
+ }
155
+
156
+ .panel-heading {
157
+ margin: 0 0 12px;
158
+ font-size: 0.95rem;
159
+ font-weight: 600;
160
+ color: var(--text-primary);
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ align-items: center;
164
+ justify-content: space-between;
165
+ gap: 8px;
166
+ }
167
+
168
+ .panel-heading-title {
169
+ margin: 0;
170
+ font-size: 0.95rem;
171
+ font-weight: 600;
172
+ color: var(--text-primary);
173
+ }
174
+
175
+ .export-actions-row {
176
+ display: flex;
177
+ flex-wrap: wrap;
178
+ align-items: center;
179
+ gap: 8px;
180
+ }
181
+
182
+ .btn:disabled {
183
+ opacity: 0.5;
184
+ cursor: not-allowed;
185
+ }
186
+
187
+ .form-stack {
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 12px;
191
+ min-width: 0;
192
+ }
193
+
194
+ .field-group {
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 4px;
198
+ min-width: 0;
199
+ }
200
+
201
+ .field-label {
202
+ font-size: 0.82rem;
203
+ font-weight: 600;
204
+ color: var(--text-secondary);
205
+ }
206
+
207
+ .field-hint {
208
+ font-size: 0.74rem;
209
+ color: var(--text-muted);
210
+ overflow-wrap: anywhere;
211
+ }
212
+
213
+ .input-control,
214
+ .select-control {
215
+ width: 100%;
216
+ min-height: 44px;
217
+ padding: 8px 12px;
218
+ border-radius: 8px;
219
+ border: 1px solid var(--border-subtle);
220
+ background: var(--bg-obsidian);
221
+ color: var(--text-primary);
222
+ font-size: 0.9rem;
223
+ font-family: var(--font-sans);
224
+ }
225
+
226
+ .input-control:focus,
227
+ .select-control:focus,
228
+ .btn:focus {
229
+ outline: 2px solid var(--accent-cyan);
230
+ outline-offset: 2px;
231
+ }
232
+
233
+ .checkbox-row {
234
+ display: flex;
235
+ align-items: center;
236
+ gap: 10px;
237
+ min-height: 44px;
238
+ padding: 4px 0;
239
+ cursor: pointer;
240
+ font-size: 0.85rem;
241
+ color: var(--text-secondary);
242
+ }
243
+
244
+ .checkbox-input {
245
+ width: 22px;
246
+ height: 22px;
247
+ accent-color: var(--accent-emerald);
248
+ cursor: pointer;
249
+ flex-shrink: 0;
250
+ }
251
+
252
+ .button-row {
253
+ display: flex;
254
+ flex-wrap: wrap;
255
+ gap: 10px;
256
+ margin-top: 4px;
257
+ }
258
+
259
+ .btn {
260
+ min-height: 44px;
261
+ min-width: 44px;
262
+ padding: 10px 16px;
263
+ border-radius: 8px;
264
+ border: 1px solid transparent;
265
+ font-size: 0.88rem;
266
+ font-weight: 600;
267
+ cursor: pointer;
268
+ display: inline-flex;
269
+ align-items: center;
270
+ justify-content: center;
271
+ gap: 8px;
272
+ text-decoration: none;
273
+ transition: background-color 0.15s ease, border-color 0.15s ease;
274
+ }
275
+
276
+ .btn-primary {
277
+ background: var(--accent-emerald);
278
+ color: #04130e;
279
+ flex: 1;
280
+ }
281
+
282
+ .btn-primary:hover {
283
+ background: var(--accent-emerald-hover);
284
+ }
285
+
286
+ .btn-secondary {
287
+ background: var(--bg-card);
288
+ border-color: var(--border-subtle);
289
+ color: var(--text-primary);
290
+ }
291
+
292
+ .btn-secondary:hover {
293
+ border-color: var(--accent-cyan);
294
+ }
295
+
296
+ .btn[ hidden ],
297
+ .hidden {
298
+ display: none !important;
299
+ }
300
+
301
+ .status-banner {
302
+ padding: 10px 12px;
303
+ border-radius: 8px;
304
+ border: 1px solid var(--border-subtle);
305
+ background: var(--bg-obsidian);
306
+ font-size: 0.84rem;
307
+ color: var(--text-secondary);
308
+ margin-bottom: 12px;
309
+ overflow-wrap: anywhere;
310
+ }
311
+
312
+ .status-banner[data-state="ok"] {
313
+ border-color: rgba(16, 185, 129, 0.5);
314
+ color: #a7f3d0;
315
+ }
316
+
317
+ .status-banner[data-state="warn"] {
318
+ border-color: rgba(245, 158, 11, 0.55);
319
+ color: #fde68a;
320
+ }
321
+
322
+ .status-banner[data-state="error"] {
323
+ border-color: rgba(244, 63, 94, 0.55);
324
+ color: #fecdd3;
325
+ }
326
+
327
+ .telemetry-grid {
328
+ display: grid;
329
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
330
+ gap: 10px;
331
+ margin-bottom: 14px;
332
+ }
333
+
334
+ .telemetry-card {
335
+ background: var(--bg-obsidian);
336
+ border: 1px solid var(--border-subtle);
337
+ border-radius: 8px;
338
+ padding: 10px;
339
+ min-width: 0;
340
+ }
341
+
342
+ .telemetry-label {
343
+ font-size: 0.72rem;
344
+ text-transform: uppercase;
345
+ letter-spacing: 0.04em;
346
+ color: var(--text-muted);
347
+ }
348
+
349
+ .telemetry-value {
350
+ margin-top: 4px;
351
+ font-size: 0.92rem;
352
+ font-weight: 700;
353
+ color: var(--text-primary);
354
+ font-family: var(--font-mono);
355
+ overflow-wrap: anywhere;
356
+ }
357
+
358
+ .cards-stack {
359
+ display: flex;
360
+ flex-direction: column;
361
+ gap: 12px;
362
+ min-width: 0;
363
+ }
364
+
365
+ .creative-card {
366
+ background: var(--bg-card);
367
+ border: 1px solid var(--border-subtle);
368
+ border-radius: 10px;
369
+ padding: 14px;
370
+ min-width: 0;
371
+ display: flex;
372
+ flex-direction: column;
373
+ gap: 8px;
374
+ }
375
+
376
+ .creative-card-header {
377
+ display: flex;
378
+ flex-wrap: wrap;
379
+ align-items: center;
380
+ justify-content: space-between;
381
+ gap: 8px;
382
+ }
383
+
384
+ .creative-title {
385
+ font-weight: 700;
386
+ font-size: 0.92rem;
387
+ color: var(--text-primary);
388
+ overflow-wrap: anywhere;
389
+ }
390
+
391
+ .creative-meta {
392
+ font-size: 0.8rem;
393
+ color: var(--text-secondary);
394
+ font-family: var(--font-mono);
395
+ overflow-wrap: anywhere;
396
+ }
397
+
398
+ .creative-ad-copy {
399
+ background: rgba(11, 16, 32, 0.72);
400
+ border: 1px solid var(--border-subtle);
401
+ border-radius: 8px;
402
+ padding: 10px 12px;
403
+ display: flex;
404
+ flex-direction: column;
405
+ gap: 6px;
406
+ min-width: 0;
407
+ }
408
+
409
+ .creative-ad-copy-header {
410
+ display: flex;
411
+ flex-wrap: wrap;
412
+ align-items: center;
413
+ gap: 6px;
414
+ }
415
+
416
+ .creative-ad-headline {
417
+ font-size: 0.88rem;
418
+ font-weight: 600;
419
+ color: var(--text-primary);
420
+ overflow-wrap: anywhere;
421
+ }
422
+
423
+ .creative-ad-body {
424
+ font-size: 0.82rem;
425
+ color: var(--text-secondary);
426
+ overflow-wrap: anywhere;
427
+ }
428
+
429
+ .creative-ad-dest {
430
+ font-size: 0.76rem;
431
+ font-family: var(--font-mono);
432
+ color: var(--text-muted);
433
+ overflow-wrap: anywhere;
434
+ }
435
+
436
+ .creative-links {
437
+ display: flex;
438
+ flex-wrap: wrap;
439
+ gap: 8px;
440
+ margin-top: 4px;
441
+ }
442
+
443
+ .raw-json-box {
444
+ margin-top: 14px;
445
+ background: var(--bg-obsidian);
446
+ border: 1px solid var(--border-subtle);
447
+ border-radius: 8px;
448
+ padding: 12px;
449
+ font-family: var(--font-mono);
450
+ font-size: 0.78rem;
451
+ color: var(--text-secondary);
452
+ max-height: 360px;
453
+ overflow-y: auto;
454
+ overflow-x: auto;
455
+ white-space: pre-wrap;
456
+ overflow-wrap: anywhere;
457
+ word-break: break-word;
458
+ }
459
+
460
+ .site-footer {
461
+ border-top: 1px solid var(--border-subtle);
462
+ background: rgba(11, 16, 32, 0.95);
463
+ padding: 14px 16px;
464
+ text-align: center;
465
+ font-size: 0.78rem;
466
+ color: var(--text-muted);
467
+ overflow-wrap: anywhere;
468
+ }